diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index f21d9fed3a..47e47a6a5c 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -265,7 +265,7 @@ jobs: cabal --version - name: Install non-Haskell dependencies (ubuntu) if: contains(matrix.os, 'ubuntu') - run: sudo apt-get install -y libgflags-dev liblz4-dev libzstd-dev libsnappy-dev libbz2-dev + run: sudo apt-get install -y libgflags-dev liblz4-dev libzstd-dev libsnappy-dev libbz2-dev libmpfr-dev # Project Configuration - name: Create cabal.project.local run: | @@ -781,6 +781,7 @@ jobs: libbz2-1.0 \ libgflags2.2 \ zstd \ + libmpfr6 \ locales && rm -rf /var/lib/apt/lists/* && locale-gen en_US.UTF-8 && diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f5bda65d9..1fdaecc578 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -223,10 +223,9 @@ jobs: * $UBUNTU_VERSION: ```sh - apt-get install ca-certificates libgmp10 libssl3 libsnappy1v5 zlib1g liblz4-1 libbz2-1.0 libgflags2.2 zstd locales - - + apt-get install ca-certificates libgmp10 libssl3 libsnappy1v5 zlib1g liblz4-1 libbz2-1.0 libgflags2.2 zstd libmpfr6 locales ``` + ' >> CHANGELOG.md cat CHANGELOG.md diff --git a/README.md b/README.md index 0e36cab7d1..7638493fb6 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,12 @@ The following packages must be installed on the host system: * ubuntu-20.04: ```bash - apt-get install ca-certificates libgmp10 libssl1.1 libsnappy1v5 zlib1g liblz4-1 libbz2-1.0 libgflags2.2 zstd + apt-get install ca-certificates libmpfr6 libgmp10 libssl1.1 libsnappy1v5 zlib1g liblz4-1 libbz2-1.0 libgflags2.2 zstd ``` * ubuntu-22.04: ```bash - apt-get install ca-certificates libgmp10 libssl1.1 libsnappy1v5 zlib1g liblz4-1 libbz2-1.0 libgflags2.2 zstd + apt-get install ca-certificates libmpfr6 libgmp10 libssl1.1 libsnappy1v5 zlib1g liblz4-1 libbz2-1.0 libgflags2.2 zstd ``` Chainweb-node binaries for ubuntu-20.04 and ubuntu-22.04 can be found @@ -130,7 +130,7 @@ You need to install the development versions of the following libraries: On apt based distribution these can be installed as follows: ``` -apt-get install ca-certificates libssl-dev libgmp-dev libsnappy-dev zlib1g-dev liblz4-dev libbz2-dev libgflags-dev libzstd-dev +apt-get install ca-certificates libssl-dev libmpfr-dev libgmp-dev libsnappy-dev zlib1g-dev liblz4-dev libbz2-dev libgflags-dev libzstd-dev ``` To build a `chainweb-node` binary: diff --git a/bench/Chainweb/Pact/Backend/Bench.hs b/bench/Chainweb/Pact/Backend/Bench.hs index 1cf6ac962e..dcae080b75 100644 --- a/bench/Chainweb/Pact/Backend/Bench.hs +++ b/bench/Chainweb/Pact/Backend/Bench.hs @@ -52,7 +52,8 @@ import Chainweb.BlockHeader.Internal import Chainweb.Graph import Chainweb.Logger import Chainweb.MerkleLogHash -import Chainweb.Pact.Backend.RelationalCheckpointer +import Chainweb.Pact.PactService.Checkpointer.Internal + import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils import Chainweb.Pact.Types @@ -60,6 +61,8 @@ 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 Pact.Types.Command as Pact testVer :: ChainwebVersion testVer = instantCpmTestVersion petersonChainGraph @@ -70,13 +73,13 @@ 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) + :: (Monoid q, Logger logger) => Checkpointer logger -> Maybe BlockHeader - -> [(BlockHeader, ChainwebPactDbEnv logger -> IO q)] + -> [(BlockHeader, PactDbEnv (Pact4.BlockEnv logger) -> IO q)] -> IO q -cpRestoreAndSave cp pc blks = snd <$> _cpRestoreAndSave cp (ParentHeader <$> pc) - (traverse Stream.yield [RunnableBlock $ \dbEnv _ -> (,bh) <$> fun (_cpPactDbEnv dbEnv) | (bh, fun) <- blks]) +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 @@ -150,7 +153,7 @@ cpWithBench torun = let neverLogger = genericLogger Error (\_ -> return ()) !sqliteEnv <- openSQLiteConnection dbFile chainwebPragmas !cenv <- - initRelationalCheckpointer defaultModuleCacheLimit sqliteEnv DoNotPersistIntraBlockWrites neverLogger testVer testChainId + initCheckpointerResources defaultModuleCacheLimit sqliteEnv DoNotPersistIntraBlockWrites neverLogger testVer testChainId return $ NoopNFData (sqliteEnv, cenv) teardown (NoopNFData (sqliteEnv, _cenv)) = closeSQLiteConnection sqliteEnv @@ -159,7 +162,7 @@ cpWithBench torun = benches cpenv = [ torun cpenv ] -cpBenchNoRewindOverBlock :: Int -> Checkpointer logger -> C.Benchmark +cpBenchNoRewindOverBlock :: Logger logger => Int -> Checkpointer logger -> C.Benchmark cpBenchNoRewindOverBlock transactionCount cp = C.env setup' $ \ ~ut -> C.bench name $ C.nfIO $ do mv <- newMVar (initbytestring, pc01) @@ -201,7 +204,7 @@ cpBenchNoRewindOverBlock transactionCount cp = C.env setup' $ \ ~ut -> where transaction db = incIntegerAtKey db ut f k 1 -cpBenchOverBlock :: Int -> Checkpointer logger -> C.Benchmark +cpBenchOverBlock :: Logger logger => Int -> Checkpointer logger -> C.Benchmark cpBenchOverBlock transactionCount cp = C.env setup' $ \ ~(ut) -> C.bench benchname $ C.nfIO (go ut) where @@ -335,7 +338,7 @@ benchUserTableForKeys numSampleEvents dbEnv = writeRow db Update ut f rowkeyb a -_cpBenchKeys :: Int -> Checkpointer logger -> C.Benchmark +_cpBenchKeys :: Logger logger => Int -> Checkpointer logger -> C.Benchmark _cpBenchKeys numKeys cp = C.env setup' $ \ ~(ut) -> C.bench name $ C.nfIO (go ut) where @@ -368,7 +371,7 @@ _cpBenchKeys numKeys cp = let rowkey = RowKey $ "k" <> sshow numkey incIntegerAtKey db ut f rowkey 1 -cpBenchSampleKeys :: Int -> Checkpointer logger -> C.Benchmark +cpBenchSampleKeys :: Logger logger => Int -> Checkpointer logger -> C.Benchmark cpBenchSampleKeys numSampleEvents cp = C.env setup' $ \ ~(ut) -> C.bench name $ C.nfIO (go ut) where @@ -414,7 +417,7 @@ cpBenchSampleKeys numSampleEvents cp = )] -cpBenchLookupProcessedTx :: Int -> Checkpointer logger -> C.Benchmark +cpBenchLookupProcessedTx :: Logger logger => Int -> Checkpointer logger -> C.Benchmark cpBenchLookupProcessedTx transactionCount cp = C.env setup' $ \ ~(ut) -> C.bench benchname $ C.nfIO (go ut) where @@ -442,5 +445,5 @@ cpBenchLookupProcessedTx transactionCount cp = C.env setup' $ \ ~(ut) -> pc02 = childOf (Just pc01) hash02 go (NoopNFData _) = do - _cpReadFrom (_cpReadCp cp) (Just (ParentHeader pc02)) $ \dbEnv -> - _cpLookupProcessedTx dbEnv (V.fromList [Pact.TypedHash "" | _ <- [1..transactionCount]]) + 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 aa106129f3..6ccbffb3df 100644 --- a/bench/Chainweb/Pact/Backend/ForkingBench.hs +++ b/bench/Chainweb/Pact/Backend/ForkingBench.hs @@ -15,6 +15,8 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE PartialTypeSignatures #-} +{-# OPTIONS_GHC -Wno-partial-type-signatures #-} module Chainweb.Pact.Backend.ForkingBench ( bench ) where @@ -31,6 +33,7 @@ import Data.Aeson hiding (Error) import Data.ByteString (ByteString) import Data.Char import Data.Decimal +import Data.Either import Data.FileEmbed import Data.Foldable (toList) import Data.IORef @@ -81,15 +84,15 @@ import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.ChainId import Chainweb.Graph import Chainweb.Logger -import Chainweb.Mempool.Mempool (BlockFill(..)) +import Chainweb.Mempool.Mempool import Chainweb.Miner.Pact import Chainweb.Pact.Backend.Compaction qualified as C + import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils import Chainweb.Pact.PactService import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Service.Types import Chainweb.Pact.Types import Chainweb.Pact.Utils (toTxCreationTime) import Chainweb.Payload @@ -97,7 +100,7 @@ import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.InMemory import Chainweb.Test.TestVersions (slowForkingCpmTestVersion) import Chainweb.Time -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils import Chainweb.Utils.Bench import Chainweb.Version @@ -242,7 +245,7 @@ createBlock validate parent nonce pact = do -- assemble block without nonce and timestamp bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill parent pact - let payload = blockInProgressToPayloadWithOutputs bip + let payload = forAnyPactVersion finalizeBlock bip let creationTime = add second $ view blockCreationTime $ _parentHeader parent let bh = newBlockHeader @@ -341,7 +344,7 @@ withResources rdb trunkLength logLevel compact p f = C.envWithCleanup create des startPact version l bhdb pdb mempool sqlEnv = do reqQ <- newPactQueue pactQueueSize a <- async $ runPactService version cid l Nothing reqQ mempool bhdb pdb sqlEnv testPactServiceConfig - { _pactBlockGasLimit = 180_000 + { _pactNewBlockGasLimit = 180_000 , _pactPersistIntraBlockWrites = p } @@ -383,15 +386,16 @@ withResources rdb trunkLength logLevel compact p f = C.envWithCleanup create des testMemPoolAccess :: IORef Int -> MVar (Map Account (NonEmpty (DynKeyPair, [SigCapability]))) -> IO MemPoolAccess testMemPoolAccess txsPerBlock accounts = do return $ mempty - { mpaGetBlock = \bf validate bh hash header -> do + { mpaGetBlock = \bf validate bh hash bct -> do if _bfCount bf /= 0 then pure mempty else do - testBlock <- getTestBlock accounts (_bct $ view blockCreationTime header) validate bh hash + testBlock <- getTestBlock accounts (_bct bct) validate bh hash pure testBlock } where setTime time pb = pb { _pmCreationTime = toTxCreationTime time } + getTestBlock :: _ -> _ -> MempoolPreBlockCheck Pact4.UnparsedTransaction to -> _ -> _ -> IO (V.Vector to) getTestBlock mVarAccounts txOrigTime validate bHeight hash | bHeight == 1 = do meta <- setTime txOrigTime <$> makeMeta cid @@ -402,10 +406,10 @@ testMemPoolAccess txsPerBlock accounts = do modifyMVar' mVarAccounts (const $ M.fromList $ zip as kss) - vs <- validate bHeight hash (V.fromList $ toList r) + vs <- validate bHeight hash (V.fromList $ toList $ Pact4.unparseTransaction <$> r) -- TODO: something better should go here - unless (and vs) $ throwM $ userError $ "at blockheight 1: tx validation failed " <> sshow vs - return $! V.fromList $ toList r + unless (all isRight vs) $ throwM $ userError $ "at blockheight 1: tx validation failed " <> sshow r + return $! V.fromList [v | Right v <- toList vs] | otherwise = do withMVar mVarAccounts $ \accs -> do @@ -419,7 +423,9 @@ testMemPoolAccess txsPerBlock accounts = do case eCmd of Left e -> throwM $ userError e Right tx -> return tx - return $! txs + vs <- validate bHeight hash (V.fromList $ toList $ Pact4.unparseTransaction <$> txs) + unless (all isRight vs) $ throwM $ userError $ "tx validation failed " <> sshow txs + return $! V.fromList [v | Right v <- toList vs] mkTransferCaps :: ReceiverName -> Amount -> (Account, NonEmpty (DynKeyPair, [SigCapability])) -> (Account, NonEmpty (DynKeyPair, [SigCapability])) mkTransferCaps (ReceiverName (Account r)) (Amount m) (s@(Account ss),ks) = (s, (caps <$) <$> ks) @@ -509,10 +515,10 @@ safeCapitalize :: String -> String safeCapitalize = maybe [] (uncurry (:) . bimap toUpper (Prelude.map toLower)) . Data.List.uncons --- TODO: Use the new `assertCommand` function. -validateCommand :: Command Text -> Either String ChainwebTransaction +-- TODO: Use the new `assertPact4Command` function. +validateCommand :: Command Text -> Either String Pact4.Transaction validateCommand cmdText = case verifyCommand cmdBS of - ProcSucc cmd -> Right (mkPayloadWithTextOld <$> cmd) + ProcSucc cmd -> Right (Pact4.mkPayloadWithTextOld <$> cmd) ProcFail err -> Left err where cmdBS :: Command ByteString diff --git a/cabal.project b/cabal.project index f6c0225a76..82cde178f0 100644 --- a/cabal.project +++ b/cabal.project @@ -61,6 +61,11 @@ package pact -- avoid conflict with cryptonite during linking flags: +cryptonite-ed25519 -build-tool +package pact-tng + ghc-options: -Wwarn + -- avoid conflict with cryptonite during linking + flags: +cryptonite-ed25519 -build-tool + package rocksdb-haskell-kadena ghc-options: -Wwarn -optc-w -optcxx-w @@ -87,6 +92,12 @@ source-repository-package tag: 1027a1f5fd0439c58522921e3a0532c4f5867a24 --sha256: 18xgvzb3p8chch85747ln9a2191df09vwwrv9v3njr2h69n3rhxj +source-repository-package + type: git + location: https://github.com/kadena-io/pact-5.git + tag: 52f41d6b48584e8fcbfae745a6e3abe640500885 + --sha256: 06karzsrih0hjkdipc8s171v4vgk09j5w6fa7zm3rv6qjw6j6jar + source-repository-package type: git location: https://github.com/kadena-io/pact-json.git @@ -163,8 +174,6 @@ allow-newer: servant:* -- These packages are tightly bound to the GHC version and these -- settings ensure that we use the versions that are shipped with the -- GHC version that we are using. -allow-newer: *:Cabal -allow-newer: *:Cabal-syntax allow-newer: *:array allow-newer: *:base allow-newer: *:bytestring @@ -204,6 +213,8 @@ allow-newer: webauthn:* -- many packages use an spurious <1.5 upper bound on hashable allow-newer: *:hashable +allow-newer: lrucaching:base-compat + -- -------------------------------------------------------------------------- -- -- Temporary Dependency Overwrites -- @@ -219,4 +230,3 @@ allow-newer: *:hashable -- -- Please add a comment for each entry that outlines why it is needed and when -- it can be removed. - diff --git a/cabal.project.freeze b/cabal.project.freeze index d599d163b0..4d89372f3f 100644 --- a/cabal.project.freeze +++ b/cabal.project.freeze @@ -2,8 +2,11 @@ active-repositories: hackage.haskell.org:merge 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, @@ -17,6 +20,7 @@ constraints: any.Cabal ==3.10.2.0, aeson +ordered-keymap, any.aeson-pretty ==0.8.10, aeson-pretty -lib-only, + any.alex ==3.5.1.0, any.ansi-terminal ==1.1.2, ansi-terminal -example, any.ansi-terminal-types ==1.1, @@ -37,11 +41,12 @@ constraints: any.Cabal ==3.10.2.0, attoparsec -developer, any.attoparsec-aeson ==2.2.2.0, any.authenticate-oauth ==1.7, - any.auto-update ==0.2.4, + any.auto-update ==0.2.5, + 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.2, + any.base-orphans ==0.9.3, any.base-unicode-symbols ==0.2.4.2, base-unicode-symbols +base-4-8 -old-base, any.base16-bytestring ==1.0.2.0, @@ -62,11 +67,11 @@ constraints: any.Cabal ==3.10.2.0, any.bound ==2.0.7, bound +template-haskell, any.bsb-http-chunked ==0.0.0.4, - any.bytebuild ==0.3.16.2, + any.bytebuild ==0.3.16.3, bytebuild -checked, any.byteorder ==1.0.4, any.bytes ==0.17.4, - any.byteslice ==0.2.13.2, + any.byteslice ==0.2.14.0, byteslice +avoid-rawmemchr, any.bytesmith ==0.3.11.1, any.bytestring ==0.12.1.0, @@ -86,16 +91,24 @@ constraints: any.Cabal ==3.10.2.0, any.chainweb-storage ==0.1.0.0, any.character-ps ==0.1, any.charset ==0.3.11, - any.chronos ==1.1.6.1, + any.chronos ==1.1.6.2, + any.citeproc ==0.8.1.2, + 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.3, 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, comonad +containers +distributive +indexed-traversable, + any.concurrent-output ==1.10.21, any.conduit ==1.3.6, + any.conduit-extra ==1.3.6, any.configuration-tools ==0.7.0, configuration-tools -remote-configs, any.constraints ==0.14.2, @@ -136,18 +149,25 @@ constraints: any.Cabal ==3.10.2.0, 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.0, 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.emojis ==0.1.4.1, any.enclosed-exceptions ==1.0.3, any.entropy ==0.4.1.10, 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, @@ -158,16 +178,20 @@ constraints: any.Cabal ==3.10.2.0, any.fingertree ==0.1.5.0, any.finite-typelits ==0.2.1.0, any.free ==5.2, - any.generic-data ==1.1.0.1, + 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.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.3, any.happy-lib ==2.1.3, @@ -175,10 +199,12 @@ constraints: any.Cabal ==3.10.2.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.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.hsc2hs ==0.68.10, hsc2hs -in-ghc-tree, @@ -194,6 +220,7 @@ constraints: any.Cabal ==3.10.2.0, 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, @@ -202,7 +229,9 @@ constraints: any.Cabal ==3.10.2.0, 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.2, @@ -211,8 +240,14 @@ constraints: any.Cabal ==3.10.2.0, 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.3.0.0, + lsp -demo, + any.lsp-types ==2.1.0.0, + lsp-types -force-ospath, any.managed ==1.0.10, any.massiv ==1.0.4.0, massiv -unsafe-checks, @@ -230,7 +265,7 @@ constraints: any.Cabal ==3.10.2.0, any.mod ==0.2.0.1, mod +semirings +vector, any.monad-control ==1.0.3.1, - any.mono-traversable ==1.0.20.0, + 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, @@ -238,6 +273,7 @@ constraints: any.Cabal ==3.10.2.0, any.mwc-random ==0.15.1.0, mwc-random -benchpapi, any.natural-arithmetic ==0.2.1.0, + any.neat-interpolation ==0.5.1.4, any.network ==3.2.7.0, network -devel, any.network-byte-order ==0.1.7, @@ -250,12 +286,18 @@ constraints: any.Cabal ==3.10.2.0, 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.1, 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.0, + pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, + any.pandoc ==3.6, + 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, @@ -268,6 +310,8 @@ constraints: any.Cabal ==3.10.2.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, @@ -277,15 +321,20 @@ constraints: any.Cabal ==3.10.2.0, any.primitive-unlifted ==2.1.0.0, any.process ==1.6.18.0, any.profunctors ==5.6.2, + any.property-matchers ==0.2.0.0, any.psqueues ==0.2.8.0, any.pvar ==1.0.0.0, any.quickcheck-instances ==0.3.32, - any.random ==1.2.1.2, + 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, reflection -slow +template-haskell, + any.regex ==1.1.0.2, any.regex-base ==0.94.0.2, + any.regex-pcre-builtin ==0.95.2.3.8.44, any.regex-tdfa ==1.3.2.2, regex-tdfa +doctest -force-o2, any.resource-pool ==0.4.0.0, @@ -295,6 +344,7 @@ constraints: any.Cabal ==3.10.2.0, any.rocksdb-haskell-kadena ==1.1.0, rocksdb-haskell-kadena -with-tbb, any.rosetta ==1.0.1, + any.row-types ==1.0.1.2, any.rts ==1.0.2, any.run-st ==0.1.3.3, any.safe ==0.3.21, @@ -322,10 +372,19 @@ constraints: any.Cabal ==3.10.2.0, any.simple-sendfile ==0.2.32, simple-sendfile +allow-bsd -fallback, any.singleton-bool ==0.1.8, + any.skylighting ==0.14.4, + skylighting -executable, + any.skylighting-core ==0.14.4, + 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.socks ==0.6.1, any.some ==1.0.6, some +newtype-unsafe, any.sop-core ==0.5.0.2, + any.sorted-list ==0.2.2.0, any.split ==0.2.5, any.splitmix ==0.1.0.5, splitmix -optimised-mixer, @@ -342,23 +401,34 @@ constraints: any.Cabal ==3.10.2.0, any.syb ==0.7.2.4, any.tagged ==0.8.9, tagged +deepseq +transformers, + any.tagsoup ==0.14.8, any.tasty ==1.5.2, 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, 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.12, + 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, 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, @@ -366,12 +436,13 @@ constraints: any.Cabal ==3.10.2.0, any.time-compat ==1.9.7, any.time-locale-compat ==0.1.1.5, time-locale-compat +old-locale, - any.time-manager ==0.2.1, + any.time-manager ==0.2.2, any.tls ==2.1.5, tls -devel, any.tls-session-manager ==0.0.7, 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, @@ -381,6 +452,16 @@ constraints: any.Cabal ==3.10.2.0, any.trifecta ==2.1.4, any.tuples ==0.1.0.0, any.typed-process ==0.2.12.0, + any.typst ==0.6.1, + 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.3, any.unix-time ==0.4.16, @@ -420,14 +501,20 @@ constraints: any.Cabal ==3.10.2.0, 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, wreq -aws -developer +doctest -httpbin, + any.xml ==1.3.14, + any.xml-conduit ==1.9.1.4, + 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 2024-12-06T21:41:31Z +index-state: hackage.haskell.org 2024-12-18T21:45:36Z diff --git a/chainweb.cabal b/chainweb.cabal index 58a440fab2..57eac648ad 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -105,6 +105,7 @@ common warning-flags -Widentities -funclutter-valid-hole-fits -fmax-relevant-binds=0 + -Wno-gadt-mono-local-binds -- This needed because -Werror and missing-home-modules causes -- problems with ghci. @@ -126,6 +127,7 @@ library cc-options: -DSQLITE_CORE exposed-modules: , Chainweb.Backup + , Chainweb.Block , Chainweb.BlockCreationTime , Chainweb.BlockHash , Chainweb.BlockHeader @@ -139,8 +141,14 @@ library , Chainweb.BlockHeader.Genesis.FastTimedCPM1to9Payload , Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload , Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload - , Chainweb.BlockHeader.Genesis.Testnet0Payload - , Chainweb.BlockHeader.Genesis.Testnet1to19Payload + , Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM0Payload + , Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM1to9Payload + , Chainweb.BlockHeader.Genesis.Pact5Development0Payload + , Chainweb.BlockHeader.Genesis.Pact5Development1to19Payload + , Chainweb.BlockHeader.Genesis.Testnet040Payload + , Chainweb.BlockHeader.Genesis.Testnet041to19Payload + , Chainweb.BlockHeader.Genesis.Testnet050Payload + , Chainweb.BlockHeader.Genesis.Testnet051to19Payload , Chainweb.BlockHeader.Genesis.Mainnet0Payload , Chainweb.BlockHeader.Genesis.Mainnet1Payload , Chainweb.BlockHeader.Genesis.Mainnet2Payload @@ -242,7 +250,8 @@ library , Chainweb.SPV.RestAPI.Client , Chainweb.Sync.WebBlockHeaderStore , Chainweb.Time - , Chainweb.Transaction + , Chainweb.Pact4.Transaction + , Chainweb.Pact5.Transaction , Chainweb.TreeDB , Chainweb.Utils , Chainweb.Utils.Paging @@ -261,9 +270,11 @@ library , Chainweb.Version.Development , Chainweb.Version.Guards , Chainweb.Version.Mainnet + , Chainweb.Version.Pact5Development , Chainweb.Version.RecapDevelopment , Chainweb.Version.Registry - , Chainweb.Version.Testnet + , Chainweb.Version.Testnet04 + , Chainweb.Version.Testnet05 , Chainweb.Version.Utils , Chainweb.WebBlockHeaderDB , Chainweb.WebPactExecutionService @@ -290,7 +301,8 @@ library , P2P.TaskQueue -- pact - , Chainweb.Pact.Backend.ChainwebPactDb + , Chainweb.Pact4.Backend.ChainwebPactDb + , Chainweb.Pact5.Backend.ChainwebPactDb , Chainweb.Pact.Backend.DbCache , Chainweb.Pact.Backend.Compaction , Chainweb.Pact.Backend.PactState @@ -301,27 +313,38 @@ library , Chainweb.Pact.Backend.PactState.GrandHash.Calc , Chainweb.Pact.Backend.PactState.GrandHash.Import , Chainweb.Pact.Backend.PactState.GrandHash.Utils - , Chainweb.Pact.Backend.RelationalCheckpointer , Chainweb.Pact.Backend.SQLite.DirectV2 , Chainweb.Pact.Backend.SQLite.V2 , Chainweb.Pact.Backend.Types , Chainweb.Pact.Backend.Utils - , Chainweb.Pact.NoCoinbase + , Chainweb.Pact.Conversion , Chainweb.Pact.PactService , Chainweb.Pact.PactService.Checkpointer - , Chainweb.Pact.PactService.ExecBlock + , Chainweb.Pact.PactService.Checkpointer.Internal + , Chainweb.Pact.PactService.Pact4.ExecBlock + , Chainweb.Pact.PactService.Pact5.ExecBlock , Chainweb.Pact.RestAPI , Chainweb.Pact.RestAPI.Client , Chainweb.Pact.RestAPI.EthSpv , Chainweb.Pact.RestAPI.SPV , Chainweb.Pact.RestAPI.Server - , Chainweb.Pact.SPV + , Chainweb.Pact4.SPV + , Chainweb.Pact5.SPV , Chainweb.Pact.Service.BlockValidation , Chainweb.Pact.Service.PactInProcApi , Chainweb.Pact.Service.PactQueue - , Chainweb.Pact.Service.Types - , Chainweb.Pact.Templates - , Chainweb.Pact.TransactionExec + , 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 @@ -341,7 +364,6 @@ library , Chainweb.Pact.Transactions.RecapDevelopmentTransactions , Chainweb.Pact.Types , Chainweb.Pact.Utils - , Chainweb.Pact.Validations -- utils , Utils.Logging @@ -351,7 +373,6 @@ library build-depends: , Decimal >= 0.4.2 , aeson >= 2.2 - , aeson-pretty >= 0.8 , asn1-encoding >=0.9 , asn1-types >=0.3 , async >= 2.2 @@ -364,16 +385,16 @@ library , case-insensitive >= 1.2 , cassava >= 0.5.1 , chainweb-storage >= 0.1 + , chronos >= 1.1 , clock >= 0.7 , configuration-tools >= 0.6 , containers >= 0.5 , crypton >= 0.31 - , crypton-connection >= 0.4 + , crypton-connection >= 0.4.2 , crypton-x509 >=1.7 , crypton-x509-system >=1.6 , crypton-x509-validation >=1.6 , cuckoo >= 0.3 - , data-default-class >=0.1.2 , data-dword >= 0.3 , deepseq >= 1.4 , digraph >= 0.2.3 @@ -413,6 +434,8 @@ library , pact-time:numeric >=0.3.0.1 , parallel >= 3.2.2.0 , patience >= 0.3 + , pact-tng + , pact-tng:pact-request-api , pem >=0.2 , primitive >= 0.7.1.0 , random >= 1.2 @@ -432,7 +455,7 @@ library , text >= 2.0 , these >= 1.0 , time >= 1.12.2 - , tls >=1.9 + , tls >=2.1.4 , tls-session-manager >= 0.0 , token-bucket >= 0.1 , transformers >= 0.5 @@ -477,12 +500,14 @@ library chainweb-test-utils Chainweb.Test.HostAddress Chainweb.Test.MultiNode Chainweb.Test.P2P.Peer.BootstrapConfig - Chainweb.Test.Pact.Utils - Chainweb.Test.Pact.VerifierPluginTest.Transaction - Chainweb.Test.Pact.VerifierPluginTest.Transaction.Message.After225 - Chainweb.Test.Pact.VerifierPluginTest.Transaction.Message.Before225 - Chainweb.Test.Pact.VerifierPluginTest.Transaction.Utils - Chainweb.Test.Pact.VerifierPluginTest.Unit + 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.RestAPI.Client_ Chainweb.Test.RestAPI.Utils Chainweb.Test.TestVersions @@ -515,9 +540,9 @@ library chainweb-test-utils , case-insensitive >= 1.2 , chainweb-storage >= 0.1 , chronos >= 1.1 - , crypton-connection >=0.4 , containers >= 0.5 , crypton >= 0.31 + , crypton-connection >=0.4 , data-dword >= 0.3 , deepseq >= 1.4 , direct-sqlite >= 2.3.27 @@ -536,12 +561,15 @@ library chainweb-test-utils , pact , pact-json >= 0.1 , pact-time:numeric >=0.3.0.1 + , pact-tng >=5.0 + , pact-tng:pact-request-api >=5.0 , quickcheck-instances >= 0.3 , random >= 1.2 , resourcet >= 1.3 , retry >= 0.7 , rocksdb-haskell-kadena >= 1.1.0 , rosetta >= 1.0 + , safe-exceptions >= 0.1 , servant >= 0.20.1 , servant-client >= 0.18.2 , servant-client-core >= 0.20 @@ -554,6 +582,7 @@ library chainweb-test-utils , tasty-quickcheck >= 0.9 , temporary >= 1.3 , text >=2.0 + , tls >=2.1.4 , unliftio >= 0.2.25 , unordered-containers >= 0.2.20 , vector >= 0.12.2 @@ -598,6 +627,7 @@ test-suite chainweb-tests Chainweb.Test.BlockHeader.Validation Chainweb.Test.BlockHeaderDB Chainweb.Test.BlockHeaderDB.PruneForks + Chainweb.Test.Chainweb.Utils.Paging Chainweb.Test.CutDB Chainweb.Test.Difficulty Chainweb.Test.Mempool @@ -607,22 +637,28 @@ test-suite chainweb-tests Chainweb.Test.Mempool.Sync Chainweb.Test.Mining Chainweb.Test.Misc - Chainweb.Test.Pact.Checkpointer - Chainweb.Test.Pact.DbCacheTest - Chainweb.Test.Pact.GrandHash - Chainweb.Test.Pact.ModuleCacheOnRestart - Chainweb.Test.Pact.NoCoinbase - Chainweb.Test.Pact.PactExec - Chainweb.Test.Pact.PactMultiChainTest - Chainweb.Test.Pact.PactReplay - Chainweb.Test.Pact.PactSingleChainTest - Chainweb.Test.Pact.RemotePactTest - Chainweb.Test.Pact.RewardsTest - Chainweb.Test.Pact.SPV - Chainweb.Test.Pact.SQLite - Chainweb.Test.Pact.TTL - Chainweb.Test.Pact.TransactionTests - Chainweb.Test.Pact.VerifierPluginTest + 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.PactServiceTest + Chainweb.Test.Pact5.RemotePactTest + Chainweb.Test.Pact5.SPVTest + Chainweb.Test.Pact5.TransactionExecTest Chainweb.Test.RestAPI Chainweb.Test.Rosetta Chainweb.Test.Rosetta.RestAPI @@ -633,7 +669,6 @@ test-suite chainweb-tests Chainweb.Test.TreeDB Chainweb.Test.TreeDB.RemoteDB Chainweb.Test.Version - Chainweb.Test.Chainweb.Utils.Paging -- Data Data.Test.PQueue @@ -658,11 +693,11 @@ test-suite chainweb-tests , base64-bytestring-kadena == 0.1 , byteslice >= 0.2.12 , bytesmith >= 0.3.10 - -- due to change in error message that is inlined in pact , bytestring >= 0.10.12 , chainweb-storage >= 0.1 , containers >= 0.5 , crypton >= 0.31 + , crypton-connection >=0.4 , data-dword >= 0.3 , data-ordlist >= 0.4.7 , deepseq >= 1.4 @@ -672,7 +707,9 @@ test-suite chainweb-tests , ghc-compact >= 0.1 , hashable >= 1.3 , hashes >=0.2.2.0 + , hedgehog >= 1.4 , http-client >= 0.5 + , http-client-tls >=0.3 , http-types >= 0.12 , lens >= 4.17 , lens-aeson >= 1.2.2 @@ -680,15 +717,22 @@ test-suite chainweb-tests , memory >=0.14 , merkle-log >=0.2 , 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 , patience >= 0.3 + , property-matchers ^>= 0.2 + , pretty-show , quickcheck-instances >= 0.3 , random >= 1.2 , resource-pool >= 0.4 , resourcet >= 1.3 , rosetta >= 1.0 + , safe-exceptions >= 0.1 , scheduler >= 1.4 , servant-client >= 0.18.2 , sha-validation >=0.1 @@ -697,17 +741,20 @@ test-suite chainweb-tests , streaming >= 0.2.2 , strict-concurrency >= 0.2 , tasty >= 1.0 + , tasty-hedgehog >= 1.4.0.2 , tasty-hunit >= 0.9 , tasty-json >= 0.1 , tasty-quickcheck >= 0.9 , text >=2.0 , time >= 1.12.2 + , tls >=2.1.4 , transformers >= 0.5 , unordered-containers >= 0.2.20 , vector >= 0.12.2 , wai >= 3.2 + , warp >= 3.3.6 + , warp-tls >= 3.4 , yaml >= 0.11 - if flag(ed25519) cpp-options: -DWITH_ED25519=1 diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index 278f82671d..f78ac423af 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -175,6 +175,8 @@ executable ea , chainweb , chainweb:chainweb-test-utils + , aeson + , async , base , chainweb-storage , lens diff --git a/cwtools/db-checksum/Main.hs b/cwtools/db-checksum/Main.hs index c4b83416c5..ea69397fdd 100644 --- a/cwtools/db-checksum/Main.hs +++ b/cwtools/db-checksum/Main.hs @@ -14,9 +14,10 @@ module Main (main) where import Chainweb.BlockHeight + import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Backend.Utils hiding (callDb) -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Backend.Utils +import Chainweb.Pact.Types import Chainweb.Utils hiding (check) import Configuration.Utils hiding (Error, Lens', action, encode) import Control.Exception.Safe (tryAny) @@ -183,8 +184,8 @@ work args = withSQLiteConnection (_sqliteFile args) chainwebPragmas (runReaderT "[SYS:Pacts]" -> "SELECT * FROM [SYS:Pacts] \ \WHERE txid > ? AND txid <= ? \ \ORDER BY txid DESC, rowkey ASC, rowdata ASC;" - tbl -> "SELECT * FROM [" - <> Utf8 tbl + tabl -> "SELECT * FROM [" + <> Utf8 tabl <> "] WHERE txid > ? AND txid <= ? ORDER BY txid DESC, rowkey ASC, rowdata ASC;" callDb :: T.Text -> (Database -> IO a) -> ReaderT SQLiteEnv IO a @@ -216,12 +217,12 @@ getUserTables low high = callDb "getUserTables" $ \db -> do >>= eInternalError when (HashSet.null r) $ internalError errMsg - let res = getFirst $ foldMap (\tbl -> First $ if HashSet.member tbl r then Nothing else Just tbl) tbls + let res = getFirst $ foldMap (\tabl -> First $ if HashSet.member tabl r then Nothing else Just tabl) tbls maybe (return ()) (internalError . tableErrMsg . T.decodeUtf8) res where errMsg = "Somehow there are no tables in this connection. This should be an impossible case." alltables = "SELECT name FROM sqlite_master WHERE type='table';" - tableErrMsg tbl = "This table " <> tbl <> " is listed in VersionedTableCreation but is not actually in the database." + tableErrMsg tabl = "This table " <> tabl <> " is listed in VersionedTableCreation but is not actually in the database." data Args = Args { _sqliteFile :: FilePath diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index 1271fd71fd..39f7ba3385 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -3,6 +3,8 @@ {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeOperators #-} -- | -- Module: Ea @@ -33,24 +35,26 @@ import Chainweb.Pact.Backend.Utils import Chainweb.Pact.PactService import Chainweb.Pact.Types (testPactServiceConfig) import Chainweb.Pact.Utils (toTxCreationTime) -import Chainweb.Pact.Validations (defaultMaxTTL) +import Chainweb.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact4.Validations (defaultMaxTTL) import Chainweb.Payload import Chainweb.Payload.PayloadStore.InMemory import Chainweb.Storage.Table.RocksDB import Chainweb.Time -import Chainweb.Transaction (ChainwebTransaction, chainwebPayloadCodec, mkPayloadWithTextOld) import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Development (pattern Development) +import Chainweb.Version.Pact5Development (pattern Pact5Development) import Chainweb.Version.RecapDevelopment (pattern RecapDevelopment) import Chainweb.Version.Registry (registerVersion) +import Control.Concurrent.Async import Control.Exception import Control.Lens +import Data.Aeson qualified as Aeson import Data.Foldable import Data.Functor import Data.Text (Text) import Data.Text qualified as T -import Data.Text.Encoding qualified as TE import Data.Text.IO qualified as TIO import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Builder qualified as TB @@ -61,26 +65,34 @@ 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 System.LogLevel (LogLevel(..)) import Text.Printf +--- + main :: IO () main = do registerVersion RecapDevelopment registerVersion Development - - recapDevnet - devnet - fastnet - instantnet - testnet - mainnet - genTxModules - genCoinV3Payloads - genCoinV4Payloads - genCoinV5Payloads - genCoinV6Payloads + registerVersion Pact5Development + + mapConcurrently_ id + [ recapDevnet + , devnet + , pact5Devnet + , fastnet + , instantnet + , pact5Instantnet + , testnet04 + , testnet05 + , mainnet + , genTxModules + , genCoinV3Payloads + , genCoinV4Payloads + , genCoinV5Payloads + , genCoinV6Payloads + ] putStrLn "Done." where recapDevnet = mkPayloads @@ -92,9 +104,15 @@ main = do [ fastDevelopment0 , fastDevelopmentN ] + pact5Devnet = mkPayloads + [ pact5Development0 + , pact5DevelopmentN + ] fastnet = mkPayloads [fastTimedCPM0, fastTimedCPMN] instantnet = mkPayloads [instantCPM0, instantCPMN] - testnet = mkPayloads [testnet0, testnetN] + pact5Instantnet = mkPayloads [pact5InstantCPM0, pact5InstantCPMN] + testnet04 = mkPayloads [testnet040, testnet04N] + testnet05 = mkPayloads [testnet050, testnet05N] mainnet = mkPayloads [ mainnet0 , mainnet1 @@ -165,7 +183,7 @@ genCoinV6Payloads = genTxModule "CoinV6" -- Payload Generation --------------------- -genPayloadModule :: ChainwebVersion -> Text -> ChainId -> [ChainwebTransaction] -> IO Text +genPayloadModule :: ChainwebVersion -> Text -> ChainId -> [Pact4.UnparsedTransaction] -> IO Text genPayloadModule v tag cid cwTxs = withTempRocksDb "chainweb-ea" $ \rocks -> withBlockHeaderDb rocks v cid $ \bhdb -> do @@ -177,24 +195,19 @@ genPayloadModule v tag cid cwTxs = execNewGenesisBlock noMiner (V.fromList cwTxs) return $ TL.toStrict $ TB.toLazyText $ payloadModuleCode tag payloadWO -mkChainwebTxs :: [FilePath] -> IO [ChainwebTransaction] +mkChainwebTxs :: [FilePath] -> IO [Pact4.UnparsedTransaction] mkChainwebTxs txFiles = mkChainwebTxs' =<< traverse mkTx txFiles -mkChainwebTxs' :: [Command Text] -> IO [ChainwebTransaction] +mkChainwebTxs' :: [Command Text] -> IO [Pact4.UnparsedTransaction] mkChainwebTxs' rawTxs = forM rawTxs $ \cmd -> do - let cmdBS = fmap TE.encodeUtf8 cmd - -- TODO: Use the new `assertCommand` function. - -- We want to delete `verifyCommand` at some point. - -- It's not critical for Ea because WebAuthn signatures (which - -- the new `assertCommand` knows how to handle) are not present - -- in Genesis blocks. - procCmd = verifyCommand cmdBS - case procCmd of - f@ProcFail{} -> fail (show f) - ProcSucc c -> do - let t = toTxCreationTime (Time (TimeSpan 0)) - return $! mkPayloadWithTextOld <$> (c & setTxTime t & setTTL defaultMaxTTL) + 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) @@ -300,7 +313,7 @@ genTxModule tag txFiles = do let encTxs = map quoteTx cwTxs quoteTx tx = " \"" <> encTx tx <> "\"" - encTx = encodeB64UrlNoPaddingText . codecEncode (chainwebPayloadCodec maxBound) + encTx = encodeB64UrlNoPaddingText . codecEncode Pact4.rawCommandCodec modl = T.unlines $ startTxModule tag <> [T.intercalate "\n ,\n" encTxs] <> endTxModule fileName = "src/Chainweb/Pact/Transactions/" <> tag <> "Transactions.hs" @@ -317,13 +330,13 @@ startTxModule tag = , "import Data.Bifunctor (first)" , "import System.IO.Unsafe" , "" - , "import Chainweb.Transaction" + , "import qualified Chainweb.Pact4.Transaction as Pact4" , "import Chainweb.Utils" , "" - , "transactions :: [ChainwebTransaction]" + , "transactions :: [Pact4.Transaction]" , "transactions =" , " let decodeTx t =" - , " fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText t" + , " fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t" , " in unsafePerformIO $ mapM decodeTx [" ] diff --git a/cwtools/ea/Ea/Genesis.hs b/cwtools/ea/Ea/Genesis.hs index 9dc11320c7..2ee0482004 100644 --- a/cwtools/ea/Ea/Genesis.hs +++ b/cwtools/ea/Ea/Genesis.hs @@ -18,16 +18,22 @@ module Ea.Genesis , recapDevelopmentKAD , fastDevelopment0 , fastDevelopmentN +, pact5Development0 +, pact5DevelopmentN -- * Testing Genesis Txs , fastTimedCPM0 , fastTimedCPMN , instantCPM0 , instantCPMN +, pact5InstantCPM0 +, pact5InstantCPMN -- * Testnet Genesis txs -, testnet0 -, testnetN +, testnet040 +, testnet04N +, testnet050 +, testnet05N -- * Mainnet Genesis txs , mainnet0 @@ -67,9 +73,11 @@ import Chainweb.Graph import Chainweb.Test.TestVersions import Chainweb.Version import Chainweb.Version.Development +import Chainweb.Version.Pact5Development import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet -import Chainweb.Version.Testnet +import Chainweb.Version.Testnet04 +import Chainweb.Version.Testnet05 -- ---------------------------------------------------------------------- -- @@ -207,6 +215,23 @@ fastDevelopmentN = fastDevelopment0 & txChainIds .~ mkChainIdRange 1 19 & coinbase ?~ devNGrants +pact5Development0 :: Genesis +pact5Development0 = Genesis + { _version = Pact5Development + , _tag = "Pact5Development" + , _txChainIds = onlyChainId 0 + , _coinbase = Just dev0Grants + , _keysets = Just devKeysets + , _allocations = Just devAllocations + , _namespaces = Just devNs2 + , _coinContract = [fungibleAssetV1, fungibleXChainV1, fungibleAssetV2, installCoinContractV6, gasPayer] + } + +pact5DevelopmentN :: Genesis +pact5DevelopmentN = pact5Development0 + & txChainIds .~ mkChainIdRange 1 19 + & coinbase ?~ devNGrants + devNs2 :: FilePath devNs2 = "pact/genesis/ns-v2.yaml" @@ -248,6 +273,23 @@ 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 + fastTimedCPM0 :: Genesis fastTimedCPM0 = Genesis { _version = fastForkingCpmTestVersion petersonChainGraph @@ -281,12 +323,12 @@ fastAllocations :: FilePath fastAllocations = "pact/genesis/devnet/allocations.yaml" -- ---------------------------------------------------------------------- -- --- Testnet +-- Testnet 04 -testnet0 :: Genesis -testnet0 = Genesis +testnet040 :: Genesis +testnet040 = Genesis { _version = Testnet04 - , _tag = "Testnet" + , _tag = "Testnet04" , _txChainIds = onlyChainId 0 , _coinbase = Just test0Grants , _keysets = Just testnetKeysets @@ -295,25 +337,51 @@ testnet0 = Genesis , _coinContract = [fungibleAssetV1, coinContractV1, gasPayer] } -testnetN :: Genesis -testnetN = testnet0 +testnet04N :: Genesis +testnet04N = testnet040 & txChainIds .~ mkChainIdRange 1 19 & coinbase ?~ testNGrants test0Grants :: FilePath -test0Grants = "pact/genesis/testnet/grants0.yaml" +test0Grants = "pact/genesis/testnet04/grants0.yaml" testNGrants :: FilePath -testNGrants = "pact/genesis/testnet/grantsN.yaml" +testNGrants = "pact/genesis/testnet04/grantsN.yaml" testNs :: FilePath testNs = "pact/genesis/ns-v1.yaml" testnetAllocations :: FilePath -testnetAllocations = "pact/genesis/testnet/allocations.yaml" +testnetAllocations = "pact/genesis/testnet04/allocations.yaml" testnetKeysets :: FilePath -testnetKeysets = "pact/genesis/testnet/keysets.yaml" +testnetKeysets = "pact/genesis/testnet04/keysets.yaml" + +-- ---------------------------------------------------------------------- -- +-- Testnet 05 + +testnet050 :: Genesis +testnet050 = Genesis + { _version = Testnet05 + , _tag = "Testnet05" + , _txChainIds = onlyChainId 0 + , _coinbase = Just testnet05Chain0Grants + , _keysets = Just testnetKeysets + , _allocations = Just testnetAllocations + , _namespaces = Just "pact/genesis/ns-v2.yaml" + , _coinContract = [fungibleAssetV1, fungibleXChainV1, fungibleAssetV2, installCoinContractV6, gasPayer] + } + +testnet05N :: Genesis +testnet05N = testnet050 + & txChainIds .~ mkChainIdRange 1 19 + & coinbase ?~ testnet05ChainNGrants + +testnet05Chain0Grants :: FilePath +testnet05Chain0Grants = "pact/genesis/testnet05/grants0.yaml" + +testnet05ChainNGrants :: FilePath +testnet05ChainNGrants = "pact/genesis/testnet05/grantsN.yaml" -- ---------------------------------------------------------------------- -- -- Mainnet diff --git a/flake.lock b/flake.lock index 5a719104cd..fb9488dbd1 100644 --- a/flake.lock +++ b/flake.lock @@ -69,11 +69,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1687709756, - "narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -85,11 +85,11 @@ "hackage": { "flake": false, "locked": { - "lastModified": 1730680170, - "narHash": "sha256-CUPGIJ4PMrGKVC30bZfCrlzvTvlAjvz2bQ091DKqNNk=", + "lastModified": 1734395427, + "narHash": "sha256-UlJvZ28OJlxMPWul1fBnAKQNi2FB9THzwXGaZk05Igg=", "owner": "input-output-hk", "repo": "hackage.nix", - "rev": "3f9db9843a52f45de2e9884838de31fe7c526c75", + "rev": "c156286449af88190a9c1af6c0bf05d452713eae", "type": "github" }, "original": { @@ -125,14 +125,6 @@ "hs-nix-infra", "empty" ], - "ghc910X": [ - "hs-nix-infra", - "empty" - ], - "ghc911": [ - "hs-nix-infra", - "empty" - ], "hackage": [ "hs-nix-infra", "hackage" @@ -164,6 +156,7 @@ "hls-2.6": "hls-2.6", "hls-2.7": "hls-2.7", "hls-2.8": "hls-2.8", + "hls-2.9": "hls-2.9", "hpc-coveralls": [ "hs-nix-infra", "empty" @@ -209,6 +202,7 @@ "hs-nix-infra", "empty" ], + "nixpkgs-2405": "nixpkgs-2405", "nixpkgs-unstable": "nixpkgs-unstable", "old-ghc-nix": [ "hs-nix-infra", @@ -220,11 +214,11 @@ ] }, "locked": { - "lastModified": 1716252607, - "narHash": "sha256-QsljiA7KBPFviyUJRil++p/JxLvp7tF9+L0mBtS5fnU=", + "lastModified": 1721091024, + "narHash": "sha256-3C4mAnpaBV9EgBgJw+l8YJWBp6tmfgrYMD8yReru+9E=", "owner": "input-output-hk", "repo": "haskell.nix", - "rev": "88f20f0876efc11eff32fffc1b9721e6bee3868f", + "rev": "2c99ded7de739e6e9795577c7c845317c9ad331e", "type": "github" }, "original": { @@ -284,6 +278,23 @@ "type": "github" } }, + "hls-2.9": { + "flake": false, + "locked": { + "lastModified": 1718469202, + "narHash": "sha256-THXSz+iwB1yQQsr/PY151+2GvtoJnTIB2pIQ4OzfjD4=", + "owner": "haskell", + "repo": "haskell-language-server", + "rev": "40891bccb235ebacce020b598b083eab9dda80f1", + "type": "github" + }, + "original": { + "owner": "haskell", + "ref": "2.9.0.0", + "repo": "haskell-language-server", + "type": "github" + } + }, "hs-nix-infra": { "inputs": { "empty": "empty_2", @@ -296,11 +307,11 @@ "nixpkgs-rec": "nixpkgs-rec" }, "locked": { - "lastModified": 1717427841, - "narHash": "sha256-8LszV//rTjrOL5hEn47TWdrw2k5F01SII0HM2Nrh9rM=", + "lastModified": 1721155341, + "narHash": "sha256-TdLe5O6ath6lgsgVYyfZHUwoZm7R4AcCZxa63SOg86I=", "owner": "kadena-io", "repo": "hs-nix-infra", - "rev": "a2d4cbbb73d82ea462005013006112e09bbd802f", + "rev": "b20a03107b870cdff4c16486ada0d674c89f3242", "type": "github" }, "original": { @@ -311,11 +322,11 @@ }, "nix-filter": { "locked": { - "lastModified": 1687178632, - "narHash": "sha256-HS7YR5erss0JCaUijPeyg2XrisEb959FIct3n2TMGbE=", + "lastModified": 1710156097, + "narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=", "owner": "numtide", "repo": "nix-filter", - "rev": "d90c75e8319d0dd9be67d933d8eb9d0894ec9174", + "rev": "3342559a24e85fc164b295c3444e8a139924675b", "type": "github" }, "original": { @@ -340,6 +351,22 @@ "type": "github" } }, + "nixpkgs-2405": { + "locked": { + "lastModified": 1720122915, + "narHash": "sha256-Nby8WWxj0elBu1xuRaUcRjPi/rU3xVbkAt2kj4QwX2U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "835cf2d3f37989c5db6585a28de967a667a75fb1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-24.05-darwin", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-rec": { "locked": { "lastModified": 1669833724, @@ -358,17 +385,17 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1694822471, - "narHash": "sha256-6fSDCj++lZVMZlyqOe9SIOL8tYSBz1bI8acwovRwoX8=", + "lastModified": 1720181791, + "narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "47585496bcb13fb72e4a90daeea2f434e2501998", + "rev": "4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", - "rev": "47585496bcb13fb72e4a90daeea2f434e2501998", "type": "github" } }, diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 2cca19a610..6ded0f6f06 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -95,7 +95,7 @@ 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(TxFailureLog) +import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time @@ -104,7 +104,7 @@ import Chainweb.Utils import Chainweb.Utils.RequestLog import Chainweb.Version import Chainweb.Version.Mainnet -import Chainweb.Version.Testnet (testnet) +import Chainweb.Version.Testnet04 (testnet04) import Chainweb.Version.Registry import Chainweb.Storage.Table.RocksDB @@ -384,8 +384,8 @@ 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 TxFailureLog) - else passthroughLogHandler + then [dropLogHandler (Proxy :: Proxy Pact4TxFailureLog), dropLogHandler (Proxy :: Proxy Pact5TxFailureLog)] + else [] -- Telemetry Backends monitorBackend <- managed @@ -430,28 +430,31 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do logger <- managed $ L.withLogger (_logConfigLogger logCfg) $ logHandles - [ logFilterHandle (_logConfigFilter logCfg) - , txFailureHandler - , logHandler monitorBackend - , logHandler p2pInfoBackend - , logHandler rtsBackend - , logHandler counterBackend - , logHandler endpointBackend - , logHandler newBlockBackend - , logHandler orphanedBlockBackend - , logHandler miningStatsBackend - , logHandler requestLogBackend - , logHandler queueStatsBackend - , logHandler reintroBackend - , logHandler traceBackend - , logHandler mempoolStatsBackend - , logHandler blockUpdateBackend - , logHandler dbCacheBackend - , logHandler dbStatsBackend - , logHandler pactQueueStatsBackend - , logHandler p2pNodeStatsBackend - , logHandler topLevelStatusBackend - ] baseBackend + (concat + [ [ logFilterHandle (_logConfigFilter logCfg) ] + , txFailureHandler + , + [ logHandler monitorBackend + , logHandler p2pInfoBackend + , logHandler rtsBackend + , logHandler counterBackend + , logHandler endpointBackend + , logHandler newBlockBackend + , logHandler orphanedBlockBackend + , logHandler miningStatsBackend + , logHandler requestLogBackend + , logHandler queueStatsBackend + , logHandler reintroBackend + , logHandler traceBackend + , logHandler mempoolStatsBackend + , logHandler blockUpdateBackend + , logHandler dbCacheBackend + , logHandler dbStatsBackend + , logHandler pactQueueStatsBackend + , logHandler p2pNodeStatsBackend + , logHandler topLevelStatusBackend + ] + ]) baseBackend liftIO $ f $ maybe id (\x -> addLabel ("cluster", toText x)) (_logConfigClusterId logCfg) @@ -492,7 +495,7 @@ withServiceDate v lf msd inner = case msd of Nothing -> do inner Just sd -> do - if _versionCode v == _versionCode mainnet || _versionCode v == _versionCode testnet + if _versionCode v == _versionCode mainnet || _versionCode v == _versionCode testnet04 then do race (timer sd) inner >>= \case Left () -> error "Service date thread terminated unexpectedly" diff --git a/pact/coin-contract/v5/install-coin-contract-v5.yaml b/pact/coin-contract/v5/install-coin-contract-v5.yaml deleted file mode 100644 index 12b26ac2b1..0000000000 --- a/pact/coin-contract/v5/install-coin-contract-v5.yaml +++ /dev/null @@ -1,3 +0,0 @@ -codeFile: coin-v5-install.pact -nonce: coin-contract-v5-install -keyPairs: [] diff --git a/pact/genesis/mainnet/kad-ops-grants.yaml b/pact/genesis/mainnet/kad-ops-grants.yaml index 4d78e44219..6dd431948a 100644 --- a/pact/genesis/mainnet/kad-ops-grants.yaml +++ b/pact/genesis/mainnet/kad-ops-grants.yaml @@ -8,4 +8,4 @@ data: - "9a4849687cbcfeb1f7c6510539638da576289508aedcc75f4d6ad3ed2623635c" pred: "keys-any" nonce: mainnet-grants-kadops -keyPairs: [] \ No newline at end of file +keyPairs: [] diff --git a/pact/genesis/testnet/allocations.yaml b/pact/genesis/testnet04/allocations.yaml similarity index 100% rename from pact/genesis/testnet/allocations.yaml rename to pact/genesis/testnet04/allocations.yaml diff --git a/pact/genesis/testnet/grants0.yaml b/pact/genesis/testnet04/grants0.yaml similarity index 100% rename from pact/genesis/testnet/grants0.yaml rename to pact/genesis/testnet04/grants0.yaml diff --git a/pact/genesis/testnet/grantsN.yaml b/pact/genesis/testnet04/grantsN.yaml similarity index 100% rename from pact/genesis/testnet/grantsN.yaml rename to pact/genesis/testnet04/grantsN.yaml diff --git a/pact/genesis/testnet/keysets.yaml b/pact/genesis/testnet04/keysets.yaml similarity index 100% rename from pact/genesis/testnet/keysets.yaml rename to pact/genesis/testnet04/keysets.yaml diff --git a/pact/genesis/testnet05/grants0.yaml b/pact/genesis/testnet05/grants0.yaml new file mode 100644 index 0000000000..80999dbaa4 --- /dev/null +++ b/pact/genesis/testnet05/grants0.yaml @@ -0,0 +1,45 @@ +# Internal testnet accounts +code: |- + (coin.coinbase "anastasia" (read-keyset "anastasia") 10000000.0) + (coin.coinbase "emily" (read-keyset "emily") 10000000.0) + (coin.coinbase "heekyun" (read-keyset "heekyun") 10000000.0) + (coin.coinbase "jeff" (read-keyset "jeff") 10000000.0) + (coin.coinbase "leah" (read-keyset "leah") 10000000.0) + (coin.coinbase "linda" (read-keyset "linda") 10000000.0) + (coin.coinbase "stuart" (read-keyset "stuart") 1000000000.0) + (coin.coinbase "will" (read-keyset "will") 1000000000.0) + (coin.coinbase "Kadena" (keyset-ref-guard "Kadena") 1000000000.0) + (coin.coinbase "k:4a7c20d087df76753e28d765eb84e1338479dd6df121ddda3e59576d0a32a5bf" (read-keyset "4a7c20d087df76753e28d765eb84e1338479dd6df121ddda3e59576d0a32a5bf") 10000000.0) + (coin.coinbase "k:fab92c47ccb1fdd25173e2981ebb29de989acea88792e34cc87298f6a1578842" (read-keyset "fab92c47ccb1fdd25173e2981ebb29de989acea88792e34cc87298f6a1578842") 10000000.0) + (coin.coinbase "k:cbb94b12f55bd540a5aa507635111938f2282c2891b78c13e462fa018c4145d8" (read-keyset "cbb94b12f55bd540a5aa507635111938f2282c2891b78c13e462fa018c4145d8") 10000000.0) + (coin.coinbase "k:89f0a840e3bab4c32e07b14862f0a0f414543da036ecf339d3ed7ec93fa4a41b" (read-keyset "89f0a840e3bab4c32e07b14862f0a0f414543da036ecf339d3ed7ec93fa4a41b") 10000000.0) + (coin.coinbase "k:4c73566b5d8fa4cd2a2620f7b2bc57284c7fc5c48f8f0db2bf1675cad08dc4af" (read-keyset "4c73566b5d8fa4cd2a2620f7b2bc57284c7fc5c48f8f0db2bf1675cad08dc4af") 10000000.0) + (coin.coinbase "k:1c835d4e67917fd25781b11db1c12efbc4296c5c7fe981d35bbcf4a46a53441f" (read-keyset "1c835d4e67917fd25781b11db1c12efbc4296c5c7fe981d35bbcf4a46a53441f") 10000000.0) + (coin.coinbase "k:554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94" (read-keyset "554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94") 10000000.0) + (coin.coinbase "k:3e83213bedf6be0aaf8ed3f8e72ffb9a240930b8698b9646e8d913b61c93e4ab" (read-keyset "3e83213bedf6be0aaf8ed3f8e72ffb9a240930b8698b9646e8d913b61c93e4ab") 10000000.0) + (coin.coinbase "k:5e4a8d51fa3958026cddd93bbd874e1bdae149a12ac6f0152ea253942efc989c" (read-keyset "5e4a8d51fa3958026cddd93bbd874e1bdae149a12ac6f0152ea253942efc989c") 10000000.0) + (coin.coinbase "k:154d6f239864c19c43ef377c03cc8df8e0d1e792a143a01423503028ce963afa" (read-keyset "154d6f239864c19c43ef377c03cc8df8e0d1e792a143a01423503028ce963afa") 10000000.0) + (coin.coinbase "k:49b41034ab9ba0b7fd92fd53eb79d7fd3d601191a15269682830f9c92e3688ad" (read-keyset "49b41034ab9ba0b7fd92fd53eb79d7fd3d601191a15269682830f9c92e3688ad") 10000000.0) + +data: + anastasia: ["1ec74de724b3c700737d99906aba48dbed6b90140bbc64133c558571b18883c3"] + emily: ["368059ab25bec92f19a4f8ce172030e5d672e4879aaae8d5be8b705dd3dfae7f"] + heekyun: ["dfb16b13e4032a6878fd98506b22cb0d6e5932c541e656b7ee5d69d72e6eb76e"] + jeff: ["c53217bb053b827b5fbbb322fced67a86298ec40a59aeef15b2190a05ad0a6bd"] + leah: ["216643137fb5f6a127dbb1063702cbb7c1f153451a1ad4afa9f92e77447cc330"] + linda: ["e1002db0894299bb37238bab02170180c0ae56f5932169bb2fcf756059fea175"] + stuart: ["0e3180dd4ae9d4385a21d7ca95cc7229b0f6b2ca9e0f4861dd4eed685004df66"] + will: ["830ad773510ab0f5a0d4564b7b31d2b0b1ba7fd5a116f66598a1c63cc2142fd5"] + "4a7c20d087df76753e28d765eb84e1338479dd6df121ddda3e59576d0a32a5bf": ["4a7c20d087df76753e28d765eb84e1338479dd6df121ddda3e59576d0a32a5bf"] + "fab92c47ccb1fdd25173e2981ebb29de989acea88792e34cc87298f6a1578842": ["fab92c47ccb1fdd25173e2981ebb29de989acea88792e34cc87298f6a1578842"] + "cbb94b12f55bd540a5aa507635111938f2282c2891b78c13e462fa018c4145d8": ["cbb94b12f55bd540a5aa507635111938f2282c2891b78c13e462fa018c4145d8"] + "89f0a840e3bab4c32e07b14862f0a0f414543da036ecf339d3ed7ec93fa4a41b": ["89f0a840e3bab4c32e07b14862f0a0f414543da036ecf339d3ed7ec93fa4a41b"] + "4c73566b5d8fa4cd2a2620f7b2bc57284c7fc5c48f8f0db2bf1675cad08dc4af": ["4c73566b5d8fa4cd2a2620f7b2bc57284c7fc5c48f8f0db2bf1675cad08dc4af"] + "1c835d4e67917fd25781b11db1c12efbc4296c5c7fe981d35bbcf4a46a53441f": ["1c835d4e67917fd25781b11db1c12efbc4296c5c7fe981d35bbcf4a46a53441f"] + "554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94": ["554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94"] + "3e83213bedf6be0aaf8ed3f8e72ffb9a240930b8698b9646e8d913b61c93e4ab": ["3e83213bedf6be0aaf8ed3f8e72ffb9a240930b8698b9646e8d913b61c93e4ab"] + "5e4a8d51fa3958026cddd93bbd874e1bdae149a12ac6f0152ea253942efc989c": ["5e4a8d51fa3958026cddd93bbd874e1bdae149a12ac6f0152ea253942efc989c"] + "154d6f239864c19c43ef377c03cc8df8e0d1e792a143a01423503028ce963afa": ["154d6f239864c19c43ef377c03cc8df8e0d1e792a143a01423503028ce963afa"] + "49b41034ab9ba0b7fd92fd53eb79d7fd3d601191a15269682830f9c92e3688ad": ["49b41034ab9ba0b7fd92fd53eb79d7fd3d601191a15269682830f9c92e3688ad"] +nonce: testnet-grants-0 +keyPairs: [] diff --git a/pact/genesis/testnet05/grantsN.yaml b/pact/genesis/testnet05/grantsN.yaml new file mode 100644 index 0000000000..ab90e49087 --- /dev/null +++ b/pact/genesis/testnet05/grantsN.yaml @@ -0,0 +1,45 @@ +# Internal testnet accounts +code: |- + (coin.coinbase "anastasia" (read-keyset "anastasia") 10000000.0) + (coin.coinbase "emily" (read-keyset "emily") 10000000.0) + (coin.coinbase "heekyun" (read-keyset "heekyun") 10000000.0) + (coin.coinbase "jeff" (read-keyset "jeff") 10000000.0) + (coin.coinbase "leah" (read-keyset "leah") 10000000.0) + (coin.coinbase "linda" (read-keyset "linda") 10000000.0) + (coin.coinbase "stuart" (read-keyset "stuart") 1000000000.0) + (coin.coinbase "will" (read-keyset "will") 1000000000.0) + (coin.coinbase "Kadena" (keyset-ref-guard "Kadena") 1000000000.0) + (coin.coinbase "k:4a7c20d087df76753e28d765eb84e1338479dd6df121ddda3e59576d0a32a5bf" (read-keyset "4a7c20d087df76753e28d765eb84e1338479dd6df121ddda3e59576d0a32a5bf") 10000000.0) + (coin.coinbase "k:fab92c47ccb1fdd25173e2981ebb29de989acea88792e34cc87298f6a1578842" (read-keyset "fab92c47ccb1fdd25173e2981ebb29de989acea88792e34cc87298f6a1578842") 10000000.0) + (coin.coinbase "k:cbb94b12f55bd540a5aa507635111938f2282c2891b78c13e462fa018c4145d8" (read-keyset "cbb94b12f55bd540a5aa507635111938f2282c2891b78c13e462fa018c4145d8") 10000000.0) + (coin.coinbase "k:89f0a840e3bab4c32e07b14862f0a0f414543da036ecf339d3ed7ec93fa4a41b" (read-keyset "89f0a840e3bab4c32e07b14862f0a0f414543da036ecf339d3ed7ec93fa4a41b") 10000000.0) + (coin.coinbase "k:4c73566b5d8fa4cd2a2620f7b2bc57284c7fc5c48f8f0db2bf1675cad08dc4af" (read-keyset "4c73566b5d8fa4cd2a2620f7b2bc57284c7fc5c48f8f0db2bf1675cad08dc4af") 10000000.0) + (coin.coinbase "k:1c835d4e67917fd25781b11db1c12efbc4296c5c7fe981d35bbcf4a46a53441f" (read-keyset "1c835d4e67917fd25781b11db1c12efbc4296c5c7fe981d35bbcf4a46a53441f") 10000000.0) + (coin.coinbase "k:554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94" (read-keyset "554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94") 10000000.0) + (coin.coinbase "k:3e83213bedf6be0aaf8ed3f8e72ffb9a240930b8698b9646e8d913b61c93e4ab" (read-keyset "3e83213bedf6be0aaf8ed3f8e72ffb9a240930b8698b9646e8d913b61c93e4ab") 10000000.0) + (coin.coinbase "k:5e4a8d51fa3958026cddd93bbd874e1bdae149a12ac6f0152ea253942efc989c" (read-keyset "5e4a8d51fa3958026cddd93bbd874e1bdae149a12ac6f0152ea253942efc989c") 10000000.0) + (coin.coinbase "k:154d6f239864c19c43ef377c03cc8df8e0d1e792a143a01423503028ce963afa" (read-keyset "154d6f239864c19c43ef377c03cc8df8e0d1e792a143a01423503028ce963afa") 10000000.0) + (coin.coinbase "k:49b41034ab9ba0b7fd92fd53eb79d7fd3d601191a15269682830f9c92e3688ad" (read-keyset "49b41034ab9ba0b7fd92fd53eb79d7fd3d601191a15269682830f9c92e3688ad") 10000000.0) + +data: + anastasia: ["1ec74de724b3c700737d99906aba48dbed6b90140bbc64133c558571b18883c3"] + emily: ["368059ab25bec92f19a4f8ce172030e5d672e4879aaae8d5be8b705dd3dfae7f"] + heekyun: ["dfb16b13e4032a6878fd98506b22cb0d6e5932c541e656b7ee5d69d72e6eb76e"] + jeff: ["c53217bb053b827b5fbbb322fced67a86298ec40a59aeef15b2190a05ad0a6bd"] + leah: ["216643137fb5f6a127dbb1063702cbb7c1f153451a1ad4afa9f92e77447cc330"] + linda: ["e1002db0894299bb37238bab02170180c0ae56f5932169bb2fcf756059fea175"] + stuart: ["0e3180dd4ae9d4385a21d7ca95cc7229b0f6b2ca9e0f4861dd4eed685004df66"] + will: ["830ad773510ab0f5a0d4564b7b31d2b0b1ba7fd5a116f66598a1c63cc2142fd5"] + "4a7c20d087df76753e28d765eb84e1338479dd6df121ddda3e59576d0a32a5bf": ["4a7c20d087df76753e28d765eb84e1338479dd6df121ddda3e59576d0a32a5bf"] + "fab92c47ccb1fdd25173e2981ebb29de989acea88792e34cc87298f6a1578842": ["fab92c47ccb1fdd25173e2981ebb29de989acea88792e34cc87298f6a1578842"] + "cbb94b12f55bd540a5aa507635111938f2282c2891b78c13e462fa018c4145d8": ["cbb94b12f55bd540a5aa507635111938f2282c2891b78c13e462fa018c4145d8"] + "89f0a840e3bab4c32e07b14862f0a0f414543da036ecf339d3ed7ec93fa4a41b": ["89f0a840e3bab4c32e07b14862f0a0f414543da036ecf339d3ed7ec93fa4a41b"] + "4c73566b5d8fa4cd2a2620f7b2bc57284c7fc5c48f8f0db2bf1675cad08dc4af": ["4c73566b5d8fa4cd2a2620f7b2bc57284c7fc5c48f8f0db2bf1675cad08dc4af"] + "1c835d4e67917fd25781b11db1c12efbc4296c5c7fe981d35bbcf4a46a53441f": ["1c835d4e67917fd25781b11db1c12efbc4296c5c7fe981d35bbcf4a46a53441f"] + "554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94": ["554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94"] + "3e83213bedf6be0aaf8ed3f8e72ffb9a240930b8698b9646e8d913b61c93e4ab": ["3e83213bedf6be0aaf8ed3f8e72ffb9a240930b8698b9646e8d913b61c93e4ab"] + "5e4a8d51fa3958026cddd93bbd874e1bdae149a12ac6f0152ea253942efc989c": ["5e4a8d51fa3958026cddd93bbd874e1bdae149a12ac6f0152ea253942efc989c"] + "154d6f239864c19c43ef377c03cc8df8e0d1e792a143a01423503028ce963afa": ["154d6f239864c19c43ef377c03cc8df8e0d1e792a143a01423503028ce963afa"] + "49b41034ab9ba0b7fd92fd53eb79d7fd3d601191a15269682830f9c92e3688ad": ["49b41034ab9ba0b7fd92fd53eb79d7fd3d601191a15269682830f9c92e3688ad"] +nonce: testnet-grants-N +keyPairs: [] diff --git a/src/Chainweb/Block.hs b/src/Chainweb/Block.hs new file mode 100644 index 0000000000..2d1e1be8f9 --- /dev/null +++ b/src/Chainweb/Block.hs @@ -0,0 +1,14 @@ +-- | 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 + (Block(..)) + where + +import Chainweb.BlockHeader +import Chainweb.Payload + +data Block = Block + { _blockHeader :: !BlockHeader + , _blockPayloadWithOutputs :: !PayloadWithOutputs + } + deriving (Eq, Show) diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5Development0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5Development0Payload.hs new file mode 100644 index 0000000000..33e0ab9c02 --- /dev/null +++ b/src/Chainweb/BlockHeader/Genesis/Pact5Development0Payload.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.BlockHeader.Genesis.Pact5Development0Payload ( 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 "b1SUWHsMwScdGFR-8xE0OdEsownzscx2taO6h7LFlrE" + + 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 "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6InVONzFzeDh1WWFQbjVodVFVUHJYaFJzeHkzelhlUzZ3VDYyWnRPMkhUT2siLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (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/Pact5Development1to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5Development1to19Payload.hs new file mode 100644 index 0000000000..1e156d1926 --- /dev/null +++ b/src/Chainweb/BlockHeader/Genesis/Pact5Development1to19Payload.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.BlockHeader.Genesis.Pact5Development1to19Payload ( 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 "9DDGXYZKVjkYgMtdSuBXMdS1gBPwqTg1jKPR8vKH4xc" + + 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 "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6InVONzFzeDh1WWFQbjVodVFVUHJYaFJzeHkzelhlUzZ3VDYyWnRPMkhUT2siLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (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 new file mode 100644 index 0000000000..16f28a79e5 --- /dev/null +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM0Payload ( 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 "b1SUWHsMwScdGFR-8xE0OdEsownzscx2taO6h7LFlrE" + + 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 "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6InVONzFzeDh1WWFQbjVodVFVUHJYaFJzeHkzelhlUzZ3VDYyWnRPMkhUT2siLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (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/Pact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs new file mode 100644 index 0000000000..036456f694 --- /dev/null +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM1to9Payload ( 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 "9DDGXYZKVjkYgMtdSuBXMdS1gBPwqTg1jKPR8vKH4xc" + + 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 "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6InVONzFzeDh1WWFQbjVodVFVUHJYaFJzeHkzelhlUzZ3VDYyWnRPMkhUT2siLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (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/Testnet0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Testnet040Payload.hs similarity index 99% rename from src/Chainweb/BlockHeader/Genesis/Testnet0Payload.hs rename to src/Chainweb/BlockHeader/Genesis/Testnet040Payload.hs index c0268de1e6..e7d9b18a7d 100644 --- a/src/Chainweb/BlockHeader/Genesis/Testnet0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Testnet040Payload.hs @@ -2,7 +2,7 @@ -- This module is auto-generated. DO NOT EDIT IT MANUALLY. -module Chainweb.BlockHeader.Genesis.Testnet0Payload ( payloadBlock ) where +module Chainweb.BlockHeader.Genesis.Testnet040Payload ( payloadBlock ) where import qualified Data.Text as T import qualified Data.Vector as V diff --git a/src/Chainweb/BlockHeader/Genesis/Testnet1to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Testnet041to19Payload.hs similarity index 99% rename from src/Chainweb/BlockHeader/Genesis/Testnet1to19Payload.hs rename to src/Chainweb/BlockHeader/Genesis/Testnet041to19Payload.hs index 133ca82b21..87d45eb48c 100644 --- a/src/Chainweb/BlockHeader/Genesis/Testnet1to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Testnet041to19Payload.hs @@ -2,7 +2,7 @@ -- This module is auto-generated. DO NOT EDIT IT MANUALLY. -module Chainweb.BlockHeader.Genesis.Testnet1to19Payload ( payloadBlock ) where +module Chainweb.BlockHeader.Genesis.Testnet041to19Payload ( payloadBlock ) where import qualified Data.Text as T import qualified Data.Vector as V diff --git a/src/Chainweb/BlockHeader/Genesis/Testnet050Payload.hs b/src/Chainweb/BlockHeader/Genesis/Testnet050Payload.hs new file mode 100644 index 0000000000..63909bd78a --- /dev/null +++ b/src/Chainweb/BlockHeader/Genesis/Testnet050Payload.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.BlockHeader.Genesis.Testnet050Payload ( 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 "Gbu_Tf-PJP2VyptN3m0AnTsXRfiFpnxV8iWZcimPZq4" + + 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 "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6InVONzFzeDh1WWFQbjVodVFVUHJYaFJzeHkzelhlUzZ3VDYyWnRPMkhUT2siLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (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 "eyJoYXNoIjoiV2hKVTlyMXZWc0NOdmFWdUdBaHRYempMdTR3UlR3TGsxNlQ2OEY2N3FMdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcIkVtaWx5XFxcIiAodGltZSBcXFwiMjAxOS0xMC0wMVQwMDowMDowMFpcXFwiKSBcXFwiRW1pbHlcXFwiIDY2NjY2Ni42NylcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJNQUlOTkVUXzFcXFwiICh0aW1lIFxcXCIyMDE5LTEwLTI5VDE0OjAwOjAwWlxcXCIpIFxcXCJNQUlOTkVUXFxcIiA2NjY2NjYuNjcpXFxuKGNvaW4uY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudCBcXFwiTUFJTk5FVF8yXFxcIiAodGltZSBcXFwiMjAxOS0xMC0yOVQxNzowMDowMFpcXFwiKSBcXFwiTUFJTk5FVFxcXCIgNjY2NjY2LjY3KVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcIk1BSU5ORVRfM1xcXCIgKHRpbWUgXFxcIjIwMTktMTEtMjlUMTc6MDA6MDBaXFxcIikgXFxcIk1BSU5ORVRcXFwiIDY2NjY2Ni42NylcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDBcXFwiICh0aW1lIFxcXCIxOTAwLTEwLTE1VDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDBcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDFcXFwiICh0aW1lIFxcXCIyMDIwLTEwLTMxVDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDJcXFwiICh0aW1lIFxcXCIxOTAwLTEwLTMxVDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDJcXFwiIDEwMDAwMDAuMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcInRlc3RuZXQtYWxsb2NhdGlvbnMtMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJXaEpVOXIxdlZzQ052YVZ1R0FodFh6akx1NHdSVHdMazE2VDY4RjY3cUx3IiwibG9ncyI6IkI1elZaYVRDWk9sbERjR2tGRDRhcHJPTi1mUlY1ZFlUWEJyTmtSSVo4LTQiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") + , (unsafeFromText "eyJoYXNoIjoibU1neUsxRGpvRGt6Z3ZxdHhvaW84WUt4X3JjY2UtaFR3bWRQdkc5emJXRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiNGM3MzU2NmI1ZDhmYTRjZDJhMjYyMGY3YjJiYzU3Mjg0YzdmYzVjNDhmOGYwZGIyYmYxNjc1Y2FkMDhkYzRhZlwiOltcIjRjNzM1NjZiNWQ4ZmE0Y2QyYTI2MjBmN2IyYmM1NzI4NGM3ZmM1YzQ4ZjhmMGRiMmJmMTY3NWNhZDA4ZGM0YWZcIl0sXCJlbWlseVwiOltcIjM2ODA1OWFiMjViZWM5MmYxOWE0ZjhjZTE3MjAzMGU1ZDY3MmU0ODc5YWFhZThkNWJlOGI3MDVkZDNkZmFlN2ZcIl0sXCJjYmI5NGIxMmY1NWJkNTQwYTVhYTUwNzYzNTExMTkzOGYyMjgyYzI4OTFiNzhjMTNlNDYyZmEwMThjNDE0NWQ4XCI6W1wiY2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOFwiXSxcIjQ5YjQxMDM0YWI5YmEwYjdmZDkyZmQ1M2ViNzlkN2ZkM2Q2MDExOTFhMTUyNjk2ODI4MzBmOWM5MmUzNjg4YWRcIjpbXCI0OWI0MTAzNGFiOWJhMGI3ZmQ5MmZkNTNlYjc5ZDdmZDNkNjAxMTkxYTE1MjY5NjgyODMwZjljOTJlMzY4OGFkXCJdLFwiYW5hc3Rhc2lhXCI6W1wiMWVjNzRkZTcyNGIzYzcwMDczN2Q5OTkwNmFiYTQ4ZGJlZDZiOTAxNDBiYmM2NDEzM2M1NTg1NzFiMTg4ODNjM1wiXSxcIjVlNGE4ZDUxZmEzOTU4MDI2Y2RkZDkzYmJkODc0ZTFiZGFlMTQ5YTEyYWM2ZjAxNTJlYTI1Mzk0MmVmYzk4OWNcIjpbXCI1ZTRhOGQ1MWZhMzk1ODAyNmNkZGQ5M2JiZDg3NGUxYmRhZTE0OWExMmFjNmYwMTUyZWEyNTM5NDJlZmM5ODljXCJdLFwiMWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZlwiOltcIjFjODM1ZDRlNjc5MTdmZDI1NzgxYjExZGIxYzEyZWZiYzQyOTZjNWM3ZmU5ODFkMzViYmNmNGE0NmE1MzQ0MWZcIl0sXCI4OWYwYTg0MGUzYmFiNGMzMmUwN2IxNDg2MmYwYTBmNDE0NTQzZGEwMzZlY2YzMzlkM2VkN2VjOTNmYTRhNDFiXCI6W1wiODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYlwiXSxcImZhYjkyYzQ3Y2NiMWZkZDI1MTczZTI5ODFlYmIyOWRlOTg5YWNlYTg4NzkyZTM0Y2M4NzI5OGY2YTE1Nzg4NDJcIjpbXCJmYWI5MmM0N2NjYjFmZGQyNTE3M2UyOTgxZWJiMjlkZTk4OWFjZWE4ODc5MmUzNGNjODcyOThmNmExNTc4ODQyXCJdLFwiamVmZlwiOltcImM1MzIxN2JiMDUzYjgyN2I1ZmJiYjMyMmZjZWQ2N2E4NjI5OGVjNDBhNTlhZWVmMTViMjE5MGEwNWFkMGE2YmRcIl0sXCJzdHVhcnRcIjpbXCIwZTMxODBkZDRhZTlkNDM4NWEyMWQ3Y2E5NWNjNzIyOWIwZjZiMmNhOWUwZjQ4NjFkZDRlZWQ2ODUwMDRkZjY2XCJdLFwibGVhaFwiOltcIjIxNjY0MzEzN2ZiNWY2YTEyN2RiYjEwNjM3MDJjYmI3YzFmMTUzNDUxYTFhZDRhZmE5ZjkyZTc3NDQ3Y2MzMzBcIl0sXCIxNTRkNmYyMzk4NjRjMTljNDNlZjM3N2MwM2NjOGRmOGUwZDFlNzkyYTE0M2EwMTQyMzUwMzAyOGNlOTYzYWZhXCI6W1wiMTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYVwiXSxcIjNlODMyMTNiZWRmNmJlMGFhZjhlZDNmOGU3MmZmYjlhMjQwOTMwYjg2OThiOTY0NmU4ZDkxM2I2MWM5M2U0YWJcIjpbXCIzZTgzMjEzYmVkZjZiZTBhYWY4ZWQzZjhlNzJmZmI5YTI0MDkzMGI4Njk4Yjk2NDZlOGQ5MTNiNjFjOTNlNGFiXCJdLFwid2lsbFwiOltcIjgzMGFkNzczNTEwYWIwZjVhMGQ0NTY0YjdiMzFkMmIwYjFiYTdmZDVhMTE2ZjY2NTk4YTFjNjNjYzIxNDJmZDVcIl0sXCJsaW5kYVwiOltcImUxMDAyZGIwODk0Mjk5YmIzNzIzOGJhYjAyMTcwMTgwYzBhZTU2ZjU5MzIxNjliYjJmY2Y3NTYwNTlmZWExNzVcIl0sXCI1NTQ3NTRmNDhiMTZkZjI0YjU1MmY2ODMyZGRhMDkwNjQyZWQ5NjU4NTU5ZmVmOWYzZWUxYmI0NjM3ZWE3Yzk0XCI6W1wiNTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NFwiXSxcIjRhN2MyMGQwODdkZjc2NzUzZTI4ZDc2NWViODRlMTMzODQ3OWRkNmRmMTIxZGRkYTNlNTk1NzZkMGEzMmE1YmZcIjpbXCI0YTdjMjBkMDg3ZGY3Njc1M2UyOGQ3NjVlYjg0ZTEzMzg0NzlkZDZkZjEyMWRkZGEzZTU5NTc2ZDBhMzJhNWJmXCJdLFwiaGVla3l1blwiOltcImRmYjE2YjEzZTQwMzJhNjg3OGZkOTg1MDZiMjJjYjBkNmU1OTMyYzU0MWU2NTZiN2VlNWQ2OWQ3MmU2ZWI3NmVcIl19LFwiY29kZVwiOlwiKGNvaW4uY29pbmJhc2UgXFxcImFuYXN0YXNpYVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbmFzdGFzaWFcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJlbWlseVxcXCIgKHJlYWQta2V5c2V0IFxcXCJlbWlseVxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImhlZWt5dW5cXFwiIChyZWFkLWtleXNldCBcXFwiaGVla3l1blxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImplZmZcXFwiIChyZWFkLWtleXNldCBcXFwiamVmZlxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImxlYWhcXFwiIChyZWFkLWtleXNldCBcXFwibGVhaFxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImxpbmRhXFxcIiAocmVhZC1rZXlzZXQgXFxcImxpbmRhXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwic3R1YXJ0XFxcIiAocmVhZC1rZXlzZXQgXFxcInN0dWFydFxcXCIpIDEwMDAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwid2lsbFxcXCIgKHJlYWQta2V5c2V0IFxcXCJ3aWxsXFxcIikgMTAwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJLYWRlbmFcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJLYWRlbmFcXFwiKSAxMDAwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6NGE3YzIwZDA4N2RmNzY3NTNlMjhkNzY1ZWI4NGUxMzM4NDc5ZGQ2ZGYxMjFkZGRhM2U1OTU3NmQwYTMyYTViZlxcXCIgKHJlYWQta2V5c2V0IFxcXCI0YTdjMjBkMDg3ZGY3Njc1M2UyOGQ3NjVlYjg0ZTEzMzg0NzlkZDZkZjEyMWRkZGEzZTU5NTc2ZDBhMzJhNWJmXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazpmYWI5MmM0N2NjYjFmZGQyNTE3M2UyOTgxZWJiMjlkZTk4OWFjZWE4ODc5MmUzNGNjODcyOThmNmExNTc4ODQyXFxcIiAocmVhZC1rZXlzZXQgXFxcImZhYjkyYzQ3Y2NiMWZkZDI1MTczZTI5ODFlYmIyOWRlOTg5YWNlYTg4NzkyZTM0Y2M4NzI5OGY2YTE1Nzg4NDJcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOmNiYjk0YjEyZjU1YmQ1NDBhNWFhNTA3NjM1MTExOTM4ZjIyODJjMjg5MWI3OGMxM2U0NjJmYTAxOGM0MTQ1ZDhcXFwiIChyZWFkLWtleXNldCBcXFwiY2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOFxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6ODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYlxcXCIgKHJlYWQta2V5c2V0IFxcXCI4OWYwYTg0MGUzYmFiNGMzMmUwN2IxNDg2MmYwYTBmNDE0NTQzZGEwMzZlY2YzMzlkM2VkN2VjOTNmYTRhNDFiXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazo0YzczNTY2YjVkOGZhNGNkMmEyNjIwZjdiMmJjNTcyODRjN2ZjNWM0OGY4ZjBkYjJiZjE2NzVjYWQwOGRjNGFmXFxcIiAocmVhZC1rZXlzZXQgXFxcIjRjNzM1NjZiNWQ4ZmE0Y2QyYTI2MjBmN2IyYmM1NzI4NGM3ZmM1YzQ4ZjhmMGRiMmJmMTY3NWNhZDA4ZGM0YWZcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOjFjODM1ZDRlNjc5MTdmZDI1NzgxYjExZGIxYzEyZWZiYzQyOTZjNWM3ZmU5ODFkMzViYmNmNGE0NmE1MzQ0MWZcXFwiIChyZWFkLWtleXNldCBcXFwiMWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZlxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6NTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NFxcXCIgKHJlYWQta2V5c2V0IFxcXCI1NTQ3NTRmNDhiMTZkZjI0YjU1MmY2ODMyZGRhMDkwNjQyZWQ5NjU4NTU5ZmVmOWYzZWUxYmI0NjM3ZWE3Yzk0XFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazozZTgzMjEzYmVkZjZiZTBhYWY4ZWQzZjhlNzJmZmI5YTI0MDkzMGI4Njk4Yjk2NDZlOGQ5MTNiNjFjOTNlNGFiXFxcIiAocmVhZC1rZXlzZXQgXFxcIjNlODMyMTNiZWRmNmJlMGFhZjhlZDNmOGU3MmZmYjlhMjQwOTMwYjg2OThiOTY0NmU4ZDkxM2I2MWM5M2U0YWJcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOjVlNGE4ZDUxZmEzOTU4MDI2Y2RkZDkzYmJkODc0ZTFiZGFlMTQ5YTEyYWM2ZjAxNTJlYTI1Mzk0MmVmYzk4OWNcXFwiIChyZWFkLWtleXNldCBcXFwiNWU0YThkNTFmYTM5NTgwMjZjZGRkOTNiYmQ4NzRlMWJkYWUxNDlhMTJhYzZmMDE1MmVhMjUzOTQyZWZjOTg5Y1xcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6MTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYVxcXCIgKHJlYWQta2V5c2V0IFxcXCIxNTRkNmYyMzk4NjRjMTljNDNlZjM3N2MwM2NjOGRmOGUwZDFlNzkyYTE0M2EwMTQyMzUwMzAyOGNlOTYzYWZhXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazo0OWI0MTAzNGFiOWJhMGI3ZmQ5MmZkNTNlYjc5ZDdmZDNkNjAxMTkxYTE1MjY5NjgyODMwZjljOTJlMzY4OGFkXFxcIiAocmVhZC1rZXlzZXQgXFxcIjQ5YjQxMDM0YWI5YmEwYjdmZDkyZmQ1M2ViNzlkN2ZkM2Q2MDExOTFhMTUyNjk2ODI4MzBmOWM5MmUzNjg4YWRcXFwiKSAxMDAwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwidGVzdG5ldC1ncmFudHMtMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJtTWd5SzFEam9Ea3pndnF0eG9pbzhZS3hfcmNjZS1oVHdtZFB2Rzl6YldFIiwibG9ncyI6Inkwcl9hODB2QzJhc2RsZW9GWVhXT1dHVlR2QVROWUlYRFR2MHRkTTgtZ28iLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJhbmFzdGFzaWEiLDEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJlbWlseSIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImhlZWt5dW4iLDEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJqZWZmIiwxMDAwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwibGVhaCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImxpbmRhIiwxMDAwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwic3R1YXJ0IiwxMDAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJ3aWxsIiwxMDAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJLYWRlbmEiLDEwMDAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NGE3YzIwZDA4N2RmNzY3NTNlMjhkNzY1ZWI4NGUxMzM4NDc5ZGQ2ZGYxMjFkZGRhM2U1OTU3NmQwYTMyYTViZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6ZmFiOTJjNDdjY2IxZmRkMjUxNzNlMjk4MWViYjI5ZGU5ODlhY2VhODg3OTJlMzRjYzg3Mjk4ZjZhMTU3ODg0MiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6Y2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6ODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NGM3MzU2NmI1ZDhmYTRjZDJhMjYyMGY3YjJiYzU3Mjg0YzdmYzVjNDhmOGYwZGIyYmYxNjc1Y2FkMDhkYzRhZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6MWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6M2U4MzIxM2JlZGY2YmUwYWFmOGVkM2Y4ZTcyZmZiOWEyNDA5MzBiODY5OGI5NjQ2ZThkOTEzYjYxYzkzZTRhYiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NWU0YThkNTFmYTM5NTgwMjZjZGRkOTNiYmQ4NzRlMWJkYWUxNDlhMTJhYzZmMDE1MmVhMjUzOTQyZWZjOTg5YyIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6MTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYSIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NDliNDEwMzRhYjliYTBiN2ZkOTJmZDUzZWI3OWQ3ZmQzZDYwMTE5MWExNTI2OTY4MjgzMGY5YzkyZTM2ODhhZCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9XSwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6OH0") + ] diff --git a/src/Chainweb/BlockHeader/Genesis/Testnet051to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Testnet051to19Payload.hs new file mode 100644 index 0000000000..ad6f244850 --- /dev/null +++ b/src/Chainweb/BlockHeader/Genesis/Testnet051to19Payload.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.BlockHeader.Genesis.Testnet051to19Payload ( 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 "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go" + + 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 "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6InVONzFzeDh1WWFQbjVodVFVUHJYaFJzeHkzelhlUzZ3VDYyWnRPMkhUT2siLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (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 "eyJoYXNoIjoiV2hKVTlyMXZWc0NOdmFWdUdBaHRYempMdTR3UlR3TGsxNlQ2OEY2N3FMdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcIkVtaWx5XFxcIiAodGltZSBcXFwiMjAxOS0xMC0wMVQwMDowMDowMFpcXFwiKSBcXFwiRW1pbHlcXFwiIDY2NjY2Ni42NylcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJNQUlOTkVUXzFcXFwiICh0aW1lIFxcXCIyMDE5LTEwLTI5VDE0OjAwOjAwWlxcXCIpIFxcXCJNQUlOTkVUXFxcIiA2NjY2NjYuNjcpXFxuKGNvaW4uY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudCBcXFwiTUFJTk5FVF8yXFxcIiAodGltZSBcXFwiMjAxOS0xMC0yOVQxNzowMDowMFpcXFwiKSBcXFwiTUFJTk5FVFxcXCIgNjY2NjY2LjY3KVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcIk1BSU5ORVRfM1xcXCIgKHRpbWUgXFxcIjIwMTktMTEtMjlUMTc6MDA6MDBaXFxcIikgXFxcIk1BSU5ORVRcXFwiIDY2NjY2Ni42NylcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDBcXFwiICh0aW1lIFxcXCIxOTAwLTEwLTE1VDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDBcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDFcXFwiICh0aW1lIFxcXCIyMDIwLTEwLTMxVDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDJcXFwiICh0aW1lIFxcXCIxOTAwLTEwLTMxVDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDJcXFwiIDEwMDAwMDAuMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcInRlc3RuZXQtYWxsb2NhdGlvbnMtMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJXaEpVOXIxdlZzQ052YVZ1R0FodFh6akx1NHdSVHdMazE2VDY4RjY3cUx3IiwibG9ncyI6IkI1elZaYVRDWk9sbERjR2tGRDRhcHJPTi1mUlY1ZFlUWEJyTmtSSVo4LTQiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") + , (unsafeFromText "eyJoYXNoIjoieDZKbnc4YXhaYVJLTVphVEtmTzA2dXMyQTRrRTBES2owRm5BVEZXVWVXVSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiNGM3MzU2NmI1ZDhmYTRjZDJhMjYyMGY3YjJiYzU3Mjg0YzdmYzVjNDhmOGYwZGIyYmYxNjc1Y2FkMDhkYzRhZlwiOltcIjRjNzM1NjZiNWQ4ZmE0Y2QyYTI2MjBmN2IyYmM1NzI4NGM3ZmM1YzQ4ZjhmMGRiMmJmMTY3NWNhZDA4ZGM0YWZcIl0sXCJlbWlseVwiOltcIjM2ODA1OWFiMjViZWM5MmYxOWE0ZjhjZTE3MjAzMGU1ZDY3MmU0ODc5YWFhZThkNWJlOGI3MDVkZDNkZmFlN2ZcIl0sXCJjYmI5NGIxMmY1NWJkNTQwYTVhYTUwNzYzNTExMTkzOGYyMjgyYzI4OTFiNzhjMTNlNDYyZmEwMThjNDE0NWQ4XCI6W1wiY2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOFwiXSxcIjQ5YjQxMDM0YWI5YmEwYjdmZDkyZmQ1M2ViNzlkN2ZkM2Q2MDExOTFhMTUyNjk2ODI4MzBmOWM5MmUzNjg4YWRcIjpbXCI0OWI0MTAzNGFiOWJhMGI3ZmQ5MmZkNTNlYjc5ZDdmZDNkNjAxMTkxYTE1MjY5NjgyODMwZjljOTJlMzY4OGFkXCJdLFwiYW5hc3Rhc2lhXCI6W1wiMWVjNzRkZTcyNGIzYzcwMDczN2Q5OTkwNmFiYTQ4ZGJlZDZiOTAxNDBiYmM2NDEzM2M1NTg1NzFiMTg4ODNjM1wiXSxcIjVlNGE4ZDUxZmEzOTU4MDI2Y2RkZDkzYmJkODc0ZTFiZGFlMTQ5YTEyYWM2ZjAxNTJlYTI1Mzk0MmVmYzk4OWNcIjpbXCI1ZTRhOGQ1MWZhMzk1ODAyNmNkZGQ5M2JiZDg3NGUxYmRhZTE0OWExMmFjNmYwMTUyZWEyNTM5NDJlZmM5ODljXCJdLFwiMWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZlwiOltcIjFjODM1ZDRlNjc5MTdmZDI1NzgxYjExZGIxYzEyZWZiYzQyOTZjNWM3ZmU5ODFkMzViYmNmNGE0NmE1MzQ0MWZcIl0sXCI4OWYwYTg0MGUzYmFiNGMzMmUwN2IxNDg2MmYwYTBmNDE0NTQzZGEwMzZlY2YzMzlkM2VkN2VjOTNmYTRhNDFiXCI6W1wiODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYlwiXSxcImZhYjkyYzQ3Y2NiMWZkZDI1MTczZTI5ODFlYmIyOWRlOTg5YWNlYTg4NzkyZTM0Y2M4NzI5OGY2YTE1Nzg4NDJcIjpbXCJmYWI5MmM0N2NjYjFmZGQyNTE3M2UyOTgxZWJiMjlkZTk4OWFjZWE4ODc5MmUzNGNjODcyOThmNmExNTc4ODQyXCJdLFwiamVmZlwiOltcImM1MzIxN2JiMDUzYjgyN2I1ZmJiYjMyMmZjZWQ2N2E4NjI5OGVjNDBhNTlhZWVmMTViMjE5MGEwNWFkMGE2YmRcIl0sXCJzdHVhcnRcIjpbXCIwZTMxODBkZDRhZTlkNDM4NWEyMWQ3Y2E5NWNjNzIyOWIwZjZiMmNhOWUwZjQ4NjFkZDRlZWQ2ODUwMDRkZjY2XCJdLFwibGVhaFwiOltcIjIxNjY0MzEzN2ZiNWY2YTEyN2RiYjEwNjM3MDJjYmI3YzFmMTUzNDUxYTFhZDRhZmE5ZjkyZTc3NDQ3Y2MzMzBcIl0sXCIxNTRkNmYyMzk4NjRjMTljNDNlZjM3N2MwM2NjOGRmOGUwZDFlNzkyYTE0M2EwMTQyMzUwMzAyOGNlOTYzYWZhXCI6W1wiMTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYVwiXSxcIjNlODMyMTNiZWRmNmJlMGFhZjhlZDNmOGU3MmZmYjlhMjQwOTMwYjg2OThiOTY0NmU4ZDkxM2I2MWM5M2U0YWJcIjpbXCIzZTgzMjEzYmVkZjZiZTBhYWY4ZWQzZjhlNzJmZmI5YTI0MDkzMGI4Njk4Yjk2NDZlOGQ5MTNiNjFjOTNlNGFiXCJdLFwid2lsbFwiOltcIjgzMGFkNzczNTEwYWIwZjVhMGQ0NTY0YjdiMzFkMmIwYjFiYTdmZDVhMTE2ZjY2NTk4YTFjNjNjYzIxNDJmZDVcIl0sXCJsaW5kYVwiOltcImUxMDAyZGIwODk0Mjk5YmIzNzIzOGJhYjAyMTcwMTgwYzBhZTU2ZjU5MzIxNjliYjJmY2Y3NTYwNTlmZWExNzVcIl0sXCI1NTQ3NTRmNDhiMTZkZjI0YjU1MmY2ODMyZGRhMDkwNjQyZWQ5NjU4NTU5ZmVmOWYzZWUxYmI0NjM3ZWE3Yzk0XCI6W1wiNTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NFwiXSxcIjRhN2MyMGQwODdkZjc2NzUzZTI4ZDc2NWViODRlMTMzODQ3OWRkNmRmMTIxZGRkYTNlNTk1NzZkMGEzMmE1YmZcIjpbXCI0YTdjMjBkMDg3ZGY3Njc1M2UyOGQ3NjVlYjg0ZTEzMzg0NzlkZDZkZjEyMWRkZGEzZTU5NTc2ZDBhMzJhNWJmXCJdLFwiaGVla3l1blwiOltcImRmYjE2YjEzZTQwMzJhNjg3OGZkOTg1MDZiMjJjYjBkNmU1OTMyYzU0MWU2NTZiN2VlNWQ2OWQ3MmU2ZWI3NmVcIl19LFwiY29kZVwiOlwiKGNvaW4uY29pbmJhc2UgXFxcImFuYXN0YXNpYVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbmFzdGFzaWFcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJlbWlseVxcXCIgKHJlYWQta2V5c2V0IFxcXCJlbWlseVxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImhlZWt5dW5cXFwiIChyZWFkLWtleXNldCBcXFwiaGVla3l1blxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImplZmZcXFwiIChyZWFkLWtleXNldCBcXFwiamVmZlxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImxlYWhcXFwiIChyZWFkLWtleXNldCBcXFwibGVhaFxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImxpbmRhXFxcIiAocmVhZC1rZXlzZXQgXFxcImxpbmRhXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwic3R1YXJ0XFxcIiAocmVhZC1rZXlzZXQgXFxcInN0dWFydFxcXCIpIDEwMDAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwid2lsbFxcXCIgKHJlYWQta2V5c2V0IFxcXCJ3aWxsXFxcIikgMTAwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJLYWRlbmFcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJLYWRlbmFcXFwiKSAxMDAwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6NGE3YzIwZDA4N2RmNzY3NTNlMjhkNzY1ZWI4NGUxMzM4NDc5ZGQ2ZGYxMjFkZGRhM2U1OTU3NmQwYTMyYTViZlxcXCIgKHJlYWQta2V5c2V0IFxcXCI0YTdjMjBkMDg3ZGY3Njc1M2UyOGQ3NjVlYjg0ZTEzMzg0NzlkZDZkZjEyMWRkZGEzZTU5NTc2ZDBhMzJhNWJmXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazpmYWI5MmM0N2NjYjFmZGQyNTE3M2UyOTgxZWJiMjlkZTk4OWFjZWE4ODc5MmUzNGNjODcyOThmNmExNTc4ODQyXFxcIiAocmVhZC1rZXlzZXQgXFxcImZhYjkyYzQ3Y2NiMWZkZDI1MTczZTI5ODFlYmIyOWRlOTg5YWNlYTg4NzkyZTM0Y2M4NzI5OGY2YTE1Nzg4NDJcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOmNiYjk0YjEyZjU1YmQ1NDBhNWFhNTA3NjM1MTExOTM4ZjIyODJjMjg5MWI3OGMxM2U0NjJmYTAxOGM0MTQ1ZDhcXFwiIChyZWFkLWtleXNldCBcXFwiY2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOFxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6ODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYlxcXCIgKHJlYWQta2V5c2V0IFxcXCI4OWYwYTg0MGUzYmFiNGMzMmUwN2IxNDg2MmYwYTBmNDE0NTQzZGEwMzZlY2YzMzlkM2VkN2VjOTNmYTRhNDFiXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazo0YzczNTY2YjVkOGZhNGNkMmEyNjIwZjdiMmJjNTcyODRjN2ZjNWM0OGY4ZjBkYjJiZjE2NzVjYWQwOGRjNGFmXFxcIiAocmVhZC1rZXlzZXQgXFxcIjRjNzM1NjZiNWQ4ZmE0Y2QyYTI2MjBmN2IyYmM1NzI4NGM3ZmM1YzQ4ZjhmMGRiMmJmMTY3NWNhZDA4ZGM0YWZcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOjFjODM1ZDRlNjc5MTdmZDI1NzgxYjExZGIxYzEyZWZiYzQyOTZjNWM3ZmU5ODFkMzViYmNmNGE0NmE1MzQ0MWZcXFwiIChyZWFkLWtleXNldCBcXFwiMWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZlxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6NTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NFxcXCIgKHJlYWQta2V5c2V0IFxcXCI1NTQ3NTRmNDhiMTZkZjI0YjU1MmY2ODMyZGRhMDkwNjQyZWQ5NjU4NTU5ZmVmOWYzZWUxYmI0NjM3ZWE3Yzk0XFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazozZTgzMjEzYmVkZjZiZTBhYWY4ZWQzZjhlNzJmZmI5YTI0MDkzMGI4Njk4Yjk2NDZlOGQ5MTNiNjFjOTNlNGFiXFxcIiAocmVhZC1rZXlzZXQgXFxcIjNlODMyMTNiZWRmNmJlMGFhZjhlZDNmOGU3MmZmYjlhMjQwOTMwYjg2OThiOTY0NmU4ZDkxM2I2MWM5M2U0YWJcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOjVlNGE4ZDUxZmEzOTU4MDI2Y2RkZDkzYmJkODc0ZTFiZGFlMTQ5YTEyYWM2ZjAxNTJlYTI1Mzk0MmVmYzk4OWNcXFwiIChyZWFkLWtleXNldCBcXFwiNWU0YThkNTFmYTM5NTgwMjZjZGRkOTNiYmQ4NzRlMWJkYWUxNDlhMTJhYzZmMDE1MmVhMjUzOTQyZWZjOTg5Y1xcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6MTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYVxcXCIgKHJlYWQta2V5c2V0IFxcXCIxNTRkNmYyMzk4NjRjMTljNDNlZjM3N2MwM2NjOGRmOGUwZDFlNzkyYTE0M2EwMTQyMzUwMzAyOGNlOTYzYWZhXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazo0OWI0MTAzNGFiOWJhMGI3ZmQ5MmZkNTNlYjc5ZDdmZDNkNjAxMTkxYTE1MjY5NjgyODMwZjljOTJlMzY4OGFkXFxcIiAocmVhZC1rZXlzZXQgXFxcIjQ5YjQxMDM0YWI5YmEwYjdmZDkyZmQ1M2ViNzlkN2ZkM2Q2MDExOTFhMTUyNjk2ODI4MzBmOWM5MmUzNjg4YWRcXFwiKSAxMDAwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwidGVzdG5ldC1ncmFudHMtTlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJ4NkpudzhheFphUktNWmFUS2ZPMDZ1czJBNGtFMERLajBGbkFURldVZVdVIiwibG9ncyI6Inkwcl9hODB2QzJhc2RsZW9GWVhXT1dHVlR2QVROWUlYRFR2MHRkTTgtZ28iLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJhbmFzdGFzaWEiLDEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJlbWlseSIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImhlZWt5dW4iLDEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJqZWZmIiwxMDAwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwibGVhaCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImxpbmRhIiwxMDAwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwic3R1YXJ0IiwxMDAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJ3aWxsIiwxMDAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJLYWRlbmEiLDEwMDAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NGE3YzIwZDA4N2RmNzY3NTNlMjhkNzY1ZWI4NGUxMzM4NDc5ZGQ2ZGYxMjFkZGRhM2U1OTU3NmQwYTMyYTViZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6ZmFiOTJjNDdjY2IxZmRkMjUxNzNlMjk4MWViYjI5ZGU5ODlhY2VhODg3OTJlMzRjYzg3Mjk4ZjZhMTU3ODg0MiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6Y2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6ODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NGM3MzU2NmI1ZDhmYTRjZDJhMjYyMGY3YjJiYzU3Mjg0YzdmYzVjNDhmOGYwZGIyYmYxNjc1Y2FkMDhkYzRhZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6MWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6M2U4MzIxM2JlZGY2YmUwYWFmOGVkM2Y4ZTcyZmZiOWEyNDA5MzBiODY5OGI5NjQ2ZThkOTEzYjYxYzkzZTRhYiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NWU0YThkNTFmYTM5NTgwMjZjZGRkOTNiYmQ4NzRlMWJkYWUxNDlhMTJhYzZmMDE1MmVhMjUzOTQyZWZjOTg5YyIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6MTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYSIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NDliNDEwMzRhYjliYTBiN2ZkOTJmZDUzZWI3OWQ3ZmQzZDYwMTE5MWExNTI2OTY4MjgzMGY5YzkyZTM2ODhhZCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9XSwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6OH0") + ] diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index 4efa013a20..b3d75ca56d 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -151,8 +151,8 @@ import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Guards import Chainweb.Version.Mainnet -import Chainweb.Version.Registry (lookupVersionByName) -import Chainweb.Version.Testnet +import Chainweb.Version.Registry (lookupVersionByName, lookupVersionByCode) +import Chainweb.Version.Testnet04 import Control.DeepSeq import Control.Exception import Control.Lens hiding ((.=)) @@ -379,7 +379,7 @@ instance HasChainGraph BlockHeader where _chainGraph h = _chainGraph (_chainwebVersion h, _blockHeight h) instance HasChainwebVersion BlockHeader where - _chainwebVersion = _chainwebVersion . _blockChainwebVersion + _chainwebVersion = lookupVersionByCode . _blockChainwebVersion instance IsCasValue BlockHeader where type CasKeyType BlockHeader = BlockHash @@ -661,7 +661,7 @@ genesisBlockHeaderCache = unsafePerformIO $ do genesisBlockHeaders :: ChainwebVersion -> HashMap ChainId BlockHeader genesisBlockHeaders = \v -> if _versionCode v == _versionCode mainnet then mainnetGenesisHeaders - else if _versionCode v == _versionCode testnet then testnetGenesisHeaders + else if _versionCode v == _versionCode testnet04 then testnetGenesisHeaders else unsafeDupablePerformIO $ HM.lookup (_versionCode v) <$> readIORef genesisBlockHeaderCache >>= \case Just hs -> return hs @@ -671,7 +671,7 @@ genesisBlockHeaders = \v -> return freshGenesisHeaders where mainnetGenesisHeaders = makeGenesisBlockHeaders mainnet - testnetGenesisHeaders = makeGenesisBlockHeaders testnet + testnetGenesisHeaders = makeGenesisBlockHeaders testnet04 genesisBlockHeader :: (HasCallStack, HasChainId p) => ChainwebVersion -> p -> BlockHeader genesisBlockHeader v p = genesisBlockHeaders v ^?! at (_chainId p) . _Just @@ -681,7 +681,7 @@ makeGenesisBlockHeaders v = HM.fromList [ (cid, makeGenesisBlockHeader v cid) | makeGenesisBlockHeader :: ChainwebVersion -> ChainId -> BlockHeader makeGenesisBlockHeader v cid = - makeGenesisBlockHeader' v cid (_genesisTime (_versionGenesis v) ^?! onChain cid) (Nonce 0) + makeGenesisBlockHeader' v cid (_genesisTime (_versionGenesis v) ^?! atChain cid) (Nonce 0) -- | Like `genesisBlockHeader`, but with slightly more control. -- @@ -706,7 +706,7 @@ makeGenesisBlockHeader' v p ct@(BlockCreationTime t) n = $ mkFeatureFlags :+: ct :+: genesisParentBlockHash v cid - :+: (v ^?! versionGenesis . genesisBlockTarget . onChain cid) + :+: (v ^?! versionGenesis . genesisBlockTarget . atChain cid) :+: genesisBlockPayloadHash v cid :+: cid :+: BlockWeight 0 @@ -805,7 +805,7 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag BlockHeader wh :+: nonce :+: MerkleLogBody adjParents ) = _merkleLogEntries l - cwv = _chainwebVersion cwvc + cwv = lookupVersionByCode cwvc adjGraph | height == genesisBlockHeight cwv cid = chainGraphAt cwv height diff --git a/src/Chainweb/BlockHeader/Validation.hs b/src/Chainweb/BlockHeader/Validation.hs index 50f46c7524..1dbb06d230 100644 --- a/src/Chainweb/BlockHeader/Validation.hs +++ b/src/Chainweb/BlockHeader/Validation.hs @@ -693,7 +693,7 @@ prop_block_genesis_parent b prop_block_genesis_target :: BlockHeader -> Bool prop_block_genesis_target b = isGenesisBlockHeader b - ==> view blockTarget b == _chainwebVersion b ^?! versionGenesis . genesisBlockTarget . onChain (_chainId b) + ==> view blockTarget b == _chainwebVersion b ^?! versionGenesis . genesisBlockTarget . atChain (_chainId b) prop_block_current :: Time Micros -> BlockHeader -> Bool prop_block_current t b = BlockCreationTime t >= view blockCreationTime b diff --git a/src/Chainweb/BlockHeaderDB/RestAPI.hs b/src/Chainweb/BlockHeaderDB/RestAPI.hs index 9bfe9c351a..3892b62eec 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI.hs @@ -49,7 +49,6 @@ module Chainweb.BlockHeaderDB.RestAPI -- * API types BlockHashPage , BlockHeaderPage -, Block(..) , BlockPage -- * Encodings @@ -101,6 +100,7 @@ import Network.HTTP.Media ((//), (/:)) import Servant.API -- internal modules +import Chainweb.Block import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeaderDB @@ -120,12 +120,6 @@ type BlockHashPage = Page (NextItem BlockHash) BlockHash type BlockHeaderPage = Page (NextItem BlockHash) BlockHeader -data Block = Block - { _blockHeader :: !BlockHeader - , _blockPayloadWithOutputs :: !PayloadWithOutputs - } - deriving (Eq, Show) - -- because this endpoint is only used on the service API, we assume clients -- want object-encoded block headers. blockProperties :: KeyValue e kv => Block -> [kv] diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index 1ddc3c1085..7bda07fa04 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -76,6 +76,7 @@ import Chainweb.RestAPI.Utils import Chainweb.TreeDB import Chainweb.Utils.Paging import Chainweb.Version +import Chainweb.Block -- -------------------------------------------------------------------------- -- -- Handler Tools diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index 473ee6ec73..6f9839f7f9 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -59,8 +59,9 @@ module Chainweb.ChainId -- * Mapping from chain IDs to values , ChainMap(..) -, onChain +, atChain , onChains +, onChain , chainZip ) where @@ -276,6 +277,10 @@ data ChainMap a = AllChains a | OnChains (HashMap ChainId a) onChains :: [(ChainId, a)] -> ChainMap a onChains = OnChains . HM.fromList +-- | A smart constructor, @onChain c a = OnChains (HM.singleton c a)@. +onChain :: ChainId -> a -> ChainMap a +onChain c a = OnChains (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 @@ -296,7 +301,7 @@ instance FromJSON a => FromJSON (ChainMap a) where makePrisms ''ChainMap -- | Provides access to the value at a `ChainId`, if it exists. -onChain :: ChainId -> Fold (ChainMap a) a -onChain cid = folding $ \case +atChain :: ChainId -> Fold (ChainMap a) a +atChain cid = folding $ \case OnChains m -> m ^. at cid AllChains a -> Just a diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 71a2dc7465..0dc8cdc7ef 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -72,8 +72,7 @@ module Chainweb.Chainweb , NowServing(..) -- ** Mempool integration -, ChainwebTransaction -, Mempool.chainwebTransactionConfig +, Mempool.pact4TransactionConfig , validatingMempoolConfig , withChainweb @@ -159,15 +158,15 @@ import qualified Chainweb.Mempool.Mempool as Mempool import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Config import qualified Chainweb.OpenAPIValidation as OpenAPIValidation +import Chainweb.Pact.Backend.Types(IntraBlockPersistence(..)) import Chainweb.Pact.RestAPI.Server (PactServerData(..)) -import Chainweb.Pact.Service.Types (PactServiceConfig(..)) -import Chainweb.Pact.Backend.Types (IntraBlockPersistence(..)) -import Chainweb.Pact.Validations +import Chainweb.Pact.Types (PactServiceConfig(..)) +import Chainweb.Pact4.Validations import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.RocksDB import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils import Chainweb.Utils.RequestLog import Chainweb.Version @@ -270,7 +269,7 @@ validatingMempoolConfig -> Mempool.GasLimit -> Mempool.GasPrice -> MVar PactExecutionService - -> Mempool.InMemConfig ChainwebTransaction + -> Mempool.InMemConfig Pact4.UnparsedTransaction validatingMempoolConfig cid v gl gp mv = Mempool.InMemConfig { Mempool._inmemTxCfg = txcfg , Mempool._inmemTxBlockSizeLimit = gl @@ -281,7 +280,7 @@ validatingMempoolConfig cid v gl gp mv = Mempool.InMemConfig , Mempool._inmemCurrentTxsSize = currentTxsSize } where - txcfg = Mempool.chainwebTransactionConfig (maxBound :: PactParserVersion) + 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. @@ -294,9 +293,9 @@ validatingMempoolConfig cid v gl gp mv = Mempool.InMemConfig -- | Validation: Is this TX associated with the correct `ChainId`? -- - preInsertSingle :: ChainwebTransaction -> Either Mempool.InsertError ChainwebTransaction + preInsertSingle :: Pact4.UnparsedTransaction -> Either Mempool.InsertError Pact4.UnparsedTransaction preInsertSingle tx = do - let !pay = payloadObj . P._cmdPayload $ tx + let !pay = Pact4.payloadObj . P._cmdPayload $ tx pcid = P._pmChainId $ P._pMeta pay sigs = P._cmdSigs tx ver = P._pNetworkId pay @@ -316,9 +315,9 @@ validatingMempoolConfig cid v gl gp mv = Mempool.InMemConfig -- is gossiped to us from a peer's mempool. -- preInsertBatch - :: V.Vector (T2 Mempool.TransactionHash ChainwebTransaction) + :: V.Vector (T2 Mempool.TransactionHash Pact4.UnparsedTransaction) -> IO (V.Vector (Either (T2 Mempool.TransactionHash Mempool.InsertError) - (T2 Mempool.TransactionHash ChainwebTransaction))) + (T2 Mempool.TransactionHash Pact4.UnparsedTransaction))) preInsertBatch txs | V.null txs = return V.empty | otherwise = do @@ -327,8 +326,8 @@ validatingMempoolConfig cid v gl gp mv = Mempool.InMemConfig pure $ alignWithV f rs txs where f (These r (T2 h t)) = case r of - Left e -> Left (T2 h e) - Right _ -> Right (T2 h t) + 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")) @@ -439,7 +438,7 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re , _pactUnlimitedInitialRewind = isJust (_cutDbParamsInitialHeightLimit cutConfig) || isJust (_cutDbParamsInitialCutFile cutConfig) - , _pactBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) + , _pactNewBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) , _pactLogGas = _configLogGas conf , _pactModuleCacheLimit = _configModuleCacheLimit conf , _pactEnableLocalTimeout = _configEnableLocalTimeout conf @@ -448,6 +447,7 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re if _configFullHistoricPactState conf then PersistIntraBlockWrites else DoNotPersistIntraBlockWrites + , _pactTxTimeLimit = Nothing } pruningLogger :: T.Text -> logger @@ -773,7 +773,7 @@ runChainweb cw nowServing = do chainDbsToServe :: [(ChainId, BlockHeaderDb)] chainDbsToServe = proj _chainResBlockHeaderDb - mempoolsToServe :: [(ChainId, Mempool.MempoolBackend ChainwebTransaction)] + mempoolsToServe :: [(ChainId, Mempool.MempoolBackend Pact4.UnparsedTransaction)] mempoolsToServe = proj _chainResMempool peerDb = _peerResDb (_chainwebPeer cw) diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 94d120f32c..02573d1b1c 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -47,9 +47,9 @@ 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.Service.Types +import Chainweb.Pact.Types import Chainweb.Payload.PayloadStore -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Version import Chainweb.WebPactExecutionService @@ -62,7 +62,7 @@ import Chainweb.Counter data ChainResources logger = ChainResources { _chainResBlockHeaderDb :: !BlockHeaderDb , _chainResLogger :: !logger - , _chainResMempool :: !(MempoolBackend ChainwebTransaction) + , _chainResMempool :: !(MempoolBackend Pact4.UnparsedTransaction) , _chainResPact :: PactExecutionService } @@ -85,7 +85,7 @@ withChainResources -> ChainId -> RocksDb -> logger - -> (MVar PactExecutionService -> Mempool.InMemConfig ChainwebTransaction) + -> (MVar PactExecutionService -> Mempool.InMemConfig Pact4.UnparsedTransaction) -> PayloadDb tbl -> FilePath -- ^ database directory for checkpointer @@ -98,7 +98,7 @@ withChainResources withBlockHeaderDb rdb v cid $ \cdb -> do pexMv <- newEmptyMVar let mempoolCfg = mempoolCfg0 pexMv - Mempool.withInMemoryMempool_ (setComponent "mempool" logger) mempoolCfg v $ \mempool -> do + 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 diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 3810f4ce2d..90b2009f15 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -114,11 +114,12 @@ 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.Service.Types (RewindLimit(..)) +import Chainweb.Pact.Types (RewindLimit(..)) import Chainweb.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Development +import Chainweb.Version.Pact5Development import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.Registry @@ -126,6 +127,7 @@ import Chainweb.Time import P2P.Node.Configuration import Chainweb.Pact.Backend.DbCache (DbCacheLimitBytes) +import Chainweb.Version.Testnet04 -- -------------------------------------------------------------------------- -- -- Throttling Configuration @@ -457,14 +459,22 @@ validateRosetta c = do throwError "To enable the Rosetta construction API, Rosetta must be enabled, too." validateChainwebVersion :: ConfigValidation ChainwebVersion [] -validateChainwebVersion v = unless (isDevelopment || elem v knownVersions) $ - throwError $ T.unwords - [ "Specifying version properties is only legal with chainweb-version" - , "set to recap-development or development, but version is set to" - , sshow (_versionName v) - ] +validateChainwebVersion v = do + unless (isDevelopment || elem v knownVersions) $ + throwError $ T.unwords + [ "Specifying version properties is only legal with chainweb-version" + , "set to recap-development or development, but version is set to" + , sshow (_versionName v) + ] + -- FIXME Pact5: disable + when (v == mainnet || v == testnet04) $ + throwError $ T.unwords + [ "This node version is a technical preview of Pact 5, and" + , "cannot be used with Pact 4 chainweb versions (testnet04, mainnet)" + , "just yet." + ] where - isDevelopment = _versionCode v `elem` [_versionCode dv | dv <- [recapDevnet, devnet]] + isDevelopment = _versionCode v `elem` [_versionCode dv | dv <- [recapDevnet, devnet, pact5Devnet]] validateBackupConfig :: ConfigValidation BackupConfig [] validateBackupConfig c = @@ -649,10 +659,10 @@ parseVersion = constructVersion maybe (_versionUpgrades winningVersion) (\fub' -> OnChains $ HM.mapWithKey (\cid _ -> - case winningVersion ^?! versionForks . at fub' . _Just . onChain cid of + case winningVersion ^?! versionForks . at fub' . _Just . atChain cid of ForkNever -> error "Chainweb.Chainweb.Configuration.parseVersion: the fork upper bound never occurs in this version." - ForkAtBlockHeight fubHeight -> HM.filterWithKey (\bh _ -> bh <= fubHeight) (winningVersion ^?! versionUpgrades . onChain cid) - ForkAtGenesis -> winningVersion ^?! versionUpgrades . onChain cid + ForkAtBlockHeight fubHeight -> HM.filterWithKey (\bh _ -> bh <= fubHeight) (winningVersion ^?! versionUpgrades . atChain cid) + ForkAtGenesis -> winningVersion ^?! versionUpgrades . atChain cid ) (HS.toMap (chainIds winningVersion)) ) fub diff --git a/src/Chainweb/Chainweb/MinerResources.hs b/src/Chainweb/Chainweb/MinerResources.hs index ed9d517c31..3602220286 100644 --- a/src/Chainweb/Chainweb/MinerResources.hs +++ b/src/Chainweb/Chainweb/MinerResources.hs @@ -62,7 +62,7 @@ import Chainweb.Miner.Config import Chainweb.Miner.Coordinator import Chainweb.Miner.Miners import Chainweb.Miner.Pact (Miner(..), minerId) -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Pact.Utils import Chainweb.Payload import Chainweb.Payload.PayloadStore @@ -149,12 +149,14 @@ withMiningCoordination logger conf cdb inner case primed of WorkReady (NewBlockInProgress bip) -> return (Just bip) WorkReady (NewBlockPayload {}) -> - error "periodicallyRefreshPayload: encountered NewBlockPayload in PrimedWork, which cannot be refreshed" + error "periodicallyRefreshPayload: encountered NewBlockPayload in PrimedWork, which cannot be refreshed" WorkAlreadyMined {} -> return Nothing WorkStale -> return Nothing forM_ mContinuableBlockInProgress $ \continuableBlockInProgress -> do - maybeNewBlock <- _pactContinueBlock pact cid continuableBlockInProgress + 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. @@ -164,7 +166,10 @@ withMiningCoordination logger conf cdb inner logFunctionText (chainLogger cid logger) Debug $ "refreshed block, old and new tx count: " - <> sshow (V.length $ _transactionPairs $ _blockInProgressTransactions continuableBlockInProgress, V.length $ _transactionPairs $ _blockInProgressTransactions newBlock) + <> sshow + ( forAnyPactVersion (V.length . _transactionPairs . _blockInProgressTransactions) continuableBlockInProgress + , forAnyPactVersion (V.length . _transactionPairs . _blockInProgressTransactions) newBlock + ) atomically $ modifyTVar' tpw $ workForMiner ourMiner cid .~ WorkReady (NewBlockInProgress newBlock) @@ -177,7 +182,7 @@ withMiningCoordination logger conf cdb inner primeWork tpw cid = forConcurrently_ miners $ \miner -> runForever (logFunction (chainLogger cid logger)) "primeWork" (go miner) - where + where go :: Miner -> IO () go miner = do pw <- readTVarIO tpw @@ -187,9 +192,9 @@ withMiningCoordination logger conf cdb inner ourMiner = workForMiner miner cid let !outdatedPayload = fromJuste $ pw ^? ourMiner let outdatedParentHash = case outdatedPayload of - WorkReady outdatedBlock -> view blockHash (_parentHeader (newBlockParentHeader outdatedBlock)) - WorkAlreadyMined outdatedBlockHash -> outdatedBlockHash - WorkStale -> error "primeWork loop: Invariant Violation: Stale work should be an impossibility" + 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 diff --git a/src/Chainweb/Mempool/Consensus.hs b/src/Chainweb/Mempool/Consensus.hs index bd306099cf..12d2c22336 100644 --- a/src/Chainweb/Mempool/Consensus.hs +++ b/src/Chainweb/Mempool/Consensus.hs @@ -47,20 +47,20 @@ import Chainweb.Mempool.Mempool import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.TreeDB import Chainweb.Utils -import Chainweb.Version -import Chainweb.Version.Guards import Data.LogMessage (JsonLog(..), LogFunction) +import qualified Pact.Types.ChainMeta as Pact4 +import Data.Text (Text) ------------------------------------------------------------------------------ data MempoolConsensus = MempoolConsensus - { mpcMempool :: !(MempoolBackend ChainwebTransaction) + { mpcMempool :: !(MempoolBackend Pact4.UnparsedTransaction) , mpcLastNewBlockParent :: !(IORef (Maybe BlockHeader)) , mpcProcessFork - :: LogFunction -> BlockHeader -> IO (Vector ChainwebTransaction, Vector ChainwebTransaction) + :: LogFunction -> BlockHeader -> IO (Vector Pact4.UnparsedTransaction, Vector Pact4.UnparsedTransaction) } data ReintroducedTxsLog = ReintroducedTxsLog @@ -81,7 +81,7 @@ instance Exception MempoolException ------------------------------------------------------------------------------ mkMempoolConsensus :: CanReadablePayloadCas tbl - => MempoolBackend ChainwebTransaction + => MempoolBackend Pact4.UnparsedTransaction -> BlockHeaderDb -> Maybe (PayloadDb tbl) -> IO MempoolConsensus @@ -103,27 +103,23 @@ processFork -> IORef (Maybe BlockHeader) -> LogFunction -> BlockHeader - -> IO (Vector ChainwebTransaction, Vector ChainwebTransaction) + -> IO (Vector Pact4.UnparsedTransaction, Vector Pact4.UnparsedTransaction) processFork blockHeaderDb payloadStore lastHeaderRef logFun newHeader = do now <- getCurrentTimeIntegral lastHeader <- readIORef lastHeaderRef - let v = _chainwebVersion blockHeaderDb - cid = _chainId blockHeaderDb - height = view blockHeight newHeader (a, b) <- processFork' logFun blockHeaderDb newHeader lastHeader (payloadLookup payloadStore) - (processForkCheckTTL (pactParserVersion v cid height) now) - return (V.map unHashable a, V.map unHashable b) + (processForkCheckTTL now) + return (V.map Pact4.unHashable a, V.map Pact4.unHashable b) ------------------------------------------------------------------------------ processForkCheckTTL - :: PactParserVersion - -> Time Micros - -> HashableTrans PayloadWithText -> Bool -processForkCheckTTL ppv now (HashableTrans t) = + :: Time Micros + -> Pact4.HashableTrans (Pact4.PayloadWithText Pact4.PublicMeta Text) -> Bool +processForkCheckTTL now (Pact4.HashableTrans t) = either (const False) (const True) $ - txTTLCheck (chainwebTransactionConfig ppv) now t + txTTLCheck pact4TransactionConfig now t ------------------------------------------------------------------------------ @@ -172,27 +168,26 @@ payloadLookup :: CanReadablePayloadCas tbl => Maybe (PayloadDb tbl) -> BlockHeader - -> IO (HashSet (HashableTrans PayloadWithText)) + -> IO (HashSet (Pact4.HashableTrans (Pact4.PayloadWithText Pact4.PublicMeta Text))) 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 (pactParserVersion (_chainwebVersion bh) (_chainId bh) (view blockHeight bh)) pd' + chainwebTxsFromPd pd' ------------------------------------------------------------------------------ chainwebTxsFromPd - :: PactParserVersion - -> PayloadData - -> IO (HashSet (HashableTrans PayloadWithText)) -chainwebTxsFromPd ppv pd = do + :: PayloadData + -> IO (HashSet (Pact4.HashableTrans (Pact4.PayloadWithText Pact4.PublicMeta Text))) +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 $ HashableTrans <$!> theRights + let theRights = rights $ toList eithers + return $! HS.fromList $ Pact4.HashableTrans <$!> theRights where - toCWTransaction = codecDecode (chainwebPayloadCodec ppv) + toCWTransaction = codecDecode Pact4.rawCommandCodec diff --git a/src/Chainweb/Mempool/CurrentTxs.hs b/src/Chainweb/Mempool/CurrentTxs.hs index aa9a47f039..a5e1a0c43f 100644 --- a/src/Chainweb/Mempool/CurrentTxs.hs +++ b/src/Chainweb/Mempool/CurrentTxs.hs @@ -25,7 +25,7 @@ -- block. -- module Chainweb.Mempool.CurrentTxs -( CurrentTxs +( CurrentTxs(..) , newCurrentTxs , currentTxsSize , currentTxsInsert diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 6f35df1aa5..9f2f7f62f9 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -14,8 +14,8 @@ module Chainweb.Mempool.InMem ( -- * Initialization functions - withInMemoryMempool - , withInMemoryMempool_ + startInMemoryMempoolTest + , withInMemoryMempool -- * Low-level create/destroy functions , makeInMemPool @@ -30,11 +30,9 @@ import Control.Concurrent.Async import Control.Concurrent.MVar import Control.DeepSeq import Control.Error.Util (hush) -import Control.Exception (bracket, evaluate, mask_, throw) +import Control.Exception (evaluate, mask_, throw) import Control.Monad -import Data.Aeson -import Data.Bifunctor (bimap) import qualified Data.ByteString.Short as SB import Data.Decimal #if MIN_VERSION_base(4,20,0) @@ -71,7 +69,7 @@ import Chainweb.Logger import Chainweb.Mempool.CurrentTxs import Chainweb.Mempool.InMemTypes import Chainweb.Mempool.Mempool -import Chainweb.Pact.Validations (defaultMaxTTL, defaultMaxCoinDecimalPlaces) +import Chainweb.Pact4.Validations (defaultMaxTTL, defaultMaxCoinDecimalPlaces) import Chainweb.Time import Chainweb.Utils import Chainweb.Version (ChainwebVersion) @@ -80,6 +78,8 @@ import qualified Pact.Types.ChainMeta as P import Numeric.AffineSpace import Data.ByteString (ByteString) +import Data.Either (partitionEithers) +import Control.Lens ------------------------------------------------------------------------------ compareOnGasPrice :: TransactionConfig t -> t -> t -> Ordering @@ -98,9 +98,6 @@ makeInMemPool cfg = mask_ $ do dataLock <- newInMemMempoolData >>= newMVar return $! InMemoryMempool cfg dataLock nonce -destroyInMemPool :: InMemoryMempool t -> IO () -destroyInMemPool = const $ return () - ------------------------------------------------------------------------------ newInMemMempoolData :: IO (InMemoryMempoolData t) @@ -113,7 +110,8 @@ newInMemMempoolData = ------------------------------------------------------------------------------ toMempoolBackend - :: NFData t + :: forall t logger + . NFData t => Logger logger => logger -> InMemoryMempool t @@ -143,48 +141,50 @@ toMempoolBackend logger mempool = do member = memberInMem lockMVar lookup = lookupInMem tcfg lockMVar lookupEncoded = lookupEncodedInMem lockMVar - insert = insertInMem cfg lockMVar + insert = insertInMem logger cfg lockMVar insertCheck = insertCheckInMem cfg lockMVar markValidated = markValidatedInMem logger tcfg lockMVar addToBadList = addToBadListInMem lockMVar checkBadList = checkBadListInMem lockMVar + getBlock :: forall to. + (NFData t) + => BlockFill + -> MempoolPreBlockCheck t to + -> BlockHeight + -> BlockHash + -> IO (Vector to) getBlock = getBlockInMem logger cfg lockMVar getPending = getPendingInMem cfg nonce lockMVar - prune = pruneInMem lockMVar + prune = pruneInMem logger lockMVar clear = clearInMem lockMVar ------------------------------------------------------------------------------ -- | A 'bracket' function for in-memory mempools. -- --- This function is only used in testing. Use 'withInMemoryMempool_' for +-- This function is only used in testing. Use 'withInMemoryMempool' for -- production. -- -withInMemoryMempool :: ToJSON t - => FromJSON t - => NFData t - => InMemConfig t - -> ChainwebVersion - -> (MempoolBackend t -> IO a) - -> IO a -withInMemoryMempool cfg _v f = do - let action inMem = do - back <- toMempoolBackend l inMem - f $! back - bracket (makeInMemPool cfg) destroyInMemPool action +startInMemoryMempoolTest + :: NFData t + => InMemConfig t + -> IO (MempoolBackend t) +startInMemoryMempoolTest cfg = do + toMempoolBackend l =<< makeInMemPool cfg where l = genericLogger Debug (\ _ -> return ()) -- | A 'bracket' function for in-memory mempools. -- -withInMemoryMempool_ :: Logger logger - => NFData t - => logger - -> InMemConfig t - -> ChainwebVersion - -> (MempoolBackend t -> IO a) - -> IO a -withInMemoryMempool_ l cfg _v f = do +withInMemoryMempool + :: Logger logger + => NFData t + => 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 @@ -192,12 +192,12 @@ withInMemoryMempool_ l cfg _v f = do case r of Left () -> throw $ InternalInvariantViolation "mempool monitor exited unexpectedly" Right result -> return result - bracket (makeInMemPool cfg) destroyInMemPool action + action =<< makeInMemPool cfg where 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.Mempool.InMem.withInMemoryMempool.monitor" 10 (10 * mega) $ do stats <- getMempoolStats m logFunctionJson l Info stats approximateThreadDelay 60_000_000 {- 1 minute -} @@ -475,20 +475,23 @@ insertCheckInMem' cfg lock txs hasher = txHasher txcfg insertInMem - :: forall t - . NFData t - => InMemConfig t -- ^ in-memory config + :: forall t logger. (NFData t, Logger logger) + => logger + -> InMemConfig t -- ^ in-memory config -> MVar (InMemoryMempoolData t) -- ^ in-memory state -> InsertType -> Vector t -- ^ new transactions -> IO () -insertInMem cfg lock runCheck txs0 = do +insertInMem logger cfg lock runCheck txs0 = do + logFunctionText logger Debug $ "insertInMem: " <> sshow (runCheck, V.length txs0) txhashes <- insertCheck withMVarMasked lock $ \mdata -> do pending <- readIORef (_inmemPending mdata) + logFunctionText logger Debug $ "insertInMem: pending txs: " <> sshow (HashMap.keys pending) let cnt = HashMap.size pending let txs = V.take (max 0 (maxNumPending - cnt)) txhashes let T2 !pending' !newHashesDL = V.foldl' insOne (T2 pending id) txs + logFunctionText logger Debug $ "insertInMem: updated pending txs: " <> sshow (HashMap.keys pending') let !newHashes = V.fromList $ newHashesDL [] writeIORef (_inmemPending mdata) $! force pending' modifyIORef' (_inmemRecentLog mdata) $ @@ -515,25 +518,35 @@ insertInMem cfg lock runCheck txs0 = do ------------------------------------------------------------------------------ getBlockInMem - :: forall t . NFData t - => forall l . Logger l + :: forall t l to. + (NFData t, Logger l) => l -> InMemConfig t -> MVar (InMemoryMempoolData t) -> BlockFill - -> MempoolPreBlockCheck t + -> MempoolPreBlockCheck t to -> BlockHeight -> BlockHash - -> IO (Vector t) -getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight phash = do + -> IO (Vector to) +getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight phash = do logFunctionText logg Debug $ "getBlockInMem: " <> sshow (gasLimit,bheight,phash) withMVar lock $ \mdata -> do now <- getCurrentTimeIntegral + pendingDataBeforePrune <- readIORef (_inmemPending mdata) + logFunctionText logg Debug $ "getBlockInMem: data before prune" <> sshow (HashMap.keys pendingDataBeforePrune) + -- drop any expired transactions. - pruneInternal mdata now + pruneInternal logg mdata now + + pendingData <- readIORef (_inmemPending mdata) + logFunctionText logg Debug $ "getBlockInMem: pending txs (pre filter-seen): " <> sshow (HashMap.keys pendingData) + !(T2 psq seen) <- filterSeen <$> readIORef (_inmemPending mdata) + logFunctionText logg Debug $ "getBlockInMem: pending txs (post filter-seen): " <> sshow (HashMap.keys psq) + logFunctionText logg Debug $ "getBlockInMem: seen txs: " <> sshow (HashMap.keys seen) !badmap <- readIORef (_inmemBadMap mdata) + logFunctionText logg Debug $ "getBlockInMem: bad txs: " <> sshow (HashMap.keys badmap) let size0 = gasLimit -- get our batch of output transactions, along with a new pending map @@ -545,24 +558,28 @@ getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight let !psq'' = V.foldl' ins (HashMap.union seen psq') out writeIORef (_inmemPending mdata) $! force psq'' writeIORef (_inmemBadMap mdata) $! force badmap' - mout <- V.thaw $ V.map (snd . snd) out - TimSort.sortBy (compareOnGasPrice txcfg) mout - V.unsafeFreeze mout + mout <- V.thaw $ V.map (\(_, (_, t, tOut)) -> (t, tOut)) out + TimSort.sortBy (compareOnGasPrice txcfg `on` fst) mout + fmap snd <$> V.unsafeFreeze mout where - filterSeen = (`HashMap.foldlWithKey'` (T2 mempty mempty)) $ \(T2 unseens seens) k v -> - if S.member k txHashes then (T2 unseens (HashMap.insert k v seens)) - else (T2 (HashMap.insert k v unseens) seens) + filterSeen :: PendingMap -> T2 PendingMap (HashMap TransactionHash PendingEntry) + filterSeen p = HashMap.foldlWithKey' loop (T2 mempty mempty) p + where + loop (T2 unseens seens) k v = + if S.member k txHashes + then T2 unseens (HashMap.insert k v seens) + else T2 (HashMap.insert k v unseens) seens - ins !m (!h,(!b,!t)) = + ins !m (!h,(!b,!t,_)) = let !pe = PendingEntry (txGasPrice txcfg t) (txGasLimit txcfg t) b (txMetaExpiryTime $ txMetadata txcfg t) in HashMap.insert h pe m - insBadMap !m (!h,(_,!t)) = let endTime = txMetaExpiryTime (txMetadata txcfg t) + insBadMap !m (!h,!t) = let endTime = txMetaExpiryTime (txMetadata txcfg t) in HashMap.insert h endTime m del !psq (h, _) = HashMap.delete h psq @@ -580,27 +597,32 @@ getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight ] getSize = txGasLimit txcfg maxSize = _inmemTxBlockSizeLimit cfg - sizeOK tx = getSize tx <= maxSize + sizeOK tx = when (getSize tx > maxSize) (Left $ InsertErrorOversized maxSize) validateBatch :: PendingMap -> BadMap -> Vector (TransactionHash, (SB.ShortByteString, t)) - -> IO (T3 (Vector (TransactionHash, (SB.ShortByteString, t))) + -> IO (T3 [(TransactionHash, (SB.ShortByteString, t, to))] PendingMap BadMap) validateBatch !psq0 !badmap q = do let txs = V.map (snd . snd) q oks1 <- txValidate bheight phash txs let oks2 = V.map sizeOK txs - let !oks = V.zipWith (&&) oks1 oks2 - let (good, bad1) = V.partition snd $! V.zip q oks + let !oks = V.zipWith (\ok1 ok2 -> ok1 <* ok2) oks1 oks2 + let (bad1, good) = + partitionEithers + [ either (\_err -> Left (txHash, t)) (\tOut -> Right (txHash, (bytes, t, tOut))) r + | ((txHash, (bytes, t)), r) <- V.toList (V.zip q oks) + ] + logFunctionText logg Debug $ "validateBatch badlisting: " <> sshow (map fst bad1) -- remove considered txs -- successful ones will be re-added at the end let !psq' = V.foldl' del psq0 q -- txs that fail pre-block validation get sent to the naughty list. - let !badmap' = V.foldl' insBadMap badmap (V.map fst bad1) - return $! T3 (V.map fst good) psq' badmap' + let !badmap' = foldl' insBadMap badmap bad1 + return $! T3 good psq' badmap' maxInARow :: Int maxInARow = 200 @@ -613,6 +635,7 @@ getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight -> IO [(TransactionHash, (SB.ShortByteString, t))] nextBatch !psq !remainingGas = do let !pendingTxs0 = HashMap.toList psq + logFunctionText logg Debug $ "nextBatch pendingTxs: " <> sshow (map fst pendingTxs0) mPendingTxs <- mutableVectorFromList pendingTxs0 TimSort.sortBy (compare `on` snd) mPendingTxs !pendingTxs <- V.unsafeFreeze mPendingTxs @@ -635,22 +658,24 @@ getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight let !tx = decodeTx txbytes let !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' (sz - txSz) ((h,(txbytes, tx)):soFar) 0 + else getBatch pendingTxs' sz soFar (inARow + 1) go :: PendingMap - -> BadMap - -> GasLimit - -> [Vector (TransactionHash, (SB.ShortByteString, t))] - -> IO (T3 PendingMap BadMap - (Vector (TransactionHash, (SB.ShortByteString, t)))) + -> 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 if null nb - then return $! T3 psq badmap (V.concat soFar) + then do + logFunctionText logg Debug "getBlockInMem: Batch empty" + return $! T3 psq badmap (V.fromList $ concat soFar) else do + logFunctionText logg Debug "validating batch..." T3 good psq' badmap' <- validateBatch psq badmap $! V.fromList nb - let !newGas = V.foldl' (\s (_, (_, t)) -> s + getSize t) 0 good + let !newGas = foldl' (\s (_, (_, t, _)) -> s + getSize t) 0 good go psq' badmap' (remainingGas - newGas) (good : soFar) @@ -756,31 +781,41 @@ getMempoolStats m = do -- | Prune the mempool's pending map and badmap. -- -- Complexity is linear in the size of the mempool, which is fine if it isn't --- applied to often and at a constant rate. +-- applied too often and at a constant rate. -- pruneInMem - :: forall t . NFData t - => MVar (InMemoryMempoolData t) + :: forall t logger. (NFData t, Logger logger) + => logger + -> MVar (InMemoryMempoolData t) -> IO () -pruneInMem lock = do +pruneInMem logger lock = do now <- getCurrentTimeIntegral - withMVar lock $ \mdata -> pruneInternal mdata now + withMVar lock $ \mdata -> pruneInternal logger mdata now ------------------------------------------------------------------------------ pruneInternal - :: forall t . NFData t - => InMemoryMempoolData t + :: forall t logger. (NFData t, Logger logger) + => logger + -> InMemoryMempoolData t -> Time Micros -> IO () -pruneInternal mdata now = do +pruneInternal logger mdata now = do let pref = _inmemPending mdata !pending <- readIORef pref + logFunctionText logger Debug $ "pruneInternal: pending txs pre-filter: " <> sshow (HashMap.keys pending) + logFunctionText logger Debug $ "pruneInternal: current time (micros): " <> sshow now + forM_ (HashMap.toList pending) $ \(h, pe) -> + logFunctionText logger Debug $ "pruneInternal: (pending tx, expiration): " <> sshow (h, _inmemPeExpires pe) !pending' <- evaluate $ force $ HashMap.filter flt pending + logFunctionText logger Debug $ "pruneInternal: pending txs post-filter: " <> sshow (HashMap.keys pending') writeIORef pref pending' let bref = _inmemBadMap mdata + badMapBefore <- readIORef bref + logFunctionText logger Debug $ "pruneInternal: bad txs before prune: " <> sshow (HashMap.keys badMapBefore) !badmap <- (force . pruneBadMap) <$!> readIORef bref + logFunctionText logger Debug $ "pruneInternal: bad txs after prune: " <> sshow (HashMap.keys badMapBefore) writeIORef bref badmap where diff --git a/src/Chainweb/Mempool/InMemTypes.hs b/src/Chainweb/Mempool/InMemTypes.hs index faac2b98a8..e95fb936e8 100644 --- a/src/Chainweb/Mempool/InMemTypes.hs +++ b/src/Chainweb/Mempool/InMemTypes.hs @@ -102,8 +102,8 @@ data InMemoryMempoolData t = InMemoryMempoolData { -- possibly have to pay gas for it several times. , _inmemCurrentTxs :: !(IORef CurrentTxs) - -- ^ The set of non-expired transactions that have been addeded to a block. - -- Transactions are remove from the set of pending transactions when they + -- ^ The set of non-expired transactions that have been added to a block. + -- Transactions are removed from the set of pending transactions when they -- are added to a block. This set is used to prevent transactions from being -- re-inserts when synchronizing with nodes that haven't yet validated the -- block. diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Mempool/Mempool.hs index 2bbb3b7bfb..963a1eeb23 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Mempool/Mempool.hs @@ -74,7 +74,7 @@ module Chainweb.Mempool.Mempool , bfTxHashes , bfCount - , chainwebTransactionConfig + , pact4TransactionConfig , mockCodec , mockEncode , mockBlockGasLimit @@ -86,7 +86,8 @@ module Chainweb.Mempool.Mempool , syncMempools' , GasLimit(..) , GasPrice(..) - , requestKeyToTransactionHash + , pact4RequestKeyToTransactionHash + , pact5RequestKeyToTransactionHash ) where ------------------------------------------------------------------------------ import Control.DeepSeq (NFData) @@ -132,16 +133,19 @@ 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 H +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 Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 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 ------------------------------------------------------------------------------ data LookupResult t = Missing @@ -182,7 +186,7 @@ instance Traversable LookupResult where Pending x -> Pending <$> f x ------------------------------------------------------------------------------ -type MempoolPreBlockCheck t = BlockHeight -> BlockHash -> Vector t -> IO (Vector Bool) +type MempoolPreBlockCheck ti to = BlockHeight -> BlockHash -> Vector ti -> IO (Vector (Either InsertError to)) ------------------------------------------------------------------------------ -- | Mempool operates over a transaction type @t@. Mempool needs several @@ -218,43 +222,43 @@ type HighwaterMark = (ServerNonce, MempoolTxId) data InsertType = CheckedInsert | UncheckedInsert deriving (Show, Eq) -data InsertError = InsertErrorDuplicate - | InsertErrorTTLExpired - | InsertErrorTimeInFuture - | InsertErrorOversized GasLimit - | InsertErrorUndersized - GasPrice -- actual gas price - GasPrice -- minimum gas price - | InsertErrorBadlisted - | InsertErrorMetadataMismatch - | InsertErrorTransactionsDisabled - | InsertErrorBuyGas Text - | InsertErrorCompilationFailed Text - | InsertErrorOther Text - | InsertErrorInvalidHash - | InsertErrorInvalidSigs - | InsertErrorTimedOut +data InsertError + = InsertErrorDuplicate + | InsertErrorTTLExpired + | InsertErrorTimeInFuture + | InsertErrorOversized GasLimit + | InsertErrorUndersized + GasPrice -- actual gas price + GasPrice -- minimum gas price + | InsertErrorBadlisted + | InsertErrorMetadataMismatch + | InsertErrorTransactionsDisabled + | InsertErrorBuyGas Text + | InsertErrorCompilationFailed Text + | InsertErrorOther Text + | InsertErrorInvalidHash + | InsertErrorInvalidSigs + | InsertErrorTimedOut + | InsertErrorPactParseError Text deriving (Generic, Eq, NFData) -instance Show InsertError - where - show InsertErrorDuplicate = "Transaction already exists on chain" - show InsertErrorTTLExpired = "Transaction time-to-live is expired" - show InsertErrorTimeInFuture = "Transaction creation time too far in the future" - show (InsertErrorOversized (GasLimit l)) = "Transaction gas limit exceeds block gas limit (" <> show l <> ")" - show (InsertErrorUndersized (GasPrice p) (GasPrice m)) = "Transaction gas price (" <> show p <> ") is below minimum gas price (" <> show m <> ")" - show InsertErrorBadlisted = - "Transaction is badlisted because it previously failed to validate." - show InsertErrorMetadataMismatch = - "Transaction metadata (chain id, chainweb version) conflicts with this \ - \endpoint" - show InsertErrorTransactionsDisabled = "Transactions are disabled until 2019 Dec 5" - show (InsertErrorBuyGas msg) = "Attempt to buy gas failed with: " <> T.unpack msg - show (InsertErrorCompilationFailed msg) = "Transaction compilation failed: " <> T.unpack msg - show (InsertErrorOther m) = "insert error: " <> T.unpack m - show InsertErrorInvalidHash = "Invalid transaction hash" - show InsertErrorInvalidSigs = "Invalid transaction sigs" - show InsertErrorTimedOut = "Transaction validation timed out" +instance Show InsertError where + show = \case + InsertErrorDuplicate -> "Transaction already exists on chain" + InsertErrorTTLExpired -> "Transaction time-to-live is expired" + InsertErrorTimeInFuture -> "Transaction creation time too far in the future" + InsertErrorOversized (GasLimit l) -> "Transaction gas limit exceeds block gas limit (" <> show l <> ")" + InsertErrorUndersized (GasPrice p) (GasPrice m) -> "Transaction gas price (" <> show p <> ") is below minimum gas price (" <> show m <> ")" + 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 + InsertErrorCompilationFailed msg -> "Transaction compilation failed: " <> T.unpack msg + InsertErrorOther m -> "insert error: " <> T.unpack m + InsertErrorInvalidHash -> "Invalid transaction hash" + InsertErrorInvalidSigs -> "Invalid transaction sigs" + InsertErrorTimedOut -> "Transaction validation timed out" + InsertErrorPactParseError msg -> "Pact parse error: " <> T.unpack msg instance Exception InsertError @@ -305,7 +309,7 @@ data MempoolBackend t = MempoolBackend { -- for mining. -- , mempoolGetBlock - :: BlockFill -> MempoolPreBlockCheck t -> BlockHeight -> BlockHash -> IO (Vector t) + :: forall to. BlockFill -> MempoolPreBlockCheck t to -> BlockHeight -> BlockHash -> IO (Vector to) -- | Discard any expired transactions. , mempoolPrune :: IO () @@ -324,8 +328,8 @@ data MempoolBackend t = MempoolBackend { , mempoolClear :: IO () } -noopMempoolPreBlockCheck :: MempoolPreBlockCheck t -noopMempoolPreBlockCheck _ _ v = return $! V.replicate (V.length v) True +noopMempoolPreBlockCheck :: MempoolPreBlockCheck t t +noopMempoolPreBlockCheck _ _ v = return $! V.map Right v noopMempool :: IO (MempoolBackend t) noopMempool = do @@ -368,11 +372,10 @@ noopMempool = do ------------------------------------------------------------------------------ -chainwebTransactionConfig - :: PactParserVersion - -> TransactionConfig ChainwebTransaction -chainwebTransactionConfig ppv = TransactionConfig - { txCodec = chainwebPayloadCodec ppv +pact4TransactionConfig + :: TransactionConfig Pact4.UnparsedTransaction +pact4TransactionConfig = TransactionConfig + { txCodec = Pact4.rawCommandCodec , txHasher = commandHash , txHashMeta = chainwebTestHashMeta , txGasPrice = getGasPrice @@ -382,11 +385,11 @@ chainwebTransactionConfig ppv = TransactionConfig where - getGasPrice = view cmdGasPrice . fmap payloadObj - getGasLimit = view cmdGasLimit . fmap payloadObj - getTimeToLive = view cmdTimeToLive . fmap payloadObj - getCreationTime = view cmdCreationTime . fmap payloadObj - commandHash c = let (H.Hash !h) = H.toUntypedHash $ _cmdHash c + 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 in TransactionHash h txmeta t = TransactionMetadata @@ -608,8 +611,11 @@ instance HasTextRepresentation TransactionHash where {-# INLINE toText #-} {-# INLINE fromText #-} -requestKeyToTransactionHash :: RequestKey -> TransactionHash -requestKeyToTransactionHash = TransactionHash . H.unHash . unRequestKey +pact4RequestKeyToTransactionHash :: Pact4.RequestKey -> TransactionHash +pact4RequestKeyToTransactionHash = TransactionHash . Pact4.unHash . Pact4.unRequestKey + +pact5RequestKeyToTransactionHash :: Pact5.RequestKey -> TransactionHash +pact5RequestKeyToTransactionHash = TransactionHash . Pact5.unHash . Pact5.unRequestKey ------------------------------------------------------------------------------ -- diff --git a/src/Chainweb/Miner/Config.hs b/src/Chainweb/Miner/Config.hs index 529e382de7..efe1bdae7c 100644 --- a/src/Chainweb/Miner/Config.hs +++ b/src/Chainweb/Miner/Config.hs @@ -61,7 +61,7 @@ import Chainweb.Time import Chainweb.Utils (hostArch, sshow) import Chainweb.Version import Chainweb.Version.Mainnet -import Chainweb.Version.Testnet +import Chainweb.Version.Testnet04 -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 5e37d140dc..d78dfd161f 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -235,8 +235,8 @@ newWork logFun choice eminer@(Miner mid _) hdb pact tpw c = 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 ParentHeader primedParent = newBlockParentHeader newBlock - if view blockHash primedParent == view blockHash (_parentHeader (_cutExtensionParent extension)) + let (primedParentHash, primedParentHeight, _) = newBlockParent newBlock + if primedParentHash == view blockHash (_parentHeader (_cutExtensionParent extension)) then do let payload = newBlockToPayloadWithOutputs newBlock let !phash = _payloadWithOutputsPayloadHash payload @@ -252,8 +252,8 @@ newWork logFun choice eminer@(Miner mid _) hdb pact tpw c = do let !extensionParent = _parentHeader (_cutExtensionParent extension) logFun @T.Text Info $ "newWork: chain " <> toText cid <> " not mineable because of parent header mismatch" - <> ". Primed parent hash: " <> toText (view blockHash primedParent) - <> ". Primed parent height: " <> sshow (view blockHeight primedParent) + <> ". Primed parent hash: " <> toText primedParentHash + <> ". Primed parent height: " <> sshow primedParentHeight <> ". Extension parent: " <> toText (view blockHash extensionParent) <> ". Extension height: " <> sshow (view blockHeight extensionParent) diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index 9311defb17..0b9b3de4ad 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -66,7 +66,7 @@ import Chainweb.Miner.Coordinator import Chainweb.Miner.Core import Chainweb.Miner.Pact import Chainweb.RestAPI.Orphans () -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version @@ -123,7 +123,7 @@ localTest lf v coord m cdb gen miners = -- mempoolNoopMiner :: LogFunction - -> HashMap ChainId (MempoolBackend ChainwebTransaction) + -> HashMap ChainId (MempoolBackend Pact4.UnparsedTransaction) -> IO () mempoolNoopMiner lf chainRes = runForever lf "Chainweb.Miner.Miners.mempoolNoopMiner" $ do diff --git a/src/Chainweb/Miner/Pact.hs b/src/Chainweb/Miner/Pact.hs index fbfc55ddd9..dfbe3ed29e 100644 --- a/src/Chainweb/Miner/Pact.hs +++ b/src/Chainweb/Miner/Pact.hs @@ -62,7 +62,7 @@ import Chainweb.Payload import Chainweb.Utils import qualified Pact.JSON.Encode as J -import Pact.Types.Term (KeySet(..), mkKeySet) +import qualified Pact.Types.KeySet as Pact5 -- -------------------------------------------------------------------------- -- -- Miner data @@ -77,9 +77,9 @@ newtype MinerId = MinerId { _minerId :: Text } -- | `MinerKeys` are a thin wrapper around a Pact `KeySet` to differentiate it -- from user keysets. -- -newtype MinerKeys = MinerKeys KeySet +newtype MinerKeys = MinerKeys Pact5.KeySet deriving stock (Eq, Ord, Generic) - deriving newtype (Show, FromJSON, NFData) + deriving newtype (Show, NFData) -- | Miner info data consists of a miner id (text), and its keyset (a pact -- type). @@ -97,15 +97,15 @@ data Miner = Miner !MinerId !MinerKeys instance J.Encode Miner where build (Miner (MinerId m) (MinerKeys ks)) = J.object [ "account" J..= m - , "predicate" J..= _ksPredFun ks - , "public-keys" J..= J.Array (_ksKeys ks) + , "predicate" J..= Pact5._ksPredFun ks + , "public-keys" J..= J.Array (Pact5._ksKeys ks) ] {-# INLINE build #-} instance FromJSON Miner where parseJSON = withObject "Miner" $ \o -> Miner <$> (MinerId <$> o .: "account") - <*> (MinerKeys <$> (KeySet <$> o .: "public-keys" <*> o .: "predicate")) + <*> (MinerKeys <$> (Pact5.KeySet <$> o .: "public-keys" <*> o .: "predicate")) -- | A lens into the miner id of a miner. -- @@ -125,7 +125,7 @@ minerKeys = lens (\(Miner _ k) -> k) (\(Miner i _) b -> Miner i b) defaultMiner :: Miner defaultMiner = Miner (MinerId "miner") $ MinerKeys - $ mkKeySet + $ Pact5.mkKeySet ["f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27"] "keys-all" @@ -134,7 +134,7 @@ defaultMiner = Miner (MinerId "miner") -- | A trivial Miner. -- noMiner :: Miner -noMiner = Miner (MinerId "NoMiner") (MinerKeys $ mkKeySet [] "<") +noMiner = Miner (MinerId "NoMiner") (MinerKeys $ Pact5.mkKeySet [] "<") {-# NOINLINE noMiner #-} -- | Convert from Pact `Miner` to Chainweb `MinerData`. diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index e2d6698bf5..26d11ac250 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -54,7 +54,6 @@ import System.Random -- internal modules -import Chainweb.BlockHeader import Chainweb.Cut.Create import Chainweb.Logger import Chainweb.Miner.Config @@ -62,7 +61,7 @@ import Chainweb.Miner.Coordinator import Chainweb.Miner.Core import Chainweb.Miner.Pact import Chainweb.Miner.RestAPI (MiningApi) -import Chainweb.Pact.Service.Types(BlockInProgress(..), Transactions(..)) +import Chainweb.Pact.Types(BlockInProgress(..), Transactions(..)) import Chainweb.Payload import Chainweb.RestAPI.Utils import Chainweb.Utils @@ -206,9 +205,34 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> withLimit resp $ d (WorkStale, WorkStale) -> retry (WorkAlreadyMined _, WorkAlreadyMined _) -> retry - (WorkReady (NewBlockInProgress lastBip), WorkReady (NewBlockInProgress currentBip)) - | ParentHeader lastPh <- _blockInProgressParentHeader lastBip - , ParentHeader currentPh <- _blockInProgressParentHeader currentBip + (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. diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index e5a5322f05..eae063fdbd 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -71,17 +71,17 @@ import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.Cut.CutHashes (cutIdToText) import Chainweb.CutDB (cutHashesTable) import Chainweb.Logger (Logger, l2l, setComponent, logFunctionText) -import Chainweb.Pact.Backend.ChainwebPactDb () +import Chainweb.Pact4.Backend.ChainwebPactDb () import Chainweb.Pact.Backend.PactState -import Chainweb.Pact.Backend.Types (SQLiteEnv) -import Chainweb.Pact.Backend.Utils (fromUtf8, toUtf8) +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils import Chainweb.Payload.PayloadStore (initializePayloadDb, addNewPayload, lookupPayloadWithHeight) import Chainweb.Payload.PayloadStore.RocksDB (newPayloadDb) import Chainweb.Utils (sshow, fromText, toText, int) import Chainweb.Version (ChainId, ChainwebVersion(..), chainIdToText) import Chainweb.Version.Mainnet (mainnet) import Chainweb.Version.Registry (lookupVersionByName) -import Chainweb.Version.Testnet (testnet) +import Chainweb.Version.Testnet04 (testnet04) import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb, initWebBlockHeaderDb) import Data.LogMessage (SomeLogMessage, logText) @@ -617,9 +617,6 @@ getVersionedTableMutationRowsAt logger db target = do _ -> do exitLog logger "getVersionedTableMutationRowsAt query: invalid query" -tbl :: Utf8 -> Utf8 -tbl u = "[" <> u <> "]" - -- | Locate the latest "safe" target blockheight for compaction. -- -- In mainnet/testnet, this is determined @@ -647,7 +644,7 @@ locateLatestSafeTarget logger v dbDir cids = do -- In devnet or testing versions we don't care. let safeDepth :: BlockHeight safeDepth - | v == mainnet || v == testnet = BlockHeight 1_000 + | v == mainnet || v == testnet04 = BlockHeight 1_000 | otherwise = BlockHeight 0 when (latestCommon - earliestCommon < safeDepth) $ do diff --git a/src/Chainweb/Pact/Backend/DbCache.hs b/src/Chainweb/Pact/Backend/DbCache.hs index 6d8a6973c9..0a6461ccbb 100644 --- a/src/Chainweb/Pact/Backend/DbCache.hs +++ b/src/Chainweb/Pact/Backend/DbCache.hs @@ -38,7 +38,7 @@ import Control.Monad.State.Strict import qualified Crypto.Hash as C (hash) import Crypto.Hash.Algorithms -import Data.Aeson (FromJSON, ToJSON(..), object, decodeStrict, (.=)) +import Data.Aeson (FromJSON, ToJSON(..), (.=), object) import qualified Data.ByteArray as BA import Data.ByteString (ByteString) import qualified Data.ByteString.Short as BS @@ -107,14 +107,13 @@ instance Ord (CacheEntry a) where -- | Cache entries are stored within a compact regions. The whole region is GCed -- only when no pointer to any data in the region is live any more. -- -compactCacheEntry :: MonadIO m => FromJSON a => ByteString -> m (Maybe (a, Int)) -compactCacheEntry rowdata = do - c <- liftIO $ compact (decodeStrict {- lazy, forced by compact -} rowdata) - case getCompact c of - Nothing -> return Nothing - Just m -> do - s <- liftIO $ compactSize c - return $ Just (m, fromIntegral s) +compactCacheEntry :: MonadIO m => a -> m (a, Int) +compactCacheEntry d = do + (c, s) <- liftIO $ do + c <- compact d + s <- compactSize c + pure (c, s) + return (getCompact c, fromIntegral s) -- -------------------------------------------------------------------------- -- -- DbCache @@ -165,9 +164,10 @@ cacheCount = HM.size . _dcStore -- cache maintenance. -- checkDbCache - :: FromJSON a - => Utf8 + :: Utf8 -- ^ Db key for data + -> (ByteString -> Maybe a) + -- ^ row data's decoding function -> ByteString -- ^ row data that contains the encoded value -> TxId @@ -175,7 +175,7 @@ checkDbCache -- Smaller values are evicted first. -> DbCache a -> IO (Maybe a, DbCache a) -checkDbCache key rowdata txid = runStateT $ do +checkDbCache key f rowdata txid = runStateT $ do readCache txid addy >>= \case -- Cache hit @@ -184,9 +184,10 @@ checkDbCache key rowdata txid = runStateT $ do return $ Just x -- Cache miss: decode module and insert into cache - Nothing -> compactCacheEntry rowdata >>= \case + Nothing -> case f rowdata of Nothing -> return Nothing - Just (m, s) -> do + Just v -> do + (m, s) <- compactCacheEntry v writeCache txid addy s m modify' (dcMisses +~ 1) return $ Just m diff --git a/src/Chainweb/Pact/Backend/PactState.hs b/src/Chainweb/Pact/Backend/PactState.hs index 4085d7b557..256819a82f 100644 --- a/src/Chainweb/Pact/Backend/PactState.hs +++ b/src/Chainweb/Pact/Backend/PactState.hs @@ -55,7 +55,6 @@ module Chainweb.Pact.Backend.PactState import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.Logger (Logger, addLabel) -import Chainweb.Pact.Backend.Types (SQLiteEnv) import Chainweb.Pact.Backend.Utils (fromUtf8, withSqliteDb) import Chainweb.Utils (T2(..), int) import Chainweb.Version (ChainId, ChainwebVersion, chainIdToText) @@ -82,12 +81,15 @@ import Data.Text.Encoding qualified as Text import Database.SQLite3 qualified as SQL import Database.SQLite3.Direct (Utf8(..), Database) 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 System.Directory (doesFileExist) -import System.FilePath (()) +import Chainweb.Pact.Backend.Types excludedTables :: [Utf8] excludedTables = checkpointerTables ++ compactionTables diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs index fea74d363a..f78163d741 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs @@ -32,14 +32,14 @@ import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..)) import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot.Mainnet qualified as MainnetSnapshot 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 (SQLiteEnv) +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.Development (devnet) import Chainweb.Version.Mainnet (mainnet) import Chainweb.Version.RecapDevelopment (recapDevnet) -import Chainweb.Version.Testnet (testnet) +import Chainweb.Version.Testnet04 (testnet04) import Control.Applicative ((<|>), many) import Control.Monad (forM_, when) import Data.ByteString.Lazy qualified as BL @@ -330,7 +330,7 @@ chainHashesToModule v input = prefix versionModuleName :: ChainwebVersion -> String versionModuleName v | v == mainnet = "Mainnet" - | v == testnet = "Testnet" + | v == testnet04 = "Testnet" | v == recapDevnet = "RecapDevnet" | v == devnet = "Devnet" | otherwise = case Text.unpack (getChainwebVersionName (_versionName v)) of diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs index 6a4d814aba..dd6d81e23e 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs @@ -32,7 +32,7 @@ -- E.g. say the db has latest height 3_980_000, but the latest embedded -- snapshot is at 3_800_000. This means that we will pick the embedded -- snapshot at blockheight 3_800_000. --- - Sets the environment variable `SNAPSHOT_BLOCKHEIGHT`, which is useful +-- - Sets the environment variable `SNAPSHOTview blockHeight`, which is useful -- for debugging and/or consumption by other tools, such as pact-diff. -- - Go into the db and compute the blockheader and pact grandhash -- ('Snapshot') at 'snapshotBlockHeight'. @@ -61,9 +61,8 @@ 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.RelationalCheckpointer (withProdRelationalCheckpointer) -import Chainweb.Pact.Backend.Types (IntraBlockPersistence(..), SQLiteEnv, _cpRewindTo) -import Chainweb.Pact.Types (defaultModuleCacheLimit) +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Types import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) import Chainweb.Utils (sshow) import Chainweb.Version (ChainwebVersion(..)) @@ -84,6 +83,7 @@ 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 -- | Verifies that the hashes and headers match @grands@. -- @@ -186,8 +186,8 @@ pactDropPostVerified logger v srcDir tgtDir snapshotBlockHeight snapshotChainHas let logger' = addChainIdLabel cid logger logFunctionText logger' Info $ "Dropping anything post verified state (BlockHeight " <> sshow snapshotBlockHeight <> ")" - withProdRelationalCheckpointer logger defaultModuleCacheLimit sqliteEnv DoNotPersistIntraBlockWrites v cid $ \cp -> do - _cpRewindTo cp (Just $ ParentHeader $ blockHeader $ snapshotChainHashes ^?! ix cid) + Checkpointer.Internal.withCheckpointerResources logger defaultModuleCacheLimit sqliteEnv DoNotPersistIntraBlockWrites v cid $ \cp -> do + Checkpointer.Internal.rewindTo cp (Just $ ParentHeader $ blockHeader $ snapshotChainHashes ^?! ix cid) data PactImportConfig = PactImportConfig { sourcePactDir :: FilePath diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs index 33352bf162..c72be94445 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs @@ -33,7 +33,7 @@ import Chainweb.Logger (Logger, logFunctionText) import Chainweb.Pact.Backend.PactState (getLatestPactStateAt, getLatestBlockHeight, addChainIdLabel) import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..)) import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (computeGrandHash) -import Chainweb.Pact.Backend.Types (SQLiteEnv) +import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils (startSqliteDb, stopSqliteDb) import Chainweb.Storage.Table.RocksDB (RocksDb) import Chainweb.TreeDB (seekAncestor) diff --git a/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs b/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs deleted file mode 100644 index 7a80196143..0000000000 --- a/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs +++ /dev/null @@ -1,451 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE CPP #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE BlockArguments #-} - --- | --- 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.Backend.RelationalCheckpointer - ( initRelationalCheckpointer - , initRelationalCheckpointer' - , withProdRelationalCheckpointer - ) 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 Data.ByteString (intercalate) -import qualified Data.ByteString.Short as BS -#if !MIN_VERSION_base(4,20,0) -import Data.Foldable (foldl') -#endif -import Data.Int -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 qualified Data.Vector as V -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.Hash (PactHash, TypedHash(..)) -import Pact.Types.Persistence -import Pact.Types.RowData -import Pact.Types.SQLite - --- chainweb -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Logger -import Chainweb.Pact.Backend.ChainwebPactDb -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Backend.Utils -import Chainweb.Pact.Backend.DbCache -import Chainweb.Pact.Service.Types -import Chainweb.Pact.Types (defaultModuleCacheLimit) -import Chainweb.Utils -import Chainweb.Utils.Serialization -import Chainweb.Version - -initRelationalCheckpointer - :: (Logger logger) - => DbCacheLimitBytes - -> SQLiteEnv - -> IntraBlockPersistence - -> logger - -> ChainwebVersion - -> ChainId - -> IO (Checkpointer logger) -initRelationalCheckpointer dbCacheLimit sqlenv p loggr v cid = - snd <$!> initRelationalCheckpointer' dbCacheLimit sqlenv p loggr v cid - -withProdRelationalCheckpointer - :: (Logger logger) - => logger - -> DbCacheLimitBytes - -> SQLiteEnv - -> IntraBlockPersistence - -> ChainwebVersion - -> ChainId - -> (Checkpointer logger -> IO a) - -> IO a -withProdRelationalCheckpointer logger dbCacheLimit sqlenv p v cid inner = do - (moduleCacheVar, cp) <- initRelationalCheckpointer' dbCacheLimit sqlenv p logger v cid - withAsync (logModuleCacheStats moduleCacheVar) $ \_ -> 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 -} - --- for testing -initRelationalCheckpointer' - :: (Logger logger) - => DbCacheLimitBytes - -> SQLiteEnv - -> IntraBlockPersistence - -> logger - -> ChainwebVersion - -> ChainId - -> IO (MVar (DbCache PersistModuleData), Checkpointer logger) -initRelationalCheckpointer' dbCacheLimit sqlenv p loggr v cid = do - initSchema loggr sqlenv - moduleCacheVar <- newMVar (emptyDbCache dbCacheLimit) - let - checkpointer = Checkpointer - { _cpRestoreAndSave = doRestoreAndSave loggr v cid sqlenv p moduleCacheVar - , _cpReadCp = ReadCheckpointer - { _cpReadFrom = doReadFrom loggr v cid sqlenv moduleCacheVar - , _cpGetBlockHistory = doGetBlockHistory sqlenv - , _cpGetHistoricalLookup = doGetHistoricalLookup sqlenv - , _cpGetEarliestBlock = doGetEarliestBlock sqlenv - , _cpGetLatestBlock = doGetLatestBlock sqlenv - , _cpLookupBlockInCheckpointer = doLookupBlock sqlenv - , _cpGetBlockParent = doGetBlockParent v cid sqlenv - , _cpLogger = loggr - } - } - return (moduleCacheVar, checkpointer) - - --- see the docs for _cpReadFrom -doReadFrom - :: (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> SQLiteEnv - -> MVar (DbCache PersistModuleData) - -> Maybe ParentHeader - -> (CurrentBlockDbEnv logger -> IO a) - -> IO (Historical a) -doReadFrom logger v cid sql moduleCacheVar maybeParent doRead = do - let currentHeight = case maybeParent of - Nothing -> genesisHeight v cid - Just parent -> succ . view blockHeight . _parentHeader $ parent - - modifyMVar moduleCacheVar $ \sharedModuleCache -> do - bracket - (beginSavepoint sql BatchSavepoint) - (\_ -> abortSavepoint sql BatchSavepoint) $ \() -> do - h <- getEndTxId "doReadFrom" sql maybeParent >>= traverse \startTxId -> do - newDbEnv <- newMVar $ BlockEnv - (mkBlockHandlerEnv v cid currentHeight sql DoNotPersistIntraBlockWrites logger) - (initBlockState defaultModuleCacheLimit startTxId) - { _bsModuleCache = sharedModuleCache } - -- 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 <- doGetLatestBlock sql - 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 - - let - pactDb - | parentIsLatestHeader = chainwebPactDb - | otherwise = rewoundPactDb currentHeight startTxId - curBlockDbEnv = CurrentBlockDbEnv - { _cpPactDbEnv = PactDbEnv pactDb newDbEnv - , _cpRegisterProcessedTx = - \(TypedHash hash) -> runBlockEnv newDbEnv (indexPactTransaction $ BS.fromShort hash) - , _cpLookupProcessedTx = \hs -> - runBlockEnv newDbEnv (doLookupSuccessful currentHeight hs) - } - r <- doRead curBlockDbEnv - finalCache <- _bsModuleCache . _benvBlockState <$> readMVar newDbEnv - return (r, finalCache) - case h of - NoHistory -> return (sharedModuleCache, NoHistory) - Historical (r, finalCache) -> return (finalCache, Historical r) - - - --- TODO: log more? --- see the docs for _cpRestoreAndSave. -doRestoreAndSave - :: forall logger r q. - (Logger logger, Monoid q, HasCallStack) - => logger - -> ChainwebVersion - -> ChainId - -> SQLiteEnv - -> IntraBlockPersistence - -> MVar (DbCache PersistModuleData) - -> Maybe ParentHeader - -> Stream (Of (RunnableBlock logger q)) IO r - -> IO (r, q) -doRestoreAndSave logger v cid sql p moduleCacheVar rewindParent blocks = - modifyMVar moduleCacheVar $ \moduleCache -> do - fmap fst $ generalBracket - (beginSavepoint sql BatchSavepoint) - (\_ -> \case - ExitCaseSuccess {} -> commitSavepoint sql BatchSavepoint - _ -> abortSavepoint sql BatchSavepoint - ) $ \_ -> do - startTxId <- rewindDbTo sql rewindParent - ((q, _, _, finalModuleCache) :> r) <- extend startTxId moduleCache - return (finalModuleCache, (r, q)) - where - - extend - :: TxId -> DbCache PersistModuleData - -> IO (Of (q, Maybe ParentHeader, TxId, DbCache PersistModuleData) r) - extend startTxId startModuleCache = Streaming.foldM - (\(m, maybeParent, txid, moduleCache) block -> do - let - !bh = case maybeParent of - Nothing -> genesisHeight v cid - Just parent -> (succ . view blockHeight . _parentHeader) parent - -- prepare the block state - let handlerEnv = mkBlockHandlerEnv v cid bh sql p logger - let state = (initBlockState defaultModuleCacheLimit txid) { _bsModuleCache = moduleCache } - dbMVar <- newMVar BlockEnv - { _blockHandlerEnv = handlerEnv - , _benvBlockState = state - } - - let curBlockDbEnv = CurrentBlockDbEnv - { _cpPactDbEnv = PactDbEnv chainwebPactDb dbMVar - , _cpRegisterProcessedTx = - \(TypedHash hash) -> runBlockEnv dbMVar (indexPactTransaction $ BS.fromShort hash) - , _cpLookupProcessedTx = \hs -> runBlockEnv dbMVar $ doLookupSuccessful bh hs - } - -- execute the block - (m', newBh) <- runBlock block curBlockDbEnv maybeParent - -- grab any resulting state that we're interested in keeping - nextState <- _benvBlockState <$> takeMVar dbMVar - let !nextTxId = _bsTxId nextState - let !nextModuleCache = _bsModuleCache nextState - -- 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 v cid /= view blockHeight 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 $ - "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " - <> sshow (view blockHeight ph) <> ", child height " <> sshow (view blockHeight newBh) - _ -> return () - -- persist any changes to the database - commitBlockStateToDatabase sql (view blockHash newBh) (view blockHeight newBh) nextState - return (m'', Just (ParentHeader newBh), nextTxId, nextModuleCache) - ) - (return (mempty, rewindParent, startTxId, startModuleCache)) - return - blocks - -doGetEarliestBlock :: HasCallStack => SQLiteEnv -> IO (Maybe (BlockHeight, BlockHash)) -doGetEarliestBlock 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." - -doGetLatestBlock :: HasCallStack => SQLiteEnv -> IO (Maybe (BlockHeight, BlockHash)) -doGetLatestBlock 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 (fromIntegral hgt, hash) - go _ = fail "Chainweb.Pact.Backend.RelationalCheckpointer.doGetLatest: impossible. This is a bug in chainweb-node." - -doLookupBlock :: SQLiteEnv -> (BlockHeight, BlockHash) -> IO Bool -doLookupBlock db (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 "doLookupBlock: output type mismatch" - where - qtext = "SELECT COUNT(*) FROM BlockHistory WHERE blockheight = ? AND hash = ?;" - -doGetBlockParent :: ChainwebVersion -> ChainId -> SQLiteEnv -> (BlockHeight, BlockHash) -> IO (Maybe BlockHash) -doGetBlockParent v cid db (bh, hash) - | bh == genesisHeight v cid = return Nothing - | otherwise = do - blockFound <- doLookupBlock db (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 (internalError . T.pack) (return . return) $! runGetEitherS decodeBlockHash blob - [] -> internalError "doGetBlockParent: block was found but its parent couldn't be found" - _ -> error "doGetBlockParent: output type mismatch" - where - qtext = "SELECT hash FROM BlockHistory WHERE blockheight = ?" - - -doLookupSuccessful :: BlockHeight -> V.Vector PactHash -> BlockHandler logger (HashMap.HashMap PactHash (T2 BlockHeight BlockHash)) -doLookupSuccessful curHeight hashes = do - fmap buildResultMap $ -- swizzle results of query into a HashMap - callDb "doLookupSuccessful" $ \db -> do - let - hss = V.toList hashes - params = intercalate "," (map (const "?") hss) - qtext = Utf8 $ intercalate " " - [ "SELECT blockheight, hash, txhash" - , "FROM TransactionIndex" - , "INNER JOIN BlockHistory USING (blockheight)" - , "WHERE txhash IN (" <> params <> ")" <> " AND blockheight <= ?;" - ] - qvals - -- match query params above. first, hashes - = map (\(TypedHash h) -> SBlob $ BS.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 - ++ [SInt $ fromIntegral (pred curHeight)] - - qry db qtext qvals [RInt, 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 PactHash BlockHeight BlockHash] -> HashMap.HashMap PactHash (T2 BlockHeight BlockHash) - buildResultMap xs = HashMap.fromList $ - map (\(T3 txhash blockheight blockhash) -> (txhash, T2 blockheight blockhash)) xs - - go :: [SType] -> IO (T3 PactHash BlockHeight BlockHash) - go (SInt blockheight:SBlob blockhash:SBlob txhash:_) = do - !blockhash' <- either fail return $ runGetEitherS decodeBlockHash blockhash - let !txhash' = TypedHash $ BS.toShort txhash - return $! T3 txhash' (fromIntegral blockheight) blockhash' - go _ = fail "impossible" - -doGetBlockHistory :: SQLiteEnv -> BlockHeader -> Domain RowKey RowData -> IO (Historical BlockTxHistory) -doGetBlockHistory db blockHeader d = do - historicalEndTxId <- - fmap fromIntegral - <$> getEndTxId "doGetBlockHistory" db (Just $ ParentHeader blockHeader) - forM historicalEndTxId $ \endTxId -> do - startTxId <- - if bHeight == genesisHeight v cid - then return 0 - else getEndTxId' "doGetBlockHistory" db (pred bHeight) (view blockParent blockHeader) >>= \case - NoHistory -> - internalError $ "doGetBlockHistory: missing parent for: " <> sshow blockHeader - Historical startTxId -> - return $ fromIntegral startTxId - - let tname = 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 [TxLog RowData]) - -> (Utf8,TxId,TxLog RowData) - -> (S.Set Utf8,M.Map TxId [TxLog 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,TxLog 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,) <$> toTxLog 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,TxLog 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,) <$> toTxLog d k value - _ -> internalError $ "queryPrev: expected 0 or 1 rows, got: " <> T.pack (show r) - -doGetHistoricalLookup - :: SQLiteEnv - -> BlockHeader - -> Domain RowKey RowData - -> RowKey - -> IO (Historical (Maybe (TxLog RowData))) -doGetHistoricalLookup db blockHeader d k = do - historicalEndTxId <- - getEndTxId "doGetHistoricalLookup" db (Just $ ParentHeader blockHeader) - forM historicalEndTxId (queryHistoryLookup . fromIntegral) - where - queryHistoryLookup :: Int64 -> IO (Maybe (TxLog RowData)) - queryHistoryLookup e = do - let sql = "SELECT rowKey, rowdata FROM [" <> domainTableName d <> - "] WHERE txid < ? AND rowkey = ? ORDER BY txid DESC LIMIT 1;" - r <- qry db sql - [SInt e, SText (convRowKey k)] - [RText, RBlob] - case r of - [[SText key, SBlob value]] -> Just <$> toTxLog d key value - [] -> pure Nothing - _ -> internalError $ "doGetHistoricalLookup: expected single-row result, got " <> sshow r diff --git a/src/Chainweb/Pact/Backend/SQLite/DirectV2.hs b/src/Chainweb/Pact/Backend/SQLite/DirectV2.hs index aa9bf2e14d..23fe7d630e 100644 --- a/src/Chainweb/Pact/Backend/SQLite/DirectV2.hs +++ b/src/Chainweb/Pact/Backend/SQLite/DirectV2.hs @@ -1,7 +1,10 @@ {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Chainweb.Pact.Backend.SQLite.DirectV2 -( open_v2 +( SQLiteFlag(..) +, open_v2 , close_v2 , init_sha3 ) where @@ -16,8 +19,12 @@ import Database.SQLite3.Bindings.Types -- chainweb import Chainweb.Pact.Backend.SQLite.V2 -import Chainweb.Pact.Backend.Types +import Foreign.C.Types (CInt) + + +newtype SQLiteFlag = SQLiteFlag { getFlag :: CInt } + deriving newtype (Eq, Ord, Bits, Num) open_v2 :: Utf8 -> SQLiteFlag -> Maybe Utf8 -> IO (Either (Error, Utf8) Database) open_v2 (Utf8 path) (SQLiteFlag flag) mzvfs = diff --git a/src/Chainweb/Pact/Backend/Types.hs b/src/Chainweb/Pact/Backend/Types.hs index 9d40ee295b..223baf6c7a 100644 --- a/src/Chainweb/Pact/Backend/Types.hs +++ b/src/Chainweb/Pact/Backend/Types.hs @@ -1,175 +1,70 @@ +{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} -{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE FunctionalDependencies #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE StrictData #-} -{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE KindSignatures #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE DataKinds #-} --- | --- Module: Chainweb.Pact.Backend.Types --- Copyright: Copyright © 2018 Kadena LLC. --- License: See LICENSE file --- Maintainer: Mark Nichols --- Stability: experimental --- --- Chainweb / Pact Types module for various database backends module Chainweb.Pact.Backend.Types - ( RunnableBlock(..) - , Historical(..) - , _Historical - , _NoHistory - , Checkpointer(..) - , _cpRewindTo - , ReadCheckpointer(..) - , CurrentBlockDbEnv(..) - , Env'(..) - , EnvPersist'(..) - , PactDbConfig(..) - , pdbcGasLimit - , pdbcGasRate - , pdbcLogDir - , pdbcPersistDir - , pdbcPragmas - , ChainwebPactDbEnv - , PactDbEnvPersist(..) - , pdepEnv - , pdepPactDb - , PactDbState(..) - , pdbsDbEnv - - , SQLiteRowDelta(..) - , SQLitePendingTableCreations - , SQLitePendingWrites + ( Checkpointer(..) + , SQLiteEnv + , IntraBlockPersistence(..) + , BlockHandle(..) + , blockHandleTxId + , blockHandlePending + , emptyBlockHandle , SQLitePendingData(..) - , pendingTableCreation + , emptySQLitePendingData , pendingWrites - , pendingTxLogMap , pendingSuccessfulTxs - , emptySQLitePendingData - - , BlockState(..) - , initBlockState - , bsMode - , bsTxId - , bsPendingBlock - , bsPendingTx - , bsModuleCache - , BlockEnv(..) - , benvBlockState - , blockHandlerEnv - , runBlockEnv - , SQLiteEnv - , IntraBlockPersistence(..) - , BlockHandler(..) - , BlockHandlerEnv(..) - , mkBlockHandlerEnv - , blockHandlerBlockHeight - , blockHandlerModuleNameFix - , blockHandlerSortedKeys - , blockHandlerLowerCaseTables - , blockHandlerDb - , blockHandlerLogger - , blockHandlerPersistIntraBlockWrites - , ParentHash - , SQLiteFlag(..) - - -- * mempool - , MemPoolAccess(..) - - , PactServiceException(..) - , BlockTxHistory(..) + , pendingTableCreation + , pendingTxLogMap + , SQLiteRowDelta(..) + , Historical(..) + , PactDbFor ) where -import Control.Concurrent.MVar -import Control.DeepSeq -import Control.Exception -import Control.Exception.Safe hiding (bracket) import Control.Lens -import Control.Monad.Reader -import Control.Monad.State.Strict - -import Data.Aeson -import Data.Bits +import Chainweb.Pact.Backend.DbCache +import Chainweb.Version +import Database.SQLite3.Direct (Database) +import qualified Pact.Types.Persistence as Pact4 +import Control.Concurrent.MVar import Data.ByteString (ByteString) +import GHC.Generics +import qualified Pact.Types.Names as Pact4 import Data.DList (DList) -import Data.Functor -import Data.HashMap.Strict (HashMap) +import Data.Map (Map) import Data.HashSet (HashSet) -import Data.List.NonEmpty(NonEmpty(..)) -import Data.Map.Strict (Map) -import Data.Vector (Vector) - -import Database.SQLite3.Direct as SQ3 - -import Foreign.C.Types (CInt(..)) - -import GHC.Generics -import GHC.Stack - -import Pact.Interpreter (PactDbEnv(..)) -import Pact.Persist.SQLite (Pragma(..)) -import Pact.PersistPactDb (DbEnv(..)) -import qualified Pact.Types.Hash as P -import Pact.Types.Persistence -import Pact.Types.RowData (RowData) -import Pact.Types.Runtime (TableName) - -import qualified Pact.JSON.Encode as J - --- internal modules -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Pact.Backend.DbCache -import Chainweb.Transaction -import Chainweb.Utils (T2) -import Chainweb.Version -import Chainweb.Version.Guards -import Chainweb.Mempool.Mempool (MempoolPreBlockCheck,TransactionHash,BlockFill) - -import Streaming(Stream, Of) - -data Env' = forall a. Env' (PactDbEnv (DbEnv a)) +import Data.HashMap.Strict (HashMap) +import Data.List.NonEmpty (NonEmpty) +import Control.DeepSeq (NFData) -data PactDbEnvPersist p = PactDbEnvPersist - { _pdepPactDb :: !(PactDb (DbEnv p)) - , _pdepEnv :: !(DbEnv p) +-- | Whether we write rows to the database that were already overwritten +-- in the same block. This is temporarily necessary to do while Rosetta uses +-- those rows to determine the contents of historic transactions. +data IntraBlockPersistence = PersistIntraBlockWrites | DoNotPersistIntraBlockWrites + deriving (Eq, Ord, Show) + +data Checkpointer logger + = Checkpointer + { cpLogger :: logger + , cpCwVersion :: ChainwebVersion + , cpChainId :: ChainId + , cpSql :: SQLiteEnv + , cpIntraBlockPersistence :: IntraBlockPersistence + , cpModuleCacheVar :: MVar (DbCache Pact4.PersistModuleData) } -makeLenses ''PactDbEnvPersist - - -data EnvPersist' = forall a. EnvPersist' (PactDbEnvPersist a) - -newtype PactDbState = PactDbState { _pdbsDbEnv :: EnvPersist' } - -makeLenses ''PactDbState - -data PactDbConfig = PactDbConfig - { _pdbcPersistDir :: !(Maybe FilePath) - , _pdbcLogDir :: !FilePath - , _pdbcPragmas :: ![Pragma] - , _pdbcGasLimit :: !(Maybe Int) - , _pdbcGasRate :: !(Maybe Int) - } deriving (Eq, Show, Generic) - -instance FromJSON PactDbConfig - -makeLenses ''PactDbConfig +type SQLiteEnv = Database -- | 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 @@ -178,21 +73,21 @@ makeLenses ''PactDbConfig -- data SQLiteRowDelta = SQLiteRowDelta { _deltaTableName :: !ByteString -- utf8? - , _deltaTxId :: {-# UNPACK #-} !TxId + , _deltaTxId :: {-# UNPACK #-} !Pact4.TxId , _deltaRowKey :: !ByteString , _deltaData :: !ByteString } deriving (Show, Generic, Eq) instance Ord SQLiteRowDelta where compare a b = compare aa bb - where + 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 = Map TableName (DList TxLogJson) +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 @@ -206,6 +101,14 @@ type SQLitePendingSuccessfulTxs = HashSet ByteString -- Structured as a map from table name to a map from rowkey to inserted row delta. type SQLitePendingWrites = HashMap ByteString (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' @@ -213,6 +116,7 @@ type SQLitePendingWrites = HashMap ByteString (HashMap ByteString (NonEmpty SQLi data SQLitePendingData = SQLitePendingData { _pendingTableCreation :: !SQLitePendingTableCreations , _pendingWrites :: !SQLitePendingWrites + -- See Note [TxLogs in SQLitePendingData] , _pendingTxLogMap :: !TxLogMap , _pendingSuccessfulTxs :: !SQLitePendingSuccessfulTxs } @@ -220,255 +124,25 @@ data SQLitePendingData = SQLitePendingData makeLenses ''SQLitePendingData -type SQLiteEnv = Database - --- | 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 - , _bsPendingTx :: !(Maybe SQLitePendingData) - , _bsMode :: !(Maybe ExecutionMode) - , _bsModuleCache :: !(DbCache PersistModuleData) - } - emptySQLitePendingData :: SQLitePendingData emptySQLitePendingData = SQLitePendingData mempty mempty mempty mempty -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 - , _bsPendingTx = Nothing - , _bsModuleCache = emptyDbCache cl - } - -makeLenses ''BlockState - --- | Whether we write rows to the database that were already overwritten --- in the same block. This is temporarily necessary to do while Rosetta uses --- those rows to determine the contents of historic transactions. -data IntraBlockPersistence = PersistIntraBlockWrites | DoNotPersistIntraBlockWrites - deriving (Eq, Ord, Show) - -data BlockHandlerEnv logger = BlockHandlerEnv - { _blockHandlerDb :: !SQLiteEnv - , _blockHandlerLogger :: !logger - , _blockHandlerBlockHeight :: !BlockHeight - , _blockHandlerModuleNameFix :: !Bool - , _blockHandlerSortedKeys :: !Bool - , _blockHandlerLowerCaseTables :: !Bool - , _blockHandlerPersistIntraBlockWrites :: !IntraBlockPersistence +data BlockHandle = BlockHandle + { _blockHandleTxId :: !Pact4.TxId + , _blockHandlePending :: !SQLitePendingData } + deriving (Eq, Show) +makeLenses ''BlockHandle -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. - } - -makeLenses ''BlockEnv - - -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) - ) - -type ChainwebPactDbEnv logger = PactDbEnv (BlockEnv logger) - -type ParentHash = BlockHash - --- | The parts of the checkpointer that do not mutate the database. -data ReadCheckpointer logger = ReadCheckpointer - { _cpReadFrom :: - !(forall a. Maybe ParentHeader -> - (CurrentBlockDbEnv logger -> IO a) -> IO (Historical a)) - -- ^ 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, Nothing is returned. - , _cpGetEarliestBlock :: !(IO (Maybe (BlockHeight, BlockHash))) - -- ^ get the checkpointer's idea of the earliest block. The block height - -- is the height of the block of the block hash. - , _cpGetLatestBlock :: !(IO (Maybe (BlockHeight, BlockHash))) - -- ^ 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'? - , _cpLookupBlockInCheckpointer :: !((BlockHeight, BlockHash) -> IO Bool) - -- ^ is the checkpointer aware of the given block? - , _cpGetBlockParent :: !((BlockHeight, BlockHash) -> IO (Maybe BlockHash)) - , _cpGetBlockHistory :: - !(BlockHeader -> Domain RowKey RowData -> IO (Historical BlockTxHistory)) - , _cpGetHistoricalLookup :: - !(BlockHeader -> Domain RowKey RowData -> RowKey -> IO (Historical (Maybe (TxLog RowData)))) - , _cpLogger :: logger - } - --- | 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. -newtype RunnableBlock logger a = RunnableBlock - { runBlock :: CurrentBlockDbEnv logger -> Maybe ParentHeader -> IO (a, BlockHeader) } - --- | One makes requests to the checkpointer to query the pact state at the --- current block or any earlier block, to extend the pact state with new blocks, and --- to rewind the pact state to an earlier block. -data Checkpointer logger = Checkpointer - { _cpRestoreAndSave :: - !(forall q r. - (HasCallStack, Monoid q) => - Maybe ParentHeader -> - Stream (Of (RunnableBlock logger q)) IO r -> - IO (r, q)) - -- ^ 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 with 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. - , _cpReadCp :: !(ReadCheckpointer logger) - -- ^ access all read-only operations of the checkpointer. - } - --- the special case where one doesn't want to extend the chain, just rewind it. -_cpRewindTo :: Checkpointer logger -> Maybe ParentHeader -> IO () -_cpRewindTo cp ancestor = void $ _cpRestoreAndSave cp - ancestor - (pure () :: Stream (Of (RunnableBlock logger ())) IO ()) - --- this is effectively a read-write snapshot of the Pact state at a block. -data CurrentBlockDbEnv logger = CurrentBlockDbEnv - { _cpPactDbEnv :: !(ChainwebPactDbEnv logger) - , _cpRegisterProcessedTx :: !(P.PactHash -> IO ()) - , _cpLookupProcessedTx :: - !(Vector P.PactHash -> IO (HashMap P.PactHash (T2 BlockHeight BlockHash))) - } - -newtype SQLiteFlag = SQLiteFlag { getFlag :: CInt } - deriving newtype (Eq, Ord, Bits, Num) - --- TODO: get rid of this shim, it's probably not necessary -data MemPoolAccess = MemPoolAccess - { mpaGetBlock - :: !(BlockFill - -> MempoolPreBlockCheck ChainwebTransaction - -> BlockHeight - -> BlockHash - -> BlockHeader - -> IO (Vector ChainwebTransaction) - ) - , 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) (const mempty) (const mempty) (const mempty) - - -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 - --- | 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 +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. data Historical a - = Historical a - | NoHistory - deriving stock (Foldable, Functor, Generic, Traversable) - deriving anyclass NFData + = Historical a + | NoHistory + deriving stock (Eq, Foldable, Functor, Generic, Traversable, Show) + deriving anyclass NFData -makePrisms ''Historical +type family PactDbFor logger (pv :: PactVersion) diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 1455357dd8..30a078e90c 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -6,8 +6,11 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE GADTs #-} + {-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE BangPatterns #-} -- | -- Module: Chainweb.Pact.ChainwebPactDb @@ -20,9 +23,19 @@ module Chainweb.Pact.Backend.Utils ( -- * General utils - callDb - , open2 + open2 , chainDbFileName + -- * Shared Pact database interactions + , doLookupSuccessful + , commitBlockStateToDatabase + , createVersionedTable + , tbl + , initSchema + , rewindDbTo + , rewindDbToBlock + , rewindDbToGenesis + , getEndTxId + , getEndTxId' -- * Savepoints , withSavepoint , beginSavepoint @@ -35,12 +48,6 @@ module Chainweb.Pact.Backend.Utils , fromUtf8 , toTextUtf8 , asStringUtf8 - , domainTableName - , convKeySetName - , convModuleName - , convNamespaceName - , convRowKey - , convPactId , convSavepointName , expectSingleRowCol , expectSingle @@ -59,14 +66,11 @@ module Chainweb.Pact.Backend.Utils ) where import Control.Exception (SomeAsyncException, evaluate) -import Control.Exception.Safe (tryAny) import Control.Lens import Control.Monad import Control.Monad.Catch import Control.Monad.State.Strict -import Control.Monad.Reader - import Data.Bits import Data.Foldable import Data.String @@ -82,29 +86,45 @@ import System.LogLevel -- pact -import Pact.Types.Persistence -import Pact.Types.SQLite -import Pact.Types.Term - (KeySetName(..), ModuleName(..), NamespaceName(..), PactId(..)) +import qualified Pact.Types.Persistence as Pact4 +import qualified Pact.Types.SQLite as Pact4 import Pact.Types.Util (AsString(..)) +import qualified Pact.Core.Persistence as Pact5 + + -- chainweb import Chainweb.Logger import Chainweb.Pact.Backend.SQLite.DirectV2 -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Service.Types + +import Chainweb.Pact.Types import Chainweb.Version import Chainweb.Utils +import Chainweb.BlockHash +import Chainweb.BlockHeight +import Database.SQLite3.Direct hiding (open2) +import GHC.Stack (HasCallStack) +import qualified Data.ByteString.Short as SB +import qualified Data.Vector as V +import qualified Data.HashMap.Strict as HashMap +import Chainweb.Utils.Serialization +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) -- -------------------------------------------------------------------------- -- -- SQ3.Utf8 Encodings -toUtf8 :: T.Text -> SQ3.Utf8 +toUtf8 :: Text -> SQ3.Utf8 toUtf8 = SQ3.Utf8 . T.encodeUtf8 {-# INLINE toUtf8 #-} -fromUtf8 :: SQ3.Utf8 -> T.Text +fromUtf8 :: SQ3.Utf8 -> Text fromUtf8 (SQ3.Utf8 bytes) = T.decodeUtf8 bytes {-# INLINE fromUtf8 #-} @@ -116,47 +136,10 @@ asStringUtf8 :: AsString a => a -> SQ3.Utf8 asStringUtf8 = toUtf8 . asString {-# INLINE asStringUtf8 #-} -domainTableName :: Domain k v -> SQ3.Utf8 -domainTableName = asStringUtf8 - -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 - -convSavepointName :: SavepointName -> SQ3.Utf8 -convSavepointName = toTextUtf8 -- -------------------------------------------------------------------------- -- -- -callDb - :: (MonadCatch m, MonadReader (BlockHandlerEnv logger) m, MonadIO m) - => T.Text - -> (SQ3.Database -> IO b) - -> m b -callDb callerName action = do - c <- view blockHandlerDb - res <- tryAny $ liftIO $ action c - case res of - Left err -> internalError $ "callDb (" <> callerName <> "): " <> sshow err - Right r -> return r - withSavepoint :: SQLiteEnv -> SavepointName @@ -178,11 +161,14 @@ withSavepoint db name action = mask $ \resetMask -> do beginSavepoint :: SQLiteEnv -> SavepointName -> IO () beginSavepoint db name = - exec_ db $ "SAVEPOINT [" <> convSavepointName name <> "];" + Pact4.exec_ db $ "SAVEPOINT [" <> convSavepointName name <> "];" commitSavepoint :: SQLiteEnv -> SavepointName -> IO () commitSavepoint db name = - exec_ db $ "RELEASE SAVEPOINT [" <> convSavepointName name <> "];" + Pact4.exec_ db $ "RELEASE SAVEPOINT [" <> convSavepointName name <> "];" + +convSavepointName :: SavepointName -> SQ3.Utf8 +convSavepointName = toTextUtf8 -- | @rollbackSavepoint n@ rolls back all database updates since the most recent -- savepoint with the name @n@ and restarts the transaction. @@ -196,7 +182,7 @@ commitSavepoint db name = -- rollbackSavepoint :: SQLiteEnv -> SavepointName -> IO () rollbackSavepoint db name = - exec_ db $ "ROLLBACK TRANSACTION TO SAVEPOINT [" <> convSavepointName name <> "];" + Pact4.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. @@ -225,9 +211,6 @@ instance HasTextRepresentation SavepointName where <> ". Valid names are " <> T.intercalate ", " (toText @SavepointName <$> [minBound .. maxBound]) {-# INLINE fromText #-} --- instance AsString SavepointName where --- asString = toText - expectSingleRowCol :: Show a => String -> [[a]] -> IO a expectSingleRowCol _ [[s]] = return s expectSingleRowCol s v = @@ -244,7 +227,7 @@ expectSingle desc v = "Expected single-" <> asString (show desc) <> " result, got: " <> asString (show v) -chainwebPragmas :: [Pragma] +chainwebPragmas :: [Pact4.Pragma] chainwebPragmas = [ "synchronous = NORMAL" , "journal_mode = WAL" @@ -262,12 +245,12 @@ chainwebPragmas = , "page_size = 1024" ] -execMulti :: Traversable t => SQ3.Database -> SQ3.Utf8 -> t [SType] -> IO () -execMulti db q rows = bracket (prepStmt db q) destroy $ \stmt -> do +execMulti :: Traversable t => SQ3.Database -> SQ3.Utf8 -> t [Pact4.SType] -> IO () +execMulti db q rows = bracket (Pact4.prepStmt db q) destroy $ \stmt -> do forM_ rows $ \row -> do SQ3.reset stmt >>= checkError SQ3.clearBindings stmt - bindParams stmt row + Pact4.bindParams stmt row SQ3.step stmt >>= checkError where checkError (Left e) = void $ fail $ "error during batch insert: " ++ show e @@ -313,18 +296,18 @@ chainDbFileName cid = fold stopSqliteDb :: SQLiteEnv -> IO () stopSqliteDb = closeSQLiteConnection -withSQLiteConnection :: String -> [Pragma] -> (SQLiteEnv -> IO c) -> IO c +withSQLiteConnection :: String -> [Pact4.Pragma] -> (SQLiteEnv -> IO c) -> IO c withSQLiteConnection file ps = bracket (openSQLiteConnection file ps) closeSQLiteConnection -openSQLiteConnection :: String -> [Pragma] -> IO SQLiteEnv +openSQLiteConnection :: String -> [Pact4.Pragma] -> IO SQLiteEnv openSQLiteConnection file ps = open2 file >>= \case Left (err, msg) -> internalError $ "withSQLiteConnection: Can't open db with " <> asString (show err) <> ": " <> asString (show msg) Right r -> do - runPragmas r ps + Pact4.runPragmas r ps return r closeSQLiteConnection :: SQLiteEnv -> IO () @@ -336,7 +319,7 @@ closeSQLiteConnection c = void $ close_v2 c -- -- Cf. https://www.sqlite.org/inmemorydb.html -- -withTempSQLiteConnection :: [Pragma] -> (SQLiteEnv -> IO c) -> IO 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 @@ -344,7 +327,7 @@ withTempSQLiteConnection = withSQLiteConnection "" -- -- Cf. https://www.sqlite.org/inmemorydb.html -- -withInMemSQLiteConnection :: [Pragma] -> (SQLiteEnv -> IO c) -> IO c +withInMemSQLiteConnection :: [Pact4.Pragma] -> (SQLiteEnv -> IO c) -> IO c withInMemSQLiteConnection = withSQLiteConnection ":memory:" open2 :: String -> IO (Either (SQ3.Error, SQ3.Utf8) SQ3.Database) @@ -362,3 +345,322 @@ sqlite_open_readwrite, sqlite_open_create, sqlite_open_fullmutex :: SQLiteFlag sqlite_open_readwrite = 0x00000002 sqlite_open_create = 0x00000004 sqlite_open_fullmutex = 0x00010000 + +commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockHeight -> BlockHandle -> IO () +commitBlockStateToDatabase db hsh bh blockHandle = do + let newTables = _pendingTableCreation $ _blockHandlePending blockHandle + mapM_ (\tn -> createUserTable (Utf8 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 Utf8 <$> HashMap.toList writes + + backendWriteUpdateBatch + :: [(Utf8, [SQLiteRowDelta])] + -> IO () + backendWriteUpdateBatch writesByTable = mapM_ writeTable writesByTable + where + prepRow (SQLiteRowDelta _ txid rowkey rowdata) = + [ Pact4.SText (Utf8 rowkey) + , Pact4.SInt (fromIntegral txid) + , Pact4.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 + Pact4.exec' db mutq [Pact4.SText tablename, Pact4.SInt (fromIntegral blockheight)] + where + mutq = "INSERT OR IGNORE INTO VersionedTableMutation VALUES (?,?);" + + -- | Record a block as being in the history of the checkpointer. + blockHistoryInsert :: Pact4.TxId -> IO () + blockHistoryInsert t = + Pact4.exec' db stmt + [ Pact4.SInt (fromIntegral bh) + , Pact4.SBlob (runPutS (encodeBlockHash hsh)) + , Pact4.SInt (fromIntegral t) + ] + where + stmt = + "INSERT INTO BlockHistory ('blockheight','hash','endingtxid') VALUES (?,?,?);" + + createUserTable :: Utf8 -> IO () + createUserTable 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 = + Pact4.exec' db insertstmt insertargs + where + insertstmt = "INSERT OR IGNORE INTO VersionedTableCreation VALUES (?,?)" + insertargs = [Pact4.SText tablename, Pact4.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 = [Pact4.SBlob b, Pact4.SInt (fromIntegral bh)] + dbIndexTransactions txs = do + let rows = map toRow $ toList txs + execMulti db "INSERT INTO TransactionIndex (txhash, blockheight) \ + \ VALUES (?, ?)" rows + +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." + | otherwise = "[" <> t <> "]" + +createVersionedTable :: Utf8 -> Database -> IO () +createVersionedTable tablename db = do + Pact4.exec_ db createtablestmt + Pact4.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);" + + +doLookupSuccessful :: Database -> BlockHeight -> V.Vector SB.ShortByteString -> IO (HashMap.HashMap SB.ShortByteString (T2 BlockHeight BlockHash)) +doLookupSuccessful db curHeight hashes = 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" + , "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 + -- 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 (pred curHeight)] + + Pact4.qry db qtext qvals [Pact4.RInt, Pact4.RBlob, Pact4.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 xs = HashMap.fromList $ + map (\(T3 txhash blockheight blockhash) -> (txhash, T2 blockheight blockhash)) xs + + go :: [Pact4.SType] -> IO (T3 SB.ShortByteString BlockHeight BlockHash) + go (Pact4.SInt blockheight:Pact4.SBlob blockhash:Pact4.SBlob txhash:_) = do + !blockhash' <- either fail return $ runGetEitherS decodeBlockHash blockhash + let !txhash' = SB.toShort txhash + return $! T3 txhash' (fromIntegral blockheight) blockhash' + go _ = fail "impossible" + +-- | Create all tables that exist pre-genesis +-- TODO: migrate this logic to the checkpointer itself? +initSchema :: (Logger logger) => logger -> SQLiteEnv -> IO () +initSchema logger sql = + withSavepoint sql DbTransaction $ do + createBlockHistoryTable + createTableCreationTable + createTableMutationTable + createTransactionIndexTable + create (toUtf8 $ Pact5.renderDomain Pact5.DKeySets) + create (toUtf8 $ Pact5.renderDomain Pact5.DModules) + create (toUtf8 $ Pact5.renderDomain Pact5.DNamespaces) + create (toUtf8 $ Pact5.renderDomain Pact5.DDefPacts) + create (toUtf8 $ Pact5.renderDomain Pact5.DModuleSource) + where + create tablename = do + logDebug_ logger $ "initSchema: " <> fromUtf8 tablename + createVersionedTable tablename sql + + createBlockHistoryTable :: IO () + createBlockHistoryTable = + Pact4.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));" + + createTableCreationTable :: IO () + createTableCreationTable = + Pact4.exec_ sql + "CREATE TABLE IF NOT EXISTS VersionedTableCreation\ + \(tablename TEXT NOT NULL\ + \, createBlockheight UNSIGNED BIGINT NOT NULL\ + \, CONSTRAINT creation_unique UNIQUE(createBlockheight, tablename));" + + createTableMutationTable :: IO () + createTableMutationTable = + Pact4.exec_ sql + "CREATE TABLE IF NOT EXISTS VersionedTableMutation\ + \(tablename TEXT NOT NULL\ + \, blockheight UNSIGNED BIGINT NOT NULL\ + \, CONSTRAINT mutation_unique UNIQUE(blockheight, tablename));" + + createTransactionIndexTable :: IO () + createTransactionIndexTable = do + Pact4.exec_ sql + "CREATE TABLE IF NOT EXISTS TransactionIndex \ + \ (txhash BLOB NOT NULL, \ + \ blockheight UNSIGNED BIGINT NOT NULL, \ + \ CONSTRAINT transactionIndexConstraint UNIQUE(txhash));" + Pact4.exec_ sql + "CREATE INDEX IF NOT EXISTS \ + \ transactionIndexByBH ON TransactionIndex(blockheight)"; + +getEndTxId :: Text -> SQLiteEnv -> Maybe ParentHeader -> 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) + +getEndTxId' :: Text -> SQLiteEnv -> BlockHeight -> BlockHash -> IO (Historical Pact4.TxId) +getEndTxId' msg sql bh bhsh = do + r <- Pact4.qry sql + "SELECT endingtxid FROM BlockHistory WHERE blockheight = ? and hash = ?;" + [ Pact4.SInt $ fromIntegral bh + , Pact4.SBlob $ runPutS (encodeBlockHash bhsh) + ] + [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 + + +-- | Delete any state from the database newer than the input parent header. +-- Returns the ending txid of the input parent header. +rewindDbTo + :: SQLiteEnv + -> Maybe ParentHeader + -> IO Pact4.TxId +rewindDbTo db Nothing = do + rewindDbToGenesis db + return 0 +rewindDbTo db mh@(Just (ParentHeader ph)) = do + !historicalEndingTxId <- getEndTxId "rewindDbToBlock" db mh + endingTxId <- case historicalEndingTxId of + NoHistory -> + throwM + $ BlockHeaderLookupFailure + $ "rewindDbTo.getEndTxId: not in db: " + <> sshow ph + Historical endingTxId -> + return endingTxId + rewindDbToBlock db (view blockHeight ph) 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] + forM_ tblNames $ \t -> case t of + [Pact4.SText tn] -> Pact4.exec_ db ("DROP TABLE [" <> tn <> "];") + _ -> internalError "Something went wrong when resetting tables." + Pact4.exec_ db "DELETE FROM VersionedTableCreation;" + Pact4.exec_ db "DELETE FROM VersionedTableMutation;" + Pact4.exec_ db "DELETE FROM TransactionIndex;" + +-- | Rewind the database to a particular block, given the end tx id of that +-- block. +rewindDbToBlock + :: Database + -> BlockHeight + -> Pact4.TxId + -> IO () +rewindDbToBlock db bh endingTxId = do + tableMaintenanceRowsVersionedSystemTables + droppedtbls <- dropTablesAtRewind + vacuumTablesAtRewind droppedtbls + deleteHistory + clearTxIndex + where + dropTablesAtRewind :: IO (HashSet BS.ByteString) + dropTablesAtRewind = do + toDropTblNames <- Pact4.qry db findTablesToDropStmt + [Pact4.SInt (fromIntegral bh)] [Pact4.RText] + tbls <- fmap HashSet.fromList . forM toDropTblNames $ \case + [Pact4.SText tblname@(Utf8 tn)] -> do + Pact4.exec_ db $ "DROP TABLE IF EXISTS " <> tbl tblname + return tn + _ -> internalError rewindmsg + Pact4.exec' db + "DELETE FROM VersionedTableCreation WHERE createBlockheight > ?" + [Pact4.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 = + Pact4.exec' db "DELETE FROM BlockHistory WHERE blockheight > ?" + [Pact4.SInt (fromIntegral bh)] + + vacuumTablesAtRewind :: HashSet BS.ByteString -> IO () + 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 \ + \of the table to possibly vacuum." + mutatedTables <- Pact4.qry db + "SELECT DISTINCT tablename FROM VersionedTableMutation WHERE blockheight > ?;" + [Pact4.SInt (fromIntegral bh)] + [Pact4.RText] + >>= processMutatedTables + let toVacuumTblNames = HashSet.difference mutatedTables droppedtbls + forM_ toVacuumTblNames $ \tblname -> + Pact4.exec' db ("DELETE FROM " <> tbl (Utf8 tblname) <> " WHERE txid >= ?") + [Pact4.SInt $! fromIntegral endingTxId] + Pact4.exec' db "DELETE FROM VersionedTableMutation WHERE blockheight > ?;" + [Pact4.SInt (fromIntegral bh)] + + tableMaintenanceRowsVersionedSystemTables :: 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 + where + tx = [Pact4.SInt $! fromIntegral endingTxId] + + -- | Delete all future transactions from the index + clearTxIndex :: IO () + clearTxIndex = + Pact4.exec' db "DELETE FROM TransactionIndex WHERE blockheight > ?;" + [ Pact4.SInt (fromIntegral bh) ] diff --git a/src/Chainweb/Pact/Conversion.hs b/src/Chainweb/Pact/Conversion.hs new file mode 100644 index 0000000000..4003bdf572 --- /dev/null +++ b/src/Chainweb/Pact/Conversion.hs @@ -0,0 +1,96 @@ +{-# LANGUAGE ScopedTypeVariables, LambdaCase, BangPatterns, TupleSections, OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Chainweb.Pact.Conversion + ( fromLegacyQualifiedName + , fromLegacyPactValue) + where + +import qualified Pact.Types.Term as Legacy +import qualified Pact.Types.Exp as Legacy +import qualified Pact.Types.PactValue as Legacy +import qualified Data.Set as S +import qualified Data.Map.Strict as M + +import Pact.Core.ModRefs +import Pact.Core.Literal +import Pact.Core.Names +import Pact.Core.Guards +import Pact.Core.PactValue + + +fromLegacyQualifiedName + :: Legacy.QualifiedName + -> QualifiedName +fromLegacyQualifiedName (Legacy.QualifiedName mn n _) = + QualifiedName n (fromLegacyModuleName mn) + +fromLegacyLiteral + :: Legacy.Literal + -> Either Literal PactValue +fromLegacyLiteral = \case + Legacy.LString s -> Left (LString s) + Legacy.LInteger i -> Left (LInteger i) + Legacy.LDecimal d -> Left (LDecimal d) + Legacy.LBool b -> Left (LBool b) + Legacy.LTime l -> Right $ PTime l + +fromLegacyPactId + :: Legacy.PactId + -> DefPactId +fromLegacyPactId (Legacy.PactId pid) = DefPactId pid + +fromLegacyPactValue :: Legacy.PactValue -> Either String PactValue +fromLegacyPactValue = \case + Legacy.PLiteral l -> pure $ either PLiteral id $ fromLegacyLiteral l + Legacy.PList p -> do + l <- traverse fromLegacyPactValue p + pure (PList l) + Legacy.PObject (Legacy.ObjectMap om) -> do + om' <- traverse fromLegacyPactValue om + pure (PObject $ M.mapKeys (\(Legacy.FieldKey k) -> Field k) om') + Legacy.PGuard g -> case g of + Legacy.GPact (Legacy.PactGuard p n) -> let + p' = fromLegacyPactId p + in pure (PGuard (GDefPactGuard (DefPactGuard p' n))) + Legacy.GKeySet (Legacy.KeySet k pred') -> let + ks = S.map (PublicKeyText . Legacy._pubKey) k + p' = \case + (Legacy.Name (Legacy.BareName bn _def)) + | bn == "keys-all" -> pure KeysAll + | bn == "keys-any" -> pure KeysAny + | bn == "keys-2" -> pure Keys2 + (Legacy.Name (Legacy.BareName bn _def)) -> pure (CustomPredicate (TBN $ BareName bn)) + (Legacy.QName qn) -> pure (CustomPredicate (TQN $ fromLegacyQualifiedName qn)) + o -> Left $ "fromLegacyPactValue: pred invariant: " <> show o + in (PGuard . GKeyset . KeySet ks <$> p' pred') + Legacy.GKeySetRef (Legacy.KeySetName ksn ns) -> let + ns' = fromLegacyNamespaceName <$> ns + in pure (PGuard . GKeySetRef $ KeySetName ksn ns') + Legacy.GModule (Legacy.ModuleGuard mn n) -> let + mn' = fromLegacyModuleName mn + in pure (PGuard $ GModuleGuard (ModuleGuard mn' n)) + Legacy.GUser (Legacy.UserGuard n a) -> case n of + Legacy.QName n' -> do + let qn = fromLegacyQualifiedName n' + args <- traverse fromLegacyPactValue a + pure (PGuard $ GUserGuard (UserGuard qn args)) + _ -> Left "fromLegacyPactValue: invariant" + Legacy.GCapability (Legacy.CapabilityGuard n a i) -> do + let qn = fromLegacyQualifiedName n + args <- traverse fromLegacyPactValue a + pure (PGuard $ GCapabilityGuard (CapabilityGuard qn args (fromLegacyPactId <$> i))) + Legacy.PModRef (Legacy.ModRef mn mmn _) -> let + mn' = fromLegacyModuleName mn + imp = S.fromList $ maybe [] (map fromLegacyModuleName) mmn + in pure (PModRef $ ModRef mn' imp) + + +fromLegacyNamespaceName :: Legacy.NamespaceName -> NamespaceName +fromLegacyNamespaceName (Legacy.NamespaceName ns) = NamespaceName ns + +fromLegacyModuleName + :: Legacy.ModuleName + -> ModuleName +fromLegacyModuleName (Legacy.ModuleName n ns) + = ModuleName n (fromLegacyNamespaceName <$> ns) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 60fd8c6071..f32b117b5f 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -40,7 +40,6 @@ module Chainweb.Pact.PactService , runPactService , withPactService , execNewGenesisBlock - , getGasModel ) where import Control.Concurrent hiding (throwTo) @@ -52,27 +51,20 @@ import Control.Lens hiding ((:>)) import Control.Monad import Control.Monad.Reader import Control.Monad.State.Strict -import Control.Monad.Primitive (PrimState) -import qualified Data.DList as DL import Data.Either import Data.Foldable (toList) import Data.IORef import qualified Data.HashMap.Strict as HM import Data.LogMessage -import qualified Data.Map.Strict as M import Data.Maybe import Data.Monoid -import qualified Data.Set as S import Data.Text (Text) -import qualified Data.Text as T +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 Data.Word (Word64) -import GrowableVector.Lifted (Vec) -import GrowableVector.Lifted qualified as Vec import System.IO import System.LogLevel @@ -82,15 +74,21 @@ import Prelude hiding (lookup) import qualified Streaming as Stream import qualified Streaming.Prelude as Stream -import qualified Pact.Gas as P -import Pact.Gas.Table +import qualified Pact.Gas as Pact4 import Pact.Interpreter(PactDbEnv(..)) import qualified Pact.JSON.Encode as J -import qualified Pact.Types.Command as P -import qualified Pact.Types.Hash as P -import qualified Pact.Types.RowData as P -import qualified Pact.Types.Runtime as P -import qualified Pact.Types.Pretty as P +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 Chainweb.Pact4.TransactionExec as Pact4 +import qualified Chainweb.Pact4.Validations as Pact4 import Chainweb.BlockHash import Chainweb.BlockHeader @@ -100,20 +98,17 @@ import Chainweb.ChainId import Chainweb.Logger import Chainweb.Mempool.Mempool as Mempool import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.RelationalCheckpointer (withProdRelationalCheckpointer) -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.PactService.ExecBlock -import Chainweb.Pact.PactService.Checkpointer + +import Chainweb.Pact.PactService.Pact4.ExecBlock +import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact4 import Chainweb.Pact.Service.PactQueue (PactQueue, getNextRequest) -import Chainweb.Pact.Service.Types -import Chainweb.Pact.SPV -import Chainweb.Pact.TransactionExec import Chainweb.Pact.Types -import Chainweb.Pact.Validations +import Chainweb.Pact4.SPV qualified as Pact4 +import Chainweb.Pact5.SPV qualified as Pact5 import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.TreeDB import Chainweb.Utils hiding (check) import Chainweb.Version @@ -123,6 +118,31 @@ 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 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 Data.Functor.Product +import qualified Chainweb.Pact5.TransactionExec as Pact5 +import qualified Chainweb.Pact5.Transaction as Pact5 +import Control.Monad.Except +import qualified Chainweb.Pact5.NoCoinbase as Pact5 +import qualified Pact.Parse as Pact4 +import qualified Control.Parallel.Strategies as Strategies +import qualified Chainweb.Pact5.Validations as Pact5 +import qualified Pact.Core.Errors as Pact5 +import Chainweb.Pact.Backend.Types +import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer +import Chainweb.Pact.PactService.Checkpointer (SomeBlockM(..)) + runPactService :: Logger logger @@ -140,7 +160,7 @@ runPactService -> IO () runPactService ver cid chainwebLogger txFailuresCounter reqQ mempoolAccess bhDb pdb sqlenv config = void $ withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config $ do - initialPayloadState mempoolAccess ver cid + initialPayloadState ver cid serviceRequests mempoolAccess reqQ withPactService @@ -155,15 +175,14 @@ withPactService -> PactServiceConfig -> PactServiceM logger tbl a -> IO (T2 a PactServiceState) -withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config act = - withProdRelationalCheckpointer checkpointerLogger (_pactModuleCacheLimit config) sqlenv (_pactPersistIntraBlockWrites config) ver cid $ \checkpointer -> do +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 - , _psGasModel = getGasModel , _psMinerRewards = rs , _psReorgLimit = _pactReorgLimit config , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout config @@ -172,41 +191,43 @@ withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config , _psAllowReadsInLocal = _pactAllowReadsInLocal config , _psLogger = pactServiceLogger , _psGasLogger = gasLogger <$ guard (_pactLogGas config) - , _psBlockGasLimit = _pactBlockGasLimit config + , _psBlockGasLimit = _pactNewBlockGasLimit config , _psEnableLocalTimeout = _pactEnableLocalTimeout config , _psTxFailuresCounter = txFailuresCounter + , _psTxTimeLimit = _pactTxTimeLimit config } !pst = PactServiceState mempty - when (_pactFullHistoryRequired config) $ do - mEarliestBlock <- _cpGetEarliestBlock (_cpReadCp checkpointer) - case mEarliestBlock of - Nothing -> do - pure () - Just (earliestBlockHeight, _) -> do - let gHeight = genesisHeight ver cid - when (gHeight /= earliestBlockHeight) $ do - let e = FullHistoryRequired - { _earliestBlockHeight = earliestBlockHeight - , _genesisHeight = gHeight - } - let msg = J.object - [ "details" J..= e - , "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 e - 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. -- - exitOnRewindLimitExceeded $ initializeLatestBlock (_pactUnlimitedInitialRewind config) + Checkpointer.exitOnRewindLimitExceeded $ initializeLatestBlock (_pactUnlimitedInitialRewind config) act where pactServiceLogger = setComponent "pact" chainwebLogger @@ -214,34 +235,31 @@ withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config gasLogger = addLabel ("transaction", "GasLogs") pactServiceLogger initializeLatestBlock :: (Logger logger) => CanReadablePayloadCas tbl => Bool -> PactServiceM logger tbl () -initializeLatestBlock unlimitedRewind = findLatestValidBlockHeader' >>= \case +initializeLatestBlock unlimitedRewind = Checkpointer.findLatestValidBlockHeader' >>= \case Nothing -> return () - Just b -> rewindToIncremental initialRewindLimit (ParentHeader b) + Just b -> Checkpointer.rewindToIncremental initialRewindLimit (ParentHeader b) where initialRewindLimit = RewindLimit 1000 <$ guard (not unlimitedRewind) initialPayloadState :: Logger logger => CanReadablePayloadCas tbl - => MemPoolAccess - -> ChainwebVersion + => ChainwebVersion -> ChainId -> PactServiceM logger tbl () -initialPayloadState mpa v cid +initialPayloadState v cid | v ^. versionCheats . disablePact = pure () - | otherwise = initializeCoinContract mpa v cid $ - v ^?! versionGenesis . genesisBlockPayload . onChain cid + | otherwise = initializeCoinContract v cid $ + v ^?! versionGenesis . genesisBlockPayload . atChain cid initializeCoinContract :: forall tbl logger. (CanReadablePayloadCas tbl, Logger logger) - => MemPoolAccess - -> ChainwebVersion + => ChainwebVersion -> ChainId -> PayloadWithOutputs -> PactServiceM logger tbl () -initializeCoinContract memPoolAccess v cid pwo = do - cp <- view psCheckpointer - latestBlock <- liftIO (_cpGetLatestBlock $ _cpReadCp cp) >>= \case +initializeCoinContract v cid pwo = do + latestBlock <- Checkpointer.getLatestBlock >>= \case Nothing -> return Nothing Just (_, latestHash) -> do latestHeader <- ParentHeader @@ -249,26 +267,25 @@ initializeCoinContract memPoolAccess v cid pwo = do return $ Just latestHeader case latestBlock of - Nothing -> do - logWarn "initializeCoinContract: Checkpointer returned no latest block. Starting from genesis." - validateGenesis - Just currentBlockHeader -> do - -- We check the block hash because it's more principled and - -- we don't have to compute it, so the comparison is still relatively - -- cheap. We could also check the height but that would be redundant. - if view blockHash (_parentHeader currentBlockHeader) /= view blockHash genesisHeader - then do - !mc <- readFrom (Just currentBlockHeader) readInitModules >>= \case - NoHistory -> throwM $ BlockHeaderLookupFailure - $ "initializeCoinContract: internal error: latest block not found: " <> sshow currentBlockHeader - Historical mc -> return mc - updateInitCache mc currentBlockHeader - else do - logWarn "initializeCoinContract: Starting from genesis." - validateGenesis + 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 blockHeight genesisHeader)) $ 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 $! - execValidateBlock memPoolAccess genesisHeader (CheckablePayloadWithOutputs pwo) + execValidateBlock mempty genesisHeader (CheckablePayloadWithOutputs pwo) genesisHeader :: BlockHeader genesisHeader = genesisBlockHeader v cid @@ -303,13 +320,13 @@ serviceRequests memPoolAccess reqQ = go go :: PactServiceM logger tbl () go = do PactServiceEnv{_psLogger} <- ask - logDebug "serviceRequests: wait" + 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 - logDebug $ "serviceRequests: " <> sshow msg + logDebugPact $ "serviceRequests: " <> sshow msg case msg of CloseMsg -> tryOne "execClose" statusRef $ return () @@ -332,10 +349,10 @@ serviceRequests memPoolAccess reqQ = go go ValidateBlockMsg ValidateBlockReq {..} -> do tryOne "execValidateBlock" statusRef $ - fmap fst $ trace' logFn "Chainweb.Pact.PactService.execValidateBlock" - _valBlockHeader - (\(_, g) -> fromIntegral g) - (execValidateBlock memPoolAccess _valBlockHeader _valCheckablePayload) + 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" () @@ -347,7 +364,7 @@ serviceRequests memPoolAccess reqQ = go trace logFn "Chainweb.Pact.PactService.execPreInsertCheckReq" () (length txs) $ tryOne "execPreInsertCheckReq" statusRef $ - V.map (() <$) <$> execPreInsertCheckReq txs + execPreInsertCheckReq txs go BlockTxHistoryMsg (BlockTxHistoryReq bh d) -> do trace logFn "Chainweb.Pact.PactService.execBlockTxHistory" bh 1 $ @@ -379,15 +396,15 @@ serviceRequests memPoolAccess reqQ = go evalPactOnThread `catches` [ Handler $ \(e :: SomeException) -> do - logError $ mconcat + logErrorPact $ mconcat [ "Received exception running pact service (" , which , "): " , sshow e ] liftIO $ throwIO e - ] - where + ] + where -- here we start a thread to service the request evalPactOnThread :: PactServiceM logger tbl () evalPactOnThread = do @@ -446,310 +463,176 @@ serviceRequests memPoolAccess reqQ = go RequestNotStarted -> internalError "request not started after starting" ) case maybeException of - Left (fromException -> Just AsyncCancelled) -> - logDebug "Pact action was cancelled" - Left (fromException -> Just ThreadKilled) -> - logWarn "Pact action thread was killed" - Left (exn :: SomeException) -> - logError $ mconcat - [ "Received exception running pact service (" - , which - , "): " - , sshow exn - ] - Right () -> return () + 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 - -> Miner - -> NewBlockFill - -> ParentHeader - -> PactServiceM logger tbl (Historical BlockInProgress) + :: 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 - readFrom (Just newBlockParent) $ do - blockDbEnv <- view psBlockDbEnv - let pHeight = view blockHeight $ _parentHeader newBlockParent - let pHash = view blockHash $ _parentHeader newBlockParent - liftPactServiceM $ - logInfo $ "(parent height = " <> sshow pHeight <> ")" - <> " (parent hash = " <> sshow pHash <> ")" - blockGasLimit <- view (psServiceEnv . psBlockGasLimit) - initCache <- initModuleCacheForBlock False - coinbaseOutput <- - runCoinbase False miner (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled True) initCache - finalBlockState <- fmap _benvBlockState - $ liftIO - $ readMVar - $ pdPactDbVar - $ _cpPactDbEnv blockDbEnv - let blockInProgress = BlockInProgress - { _blockInProgressModuleCache = initCache - -- ^ we do not use the module cache populated by coinbase in - -- subsequent transactions - , _blockInProgressPendingData = _bsPendingBlock finalBlockState - , _blockInProgressTxId = _bsTxId finalBlockState - , _blockInProgressParentHeader = newBlockParent - , _blockInProgressRemainingGasLimit = blockGasLimit - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = coinbaseOutput - , _transactionPairs = mempty - } - , _blockInProgressMiner = miner - } - case fill of - NewBlockFill -> continueBlock mpAccess blockInProgress - NewBlockEmpty -> return blockInProgress + 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. (Logger logger, CanReadablePayloadCas tbl) + :: forall logger tbl pv. (Logger logger, CanReadablePayloadCas tbl) => MemPoolAccess - -> BlockInProgress - -> PactServiceM logger tbl (Historical BlockInProgress) + -> BlockInProgress pv + -> PactServiceM logger tbl (Historical (BlockInProgress pv)) execContinueBlock mpAccess blockInProgress = pactLabel "execNewBlock" $ do - readFrom (Just newBlockParent) $ continueBlock mpAccess blockInProgress + 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 --- | Note: The ParentHeader param here is the PARENT HEADER of the new --- block-to-be. --- -continueBlock - :: forall logger tbl - . (Logger logger, CanReadablePayloadCas tbl) - => MemPoolAccess - -> BlockInProgress - -> PactBlockM logger tbl BlockInProgress -continueBlock mpAccess blockInProgress = do - updateMempool - liftPactServiceM $ - logInfo $ "(parent height = " <> sshow pHeight <> ")" - <> " (parent hash = " <> sshow pHash <> ")" - - blockDbEnv <- view psBlockDbEnv - -- restore the block state from the block being continued - liftIO $ - modifyMVar_ (pdPactDbVar $ _cpPactDbEnv blockDbEnv) $ \blockEnv -> - return - $! blockEnv - & benvBlockState . bsPendingBlock .~ _blockInProgressPendingData blockInProgress - & benvBlockState . bsTxId .~ _blockInProgressTxId blockInProgress - - blockGasLimit <- view (psServiceEnv . psBlockGasLimit) - - let - txTimeHeadroomFactor :: Double - txTimeHeadroomFactor = 5 - -- 2.5 microseconds per unit gas - txTimeLimit :: Micros - txTimeLimit = round $ (2.5 * txTimeHeadroomFactor) * fromIntegral blockGasLimit - - let 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 $ requestKeyToTransactionHash . P._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 $ logInfo $ "(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 - $ _cpPactDbEnv blockDbEnv - let !blockInProgress' = BlockInProgress - { _blockInProgressModuleCache = finalModuleCache - , _blockInProgressPendingData = _bsPendingBlock finalBlockState - , _blockInProgressTxId = _bsTxId finalBlockState - , _blockInProgressParentHeader = newBlockParent - , _blockInProgressRemainingGasLimit = finalGasLimit - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = cb - , _transactionPairs = txs - } - , _blockInProgressMiner = _blockInProgressMiner blockInProgress - } - return blockInProgress' - where - newBlockParent = _blockInProgressParentHeader blockInProgress - - !parentTime = - ParentCreationTime (view blockCreationTime $ _parentHeader newBlockParent) - - getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector ChainwebTransaction) - getBlockTxs bfState = do - dbEnv <- view psBlockDbEnv - psEnv <- ask - logger <- view (psServiceEnv . psLogger) - let validate bhi _bha txs = do - results <- do - let v = _chainwebVersion psEnv - cid = _chainId psEnv - validateChainwebTxs logger v cid dbEnv parentTime bhi txs return - - V.forM results $ \case - Right _ -> return True - Left _e -> return False - - liftIO $! - mpaGetBlock mpAccess bfState validate (pHeight + 1) pHash (_parentHeader newBlockParent) - - refill - :: Word64 - -> Micros - -> GrowableVec (ChainwebTransaction, P.CommandResult [P.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 $ logDebug $ "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 - logInfo $ "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' <- - execTransactionsOnly - (_blockInProgressMiner blockInProgress) - newTrans - mc - (Just txTimeLimit) - - 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 (ChainwebTransaction, P.CommandResult [P.TxLogJson]) - -> GrowableVec TransactionHash -- ^ failed txs - -> BlockFill - -> [(ChainwebTransaction, Either CommandInvalidError (P.CommandResult [P.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 (requestKeyToTransactionHash $ P._crReqKey cr) - -- Decrement actual gas used from block limit - let !g' = g - fromIntegral (P._crGas cr) - liftIO $ Vec.push successes (t, cr) - go (BlockFill g' rks' i) rest - Left (CommandInvalidGasPurchaseFailure (GasPurchaseFailure 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 $ logError $ "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 - - pHeight = view blockHeight $ _parentHeader newBlockParent - pHash = view blockHash $ _parentHeader newBlockParent - - updateMempool = liftIO $ do - mpaProcessFork mpAccess $ _parentHeader newBlockParent - mpaSetLastHeader mpAccess $ _parentHeader newBlockParent - -type GrowableVec = Vec (PrimState IO) - --- | only for use in generating genesis blocks in tools +-- | only for use in generating genesis blocks in tools. -- execNewGenesisBlock :: (Logger logger, CanReadablePayloadCas tbl) => Miner - -> Vector ChainwebTransaction + -> Vector Pact4.UnparsedTransaction -> PactServiceM logger tbl PayloadWithOutputs execNewGenesisBlock miner newTrans = pactLabel "execNewGenesisBlock" $ do - historicalBlock <- readFrom Nothing $ do - -- NEW GENESIS COINBASE: Reject bad coinbase, use date rule for precompilation - results <- execTransactions True miner newTrans - (EnforceCoinbaseFailure True) - (CoinbaseUsePrecompiled False) Nothing Nothing - >>= throwCommandInvalidError - return $! toPayloadWithOutputs miner results + 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 + NoHistory -> internalError "PactService.execNewGenesisBlock: Impossible error, unable to rewind before genesis" + Historical block -> return block execReadOnlyReplay :: forall logger tbl @@ -758,7 +641,7 @@ execReadOnlyReplay -> Maybe BlockHeader -> PactServiceM logger tbl () execReadOnlyReplay lowerBound maybeUpperBound = pactLabel "execReadOnlyReplay" $ do - ParentHeader cur <- findLatestValidBlockHeader + ParentHeader cur <- Checkpointer.findLatestValidBlockHeader logger <- view psLogger bhdb <- view psBlockHeaderDb pdb <- view psPdb @@ -789,21 +672,21 @@ execReadOnlyReplay lowerBound maybeUpperBound = pactLabel "execReadOnlyReplay" $ 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 + 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 + :: 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 @@ -815,19 +698,21 @@ execReadOnlyReplay lowerBound maybeUpperBound = pactLabel "execReadOnlyReplay" $ logFunctionText logger Error m printValidationError e = throwM e handleMissingBlock NoHistory = throwM $ BlockHeaderLookupFailure $ - "execReadOnlyReplay: missing block: " <> sshow bh + "execReadOnlyReplay: missing block: " <> sshow bh handleMissingBlock (Historical ()) = return () - handle printValidationError + payload <- liftIO $ fromJuste <$> + lookupPayloadDataWithHeight pdb (Just $ view blockHeight bh) (view blockPayloadHash bh) + let isPayloadEmpty = V.null (view payloadDataTransactions 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 - $ readFrom (Just $ ParentHeader bhParent) $ do - liftIO $ writeIORef heightRef (view blockHeight bh) - payload <- liftIO $ fromJuste <$> - lookupPayloadWithHeight pdb (Just $ view blockHeight bh) (view blockPayloadHash bh) - let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload) - let isUpgradeBlock = isJust $ _chainwebVersion bhdb ^? versionUpgrades . onChain (_chainId bhdb) . ix (view blockHeight bh) - unless (isPayloadEmpty && not isUpgradeBlock) $ - void $ execBlock bh (CheckablePayloadWithOutputs payload) + $ Checkpointer.readFrom (Just $ ParentHeader bhParent) $ + SomeBlockM $ Pair + (void $ Pact4.execBlock bh (CheckablePayload payload)) + (void $ Pact5.execExistingBlock bh (CheckablePayload payload)) ) validationFailed <- readIORef validationFailedRef when validationFailed $ @@ -840,45 +725,45 @@ execReadOnlyReplay lowerBound maybeUpperBound = pactLabel "execReadOnlyReplay" $ 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 - $ T.pack $ printf "height: %d | rate: %.1f blocks/sec | est. %s" - (fromIntegral @BlockHeight @Int $ currentHeight - initialHeight) - blocksPerSecond - (formatShow iso8601Format est) + 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) - => ChainwebTransaction + => Pact4.UnparsedTransaction -> Maybe LocalPreflightSimulation - -- ^ preflight flag + -- ^ preflight flag -> Maybe LocalSignatureVerification - -- ^ turn off signature verification checks? + -- ^ turn off signature verification checks? -> Maybe RewindDepth - -- ^ rewind depth + -- ^ rewind depth -> PactServiceM logger tbl LocalResult execLocal cwtx preflight sigVerify rdepth = pactLabel "execLocal" $ do PactServiceEnv{..} <- ask - let !cmd = payloadObj <$> cwtx - !pm = publicMetaOf cmd + let !cmd = Pact4.payloadObj <$> cwtx + !pm = Pact4.publicMetaOf cmd bhdb <- view psBlockHeaderDb @@ -888,82 +773,156 @@ execLocal cwtx preflight sigVerify rdepth = pactLabel "execLocal" $ do let rewindDepth = maybe 0 _rewindDepth rdepth let timeoutLimit - | _psEnableLocalTimeout = Just (2 * 1_000_000) - | otherwise = Nothing + | _psEnableLocalTimeout = Just (2 * 1_000_000) + | otherwise = Nothing - let act = readFromNthParent (fromIntegral rewindDepth) $ do + let act = Checkpointer.readFromNthParent (fromIntegral rewindDepth) $ SomeBlockM $ Pair + (do pc <- view psParentHeader - let spv = pactSPV bhdb (_parentHeader pc) - ctx <- getTxContext pm - let gasModel = getGasModel ctx - mc <- getInitCache - dbEnv <- view psBlockDbEnv - - -- - -- 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 - liftPactServiceM (assertLocalMetadata cmd ctx sigVerify) >>= \case - Right{} -> do - let initialGas = initialGasOf $ P._cmdPayload cwtx - T3 cr _mc warns <- liftIO $ applyCmd - _psVersion _psLogger _psGasLogger Nothing (_cpPactDbEnv dbEnv) - noMiner gasModel ctx spv cmd - initialGas mc ApplyLocal - - let cr' = toHashCommandResult cr - warns' = P.renderCompactText <$> toList warns - pure $ LocalResultWithWarns cr' warns' - Left e -> pure $ MetadataValidationFailure e - _ -> liftIO $ do - let execConfig = P.mkExecutionConfig $ - [ P.FlagAllowReadInLocal | _psAllowReadsInLocal ] ++ - enablePactEvents' (_chainwebVersion ctx) (_chainId ctx) (ctxCurrentBlockHeight ctx) ++ - enforceKeysetFormats' (_chainwebVersion ctx) (_chainId ctx) (ctxCurrentBlockHeight ctx) ++ - disableReturnRTC (_chainwebVersion ctx) (_chainId ctx) (ctxCurrentBlockHeight ctx) - - cr <- applyLocal - _psLogger _psGasLogger (_cpPactDbEnv dbEnv) - gasModel ctx spv - cwtx mc execConfig - - let cr' = toHashCommandResult cr - pure $ LocalResultLegacy cr' + let spv = Pact4.pactSPV bhdb (_parentHeader pc) + ctx <- Pact4.getTxContext noMiner pm + let gasModel = Pact4.getGasModel ctx + mc <- Pact4.getInitCache + dbEnv <- Pact4._cpPactDbEnv <$> view psBlockDbEnv + logger <- view (psServiceEnv . psLogger) + liftIO (runExceptT + (Pact4.checkParse logger (_chainwebVersion pc) (_chainId pc) (succ (view blockHeight $ _parentHeader pc)) cwtx)) + >>= \case + Left err -> + let + parseError = Pact4.CommandResult + { _crReqKey = Pact4.RequestKey (Pact4.toUntypedHash $ Pact4._cmdHash cmd) + , _crTxId = Nothing + , _crResult = Pact4.PactResult (Left (Pact4.PactError Pact4.SyntaxError Pact4.noInfo [] (sshow err))) + , _crGas = cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit . to fromIntegral + , _crLogs = Nothing + , _crContinuation = Nothing + , _crMetaData = Nothing + , _crEvents = [] + } + in case preflight of + Just PreflightSimulation -> return $ LocalResultWithWarns parseError [] + _ -> return $ LocalResultLegacy parseError + Right pact4Cwtx -> do + + -- + -- 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 + Pact4.liftPactServiceM (Pact4.assertLocalMetadata cmd ctx sigVerify) >>= \case + Right{} -> do + let initialGas = Pact4.initialGasOf $ Pact4._cmdPayload pact4Cwtx + T3 cr _mc warns <- liftIO $ Pact4.applyCmd + _psVersion _psLogger _psGasLogger Nothing dbEnv + noMiner gasModel ctx spv (Pact4.payloadObj <$> pact4Cwtx) + initialGas mc ApplyLocal + + let cr' = hashPact4TxLogs cr + warns' = Pact4.renderCompactText <$> toList warns + pure $ LocalResultWithWarns cr' warns' + Left e -> pure $ MetadataValidationFailure e + _ -> liftIO $ do + let execConfig = Pact4.mkExecutionConfig $ + [ Pact4.FlagAllowReadInLocal | _psAllowReadsInLocal ] ++ + Pact4.enablePactEvents' (_chainwebVersion ctx) (_chainId ctx) (Pact4.ctxCurrentBlockHeight ctx) ++ + Pact4.enforceKeysetFormats' (_chainwebVersion ctx) (_chainId ctx) (Pact4.ctxCurrentBlockHeight ctx) ++ + Pact4.disableReturnRTC (_chainwebVersion ctx) (_chainId ctx) (Pact4.ctxCurrentBlockHeight ctx) + + cr <- Pact4.applyLocal + _psLogger _psGasLogger dbEnv + gasModel ctx spv + pact4Cwtx mc execConfig + + let cr' = hashPact4TxLogs cr + pure $ LocalResultLegacy cr' + ) (do + ph <- view psParentHeader + case Pact5.parsePact4Command cwtx of + Left (fmap Pact5.spanInfoToLineInfo -> parseError) -> + return $ LocalPact5PreflightResult Pact5.CommandResult + { _crReqKey = Pact5.RequestKey (Pact5.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) + , _crTxId = Nothing + , _crResult = Pact5.PactResultErr $ Pact5.PELegacyError $ Pact5.toPrettyLegacyError parseError + , _crGas = Pact5.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit + , _crLogs = Nothing + , _crContinuation = Nothing + , _crMetaData = Nothing + , _crEvents = [] + } + [] + Right pact5Cmd -> do + let txCtx = Pact5.TxContext ph noMiner + let spvSupport = Pact5.pactSPV bhdb (_parentHeader ph) + case preflight of + Just PreflightSimulation -> + Pact5.liftPactServiceM (Pact5.assertLocalMetadata (view Pact5.payloadObj <$> pact5Cmd) txCtx sigVerify) >>= \case + Left e -> pure $ MetadataValidationFailure e + Right () -> do + let initialGas = Pact5.initialGasOf $ Pact5._cmdPayload pact5Cmd + Pact5.pactTransaction Nothing (\dbEnv -> + Pact5.applyCmd + _psLogger _psGasLogger dbEnv + txCtx spvSupport initialGas (view Pact5.payloadObj <$> pact5Cmd) + ) >>= \case + Left err -> + return $ LocalPact5PreflightResult Pact5.CommandResult + { _crReqKey = Pact5.RequestKey (Pact5.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) + , _crTxId = Nothing + -- FIXME: Pact5, make this nicer, the `sshow` makes for an ugly error + , _crResult = Pact5.PactResultErr $ Pact5.PELegacyError $ + Pact5.LegacyPactError Pact5.LegacyGasError "" [] ("Gas error: " <> sshow err) + , _crGas = Pact5.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit + , _crLogs = Nothing + , _crContinuation = Nothing + , _crMetaData = Nothing + , _crEvents = [] + } + [] + Right cr -> do + let cr' = hashPact5TxLogs cr + -- FIXME: Pact5, no warnings yet + pure $ LocalPact5PreflightResult (Pact5.PELegacyError . Pact5.toPrettyLegacyError <$> cr') [] + _ -> do + cr <- Pact5.pactTransaction Nothing $ \dbEnv -> do + fmap convertPact5Error <$> Pact5.applyLocal _psLogger _psGasLogger dbEnv txCtx spvSupport (view Pact5.payloadObj <$> pact5Cmd) + pure $ LocalPact5ResultLegacy (hashPact5TxLogs cr) + + ) case timeoutLimit of - Nothing -> act - Just limit -> withPactState $ \run -> timeoutYield limit (run act) >>= \case - Just r -> pure r - Nothing -> do - logError_ _psLogger $ "Local action timed out for cwtx:\n" <> sshow cwtx - pure LocalTimeout + Nothing -> act + Just limit -> withPactState $ \run -> timeoutYield limit (run act) >>= \case + Just r -> pure r + Nothing -> do + logError_ _psLogger $ "Local action timed out for cwtx:\n" <> sshow cwtx + pure LocalTimeout execSyncToBlock :: (CanReadablePayloadCas tbl, Logger logger) => BlockHeader -> PactServiceM logger tbl () execSyncToBlock targetHeader = pactLabel "execSyncToBlock" $ do - latestHeader <- findLatestValidBlockHeader' >>= maybe failNonGenesisOnEmptyDb return - if latestHeader == targetHeader - then do - logInfo $ "checkpointer at checkpointer target" - <> ". target height: " <> sshow (view blockHeight latestHeader) - <> "; target hash: " <> blockHashToText (view blockHash latestHeader) - else do - logInfo $ "rewind to checkpointer target" - <> ". current height: " <> sshow (view blockHeight latestHeader) - <> "; current hash: " <> blockHashToText (view blockHash latestHeader) - <> "; target height: " <> sshow targetHeight - <> "; target hash: " <> blockHashToText targetHash - rewindToIncremental Nothing (ParentHeader targetHeader) - where - targetHeight = view blockHeight targetHeader - targetHash = view blockHash targetHeader - failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" + 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. @@ -979,7 +938,7 @@ execValidateBlock => MemPoolAccess -> BlockHeader -> CheckablePayload - -> PactServiceM logger tbl (PayloadWithOutputs, P.Gas) + -> PactServiceM logger tbl (PayloadWithOutputs, Pact4.Gas) execValidateBlock memPoolAccess headerToValidate payloadToValidate = pactLabel "execValidateBlock" $ do bhdb <- view psBlockHeaderDb payloadDb <- view psPdb @@ -992,10 +951,10 @@ execValidateBlock memPoolAccess headerToValidate payloadToValidate = pactLabel " -- Add block-hash to the logs if presented let logBlockHash = - localLabel ("block-hash", blockHashToText (view blockParent headerToValidate)) + localLabelPact ("block-hash", blockHashToText (view blockParent headerToValidate)) logBlockHash $ do - currHeader <- findLatestValidBlockHeader' + 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) -> @@ -1029,35 +988,43 @@ execValidateBlock memPoolAccess headerToValidate payloadToValidate = pactLabel " -- given a header for a block in the fork, fetch its payload -- and run its transactions, validating its hashes - let runForkBlockHeaders = Stream.map (\forkBh -> do + 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 - void $ execBlock forkBh (CheckablePayload payload) + SomeBlockM $ Pair + (void $ Pact4.execBlock forkBh (CheckablePayload payload)) + (void $ Pact5.execExistingBlock forkBh (CheckablePayload payload)) return ([], forkBh) - ) forkBlockHeaders + ) -- run the new block, the one we're validating, and -- validate its hashes - let runThisBlock = Stream.yield $ do - !output <- execBlock headerToValidate payloadToValidate - return ([output], headerToValidate) + 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 $ restoreAndSave + 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 logWarn - else logDebug + then logWarnPact + else logDebugPact logRewind $ "execValidateBlock: rewound " <> sshow (currHeight - ancestorHeight) <> " blocks" (totalGasUsed, result) <- case results of @@ -1082,7 +1049,7 @@ execValidateBlock memPoolAccess headerToValidate payloadToValidate = pactLabel " return (result, totalGasUsed) - where + where getTarget | isGenesisBlockHeader headerToValidate = return Nothing | otherwise = Just . ParentHeader @@ -1094,161 +1061,153 @@ execValidateBlock memPoolAccess headerToValidate payloadToValidate = pactLabel " execBlockTxHistory :: Logger logger => BlockHeader - -> P.Domain P.RowKey P.RowData + -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info -> PactServiceM logger tbl (Historical BlockTxHistory) -execBlockTxHistory bh d = pactLabel "execBlockTxHistory" $ do - !cp <- view psCheckpointer - liftIO $ _cpGetBlockHistory (_cpReadCp cp) bh d +execBlockTxHistory bh d = + pactLabel "execBlockTxHistory" $ Checkpointer.getBlockHistory bh d execHistoricalLookup :: Logger logger => BlockHeader - -> P.Domain P.RowKey P.RowData - -> P.RowKey - -> PactServiceM logger tbl (Historical (Maybe (P.TxLog P.RowData))) -execHistoricalLookup bh d k = pactLabel "execHistoricalLookup" $ do - !cp <- view psCheckpointer - liftIO $ _cpGetHistoricalLookup (_cpReadCp cp) bh d k + -> 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 ChainwebTransaction - -> PactServiceM logger tbl (Vector (Either Mempool.InsertError ChainwebTransaction)) + => Vector Pact4.UnparsedTransaction + -> PactServiceM logger tbl (Vector (Maybe Mempool.InsertError)) execPreInsertCheckReq txs = pactLabel "execPreInsertCheckReq" $ do - let requestKeys = V.map P.cmdToRequestKey txs - logInfo $ "(request keys = " <> sshow requestKeys <> ")" + 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 = - readFromLatest $ 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 $ validateChainwebTxs logger v cid pdb parentTime currHeight txs - (evalPactServiceM psState psEnv . runPactBlockM pc pdb . attemptBuyGas noMiner) + 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 + blockHandle <- use Pact5.pbBlockHandle + let + parentTime = ParentCreationTime (view blockCreationTime $ _parentHeader ph) + currHeight = succ $ view blockHeight $ _parentHeader ph + isGenesis = False + liftIO $ forM txs $ \tx -> + fmap (either Just (\_ -> Nothing)) $ runExceptT $ do + pact5Tx <- Pact5.validateRawChainwebTx + logger v cid db blockHandle parentTime currHeight isGenesis tx + attemptBuyGasPact5 logger ph db blockHandle noMiner pact5Tx + ) withPactState $ \run -> - timeoutYield timeoutLimit (run act) >>= \case - Just r -> pure r - Nothing -> do - logError_ logger $ "Mempool pre-insert check timed out for txs:\n" <> sshow txs - pure $ V.map (const $ Left Mempool.InsertErrorTimedOut) txs + 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 + where + attemptBuyGasPact4 :: forall logger tbl. (Logger logger) => Miner - -> Vector (Either InsertError ChainwebTransaction) - -> PactBlockM logger tbl (Vector (Either InsertError ChainwebTransaction)) - attemptBuyGas miner txsOrErrs = localLabelBlock ("transaction", "attemptBuyGas") $ do - mc <- getInitCache + -> Pact4.Transaction + -> Pact4.PactBlockM logger tbl (Either InsertError ()) + attemptBuyGasPact4 miner tx = Pact4.localLabelBlock ("transaction", "attemptBuyGas") $ do + mcache <- Pact4.getInitCache l <- view (psServiceEnv . psLogger) - V.fromList . toList . sfst <$> V.foldM (buyGasFor l) (T2 mempty mc) txsOrErrs - where - buyGasFor :: logger - -> T2 (DL.DList (Either InsertError ChainwebTransaction)) ModuleCache - -> Either InsertError ChainwebTransaction - -> PactBlockM logger tbl (T2 (DL.DList (Either InsertError ChainwebTransaction)) ModuleCache) - buyGasFor _l (T2 dl mcache) err@Left {} = return (T2 (DL.snoc dl err) mcache) - buyGasFor l (T2 dl mcache) (Right tx) = do - T2 mcache' !res <- do - let cmd = payloadObj <$> tx - gasPrice = view cmdGasPrice cmd - gasLimit = fromIntegral $ view cmdGasLimit cmd - txst = TransactionState - { _txCache = mcache - , _txLogs = mempty - , _txGasUsed = 0 - , _txGasId = Nothing - , _txGasModel = P._geGasModel P.freeGasEnv - , _txWarnings = mempty - } - let !nid = networkIdOf cmd - let !rk = P.cmdToRequestKey cmd - pd <- getTxContext (publicMetaOf cmd) - bhdb <- view (psServiceEnv . psBlockHeaderDb) - dbEnv <- view psBlockDbEnv - spv <- pactSPV bhdb . _parentHeader <$> view psParentHeader - let ec = P.mkExecutionConfig $ - [ P.FlagDisableModuleInstall - , P.FlagDisableHistoryInTransactionalMode ] ++ - disableReturnRTC (ctxVersion pd) (ctxChainId pd) (ctxCurrentBlockHeight pd) - - let buyGasEnv = TransactionEnv P.Transactional (_cpPactDbEnv dbEnv) l Nothing (ctxToPublicData pd) spv nid gasPrice rk gasLimit ec Nothing Nothing - - cr <- liftIO - $! catchesPactError l CensorsUnexpectedError - $! execTransactionM buyGasEnv txst - $! buyGas pd cmd miner - - case cr of - Left err -> return (T2 mcache (Left (InsertErrorBuyGas (T.pack $ show err)))) - Right t -> return (T2 (_txCache t) (Right tx)) - pure $! T2 (DL.snoc dl res) mcache' + 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 + :: (Logger logger) + => logger + -> ParentHeader + -> Pact5.Pact5Db + -> BlockHandle + -> Miner + -> Pact5.Transaction + -> ExceptT InsertError IO () + attemptBuyGasPact5 logger ph db blockHandle miner tx = do + let logger' = addLabel ("transaction", "attemptBuyGas") logger + (result, _handle') <- liftIO $ Pact5.doPact5DbTransaction db blockHandle 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 + (tx <$) <$> Pact5.buyGas logger' gasEnv pactDb txCtx (view Pact5.payloadObj <$> tx) + either (throwError . InsertErrorBuyGas . sshow) (\_ -> pure ()) result execLookupPactTxs :: (CanReadablePayloadCas tbl, Logger logger) => Maybe ConfirmationDepth - -> Vector P.PactHash - -> PactServiceM logger tbl (HM.HashMap P.PactHash (T2 BlockHeight BlockHash)) + -> 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 - where - go = readFromNthParent (maybe 0 (fromIntegral . _confirmationDepth) confDepth) $ do - dbenv <- view psBlockDbEnv - liftIO $ _cpLookupProcessedTx dbenv txs - --- | Modified table gas module with free module loads --- -freeModuleLoadGasModel :: P.GasModel -freeModuleLoadGasModel = modifiedGasModel - where - defGasModel = tableGasModel defaultGasConfig - fullRunFunction = P.runGasModel defGasModel - modifiedRunFunction name ga = case ga of - P.GPostRead P.ReadModule {} -> P.MilliGas 0 - _ -> fullRunFunction name ga - modifiedGasModel = defGasModel { P.runGasModel = modifiedRunFunction } - -chainweb213GasModel :: P.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 = P.runGasModel defGasModel - modifiedRunFunction name ga = case ga of - P.GPostRead P.ReadModule {} -> 0 - P.GUnreduced _ts -> case M.lookup name updTable of - Just g -> g - Nothing -> unknownOperationPenalty - _ -> P.milliGasToGas $ fullRunFunction name ga - modifiedGasModel = defGasModel { P.runGasModel = \t g -> P.gasToMilliGas (modifiedRunFunction t g) } - -chainweb224GasModel :: P.GasModel -chainweb224GasModel = chainweb213GasModel - { P.runGasModel = \name -> \case - P.GPostRead P.ReadInterface {} -> P.MilliGas 0 - ga -> P.runGasModel chainweb213GasModel name ga - } - -getGasModel :: TxContext -> P.GasModel -getGasModel ctx - | guardCtx chainweb213Pact ctx = chainweb213GasModel - | guardCtx chainweb224Pact ctx = chainweb224GasModel - | otherwise = freeModuleLoadGasModel + if V.null txs then return 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) + ) pactLabel :: (Logger logger) => Text -> PactServiceM logger tbl x -> PactServiceM logger tbl x -pactLabel lbl x = localLabel ("pact-request", lbl) 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 2173798cea..1fe11e3690 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -10,6 +10,12 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE OverloadedRecordDot #-} -- | -- Module: Chainweb.Pact.PactService.Checkpointer @@ -29,6 +35,12 @@ module Chainweb.Pact.PactService.Checkpointer , findLatestValidBlockHeader , exitOnRewindLimitExceeded , rewindToIncremental + , SomeBlockM(..) + , getEarliestBlock + , getLatestBlock + , lookupHistorical + , getBlockHistory + , Internal.withCheckpointerResources ) where import Control.Concurrent @@ -39,9 +51,10 @@ import Control.Monad.Catch import Control.Monad.Reader import Control.Monad.State +import Data.Functor.Product import Data.IORef import Data.Maybe -import Data.Monoid +import Data.Monoid hiding (Product(..)) import Data.Text (Text) import GHC.Stack @@ -59,25 +72,38 @@ import qualified Streaming.Prelude as S import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.PactService.ExecBlock -import Chainweb.Pact.Service.Types + +import qualified Chainweb.Pact.PactService.Pact4.ExecBlock as Pact4 +import qualified Chainweb.Pact.PactService.Pact5.ExecBlock as Pact5 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 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 + e@RewindLimitExceeded{..} -> do killFunction <- asks (\x -> _psOnFatalError x) - liftIO $ killFunction e (J.encodeText $ msg e) + liftIO $ killFunction e (J.encodeText $ msg _rewindExceededLimit _rewindExceededLast _rewindExceededTarget) e -> throwM e - where - msg e = J.object - [ "details" J..= 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.\ @@ -86,6 +112,11 @@ 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 @@ -93,19 +124,19 @@ exitOnRewindLimitExceeded = handle $ \case -- we just keep grabbing the new "latest header" until we succeed. -- note: this function will never rewind before genesis. readFromLatest - :: Logger logger - => PactBlockM logger tbl a - -> PactServiceM logger tbl a + :: Logger logger + => SomeBlockM logger tbl a + -> PactServiceM logger tbl a readFromLatest doRead = readFromNthParent 0 doRead -- 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 - => Word - -> PactBlockM logger tbl a - -> PactServiceM logger tbl a + :: forall logger tbl a + . Logger logger + => Word + -> SomeBlockM logger tbl a + -> PactServiceM logger tbl a readFromNthParent n doRead = go 0 where go :: Int -> PactServiceM logger tbl a @@ -126,10 +157,10 @@ readFromNthParent n doRead = go 0 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. + -- 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) @@ -140,14 +171,30 @@ readFromNthParent n doRead = go 0 -- if that target block is missing, return Nothing. readFrom :: Logger logger - => Maybe ParentHeader -> PactBlockM logger tbl a -> PactServiceM logger tbl (Historical a) + => Maybe ParentHeader + -> SomeBlockM 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 - liftIO $ _cpReadFrom (_cpReadCp cp) ph $ \dbenv -> - evalPactServiceM s e $ runPactBlockM pactParent dbenv doRead + 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 + SomeBlockM (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; @@ -163,18 +210,34 @@ getPactParent ph = do -- 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) + :: (CanReadablePayloadCas tbl, Logger logger, Monoid q) + => Maybe ParentHeader + -> Stream (Of (SomeBlockM logger tbl (q, BlockHeader))) IO r + -> PactServiceM logger tbl (r, q) restoreAndSave ph blocks = do cp <- view psCheckpointer + 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 -> - _cpRestoreAndSave cp ph - (blocks & S.map (\block -> RunnableBlock $ \dbEnv mph -> runPact $ do - pactParent <- getPactParent mph - runPactBlockM pactParent dbEnv block - )) + Internal.restoreAndSave cp ph + $ blocks & S.zip (S.iterate succ firstBlockHeight) & S.map + (\case + (height, SomeBlockM (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 @@ -189,24 +252,24 @@ restoreAndSave ph blocks = do findLatestValidBlockHeader' :: (Logger logger) => PactServiceM logger tbl (Maybe BlockHeader) findLatestValidBlockHeader' = do cp <- view psCheckpointer - latestInCheckpointer <- liftIO (_cpGetLatestBlock (_cpReadCp cp)) + latestInCheckpointer <- liftIO (Internal.getLatestBlock cp.cpSql) case latestInCheckpointer of Nothing -> return Nothing - Just (height, hash) -> go height hash - where + Just (height, hash) -> Just <$> go height hash + where go height hash = do bhdb <- view psBlockHeaderDb liftIO (lookup bhdb hash) >>= \case Nothing -> do - logInfo $ "Latest block isn't valid." + logInfoPact $ "Latest block isn't valid." <> " Failed to lookup hash " <> sshow (height, hash) <> " in block header db." <> " Continuing with parent." cp <- view psCheckpointer - liftIO (_cpGetBlockParent (_cpReadCp cp) (height, hash)) >>= \case + 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 - x -> return x + Just x -> return x findLatestValidBlockHeader :: (Logger logger) => PactServiceM logger tbl ParentHeader findLatestValidBlockHeader = do @@ -242,7 +305,7 @@ rewindToIncremental rewindLimit (ParentHeader parent) = do failOnTooLowRequestedHeight latestHeader playFork latestHeader - where + where parentHeight = view blockHeight parent @@ -252,7 +315,7 @@ rewindToIncremental rewindLimit (ParentHeader parent) = do , parentHeight + 1 + limitHeight < lastHeight -> -- need to stick with addition because Word64 throwM $ RewindLimitExceeded limit (Just lastHeader) (Just parent) _ -> return () - where + where lastHeight = view blockHeight lastHeader failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" @@ -284,7 +347,6 @@ rewindToIncremental rewindLimit (ParentHeader parent) = do (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" @@ -292,9 +354,10 @@ rewindToIncremental rewindLimit (ParentHeader parent) = do <> ". Block: "<> encodeToText (ObjectEncoded blockHeader) Just x -> return $ payloadWithOutputsToPayloadData x liftIO $ writeIORef heightRef (view blockHeight blockHeader) - void $ execBlock blockHeader (CheckablePayload payload) + SomeBlockM $ Pair + (void $ Pact4.execBlock blockHeader (CheckablePayload payload)) + (void $ Pact5.execExistingBlock blockHeader (CheckablePayload payload)) return (Last (Just blockHeader), blockHeader) - -- double check output hash here? ) return $! fromJuste header :> r @@ -304,19 +367,46 @@ rewindToIncremental rewindLimit (ParentHeader parent) = do -- we have to rewind to the current header to start, for -- playChunk's invariant to be satisfied - _cpRewindTo cp + 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 + remaining + & S.copy + & S.length_ + & S.chunksOf 1000 + & foldChunksM (playChunk heightRef) curHdr when (c /= 0) $ - logInfo $ "rewindTo.playFork: replayed " <> sshow c <> " blocks" + logInfoPact $ "rewindTo.playFork: replayed " <> sshow c <> " blocks" + +getEarliestBlock :: PactServiceM logger tbl (Maybe (BlockHeight, BlockHash)) +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 + +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 -- -------------------------------------------------------------------------- -- -- Utils @@ -326,5 +416,5 @@ heightProgress initialHeight ref logFun = forever $ do threadDelay (20 * 1_000_000) h <- readIORef ref logFun - $ "processed blocks: " <> sshow (h - initialHeight) - <> ", current height: " <> sshow h + $ "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 new file mode 100644 index 0000000000..b9f1a14af9 --- /dev/null +++ b/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs @@ -0,0 +1,541 @@ +{-# 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 + , getBlockHistory + , getBlockParent + , lookupHistorical + ) 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.Pact4.Backend.ChainwebPactDb as Pact4 +import qualified Chainweb.Pact5.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.Types.Persistence as Pact4 +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 + -> 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 -} + +initCheckpointerResources + :: (Logger logger) + => DbCacheLimitBytes + -> SQLiteEnv + -> IntraBlockPersistence + -> logger + -> ChainwebVersion + -> ChainId + -> IO (Checkpointer logger) +initCheckpointerResources dbCacheLimit sql p loggr v cid = do + initSchema loggr 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 +-- 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 pv a + . (Logger logger) + => Checkpointer logger + -> Maybe ParentHeader + -> PactVersionT pv + -> (PactDbFor logger pv -> BlockHandle -> 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 + + 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 + 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 + -- 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 -> + 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) (emptyBlockHandle startTxId) + finalCache <- Pact4._bsModuleCache . Pact4._benvBlockState <$> readMVar newDbEnv + return (r, finalCache) + + Pact5T + | 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? + -- TODO: just do this inside of the chainwebPactCoreBlockDb function? + 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 + , Pact5._blockHandlerVersion = res.cpCwVersion + , Pact5._blockHandlerChainId = res.cpChainId + , Pact5._blockHandlerBlockHeight = currentHeight + , Pact5._blockHandlerMode = Pact5.Transactional + , Pact5._blockHandlerPersistIntraBlockWrites = DoNotPersistIntraBlockWrites + } + let upperBound + | parentIsLatestHeader = Nothing + | otherwise = Just (currentHeight, coerce @Pact4.TxId @Pact5.TxId startTxId) + let pactDb + = Pact5.chainwebPactBlockDb upperBound blockHandlerEnv + r <- doRead pactDb (emptyBlockHandle startTxId) + return (r, sharedModuleCache) + | otherwise -> + internalError $ + "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 ParentHeader -> 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 ParentHeader + -> 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 ParentHeader, 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 + 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 /= view blockHeight 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 $ + "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " + <> sshow (view blockHeight ph) <> ", child height " <> sshow (view blockHeight newBh) + _ -> return () + -- persist any changes to the database + PactDb.commitBlockStateToDatabase res.cpSql + (view blockHash newBh) (view blockHeight newBh) + (BlockHandle (Pact4._bsTxId nextState) (Pact4._bsPendingBlock nextState)) + return (m'', Just (ParentHeader newBh), nextTxId, nextModuleCache) + Pact5RunnableBlock 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._blockHandlerPersistIntraBlockWrites = res.cpIntraBlockPersistence + } + pactDb = Pact5.chainwebPactBlockDb Nothing 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 /= view blockHeight 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 $ + "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " + <> sshow (view blockHeight ph) <> ", child height " <> sshow (view blockHeight nextBlockHeader) + _ -> return () + PactDb.commitBlockStateToDatabase res.cpSql + (view blockHash nextBlockHeader) (view blockHeight nextBlockHeader) + blockHandle + + return (m'', Just (ParentHeader nextBlockHeader), _blockHandleTxId blockHandle, moduleCache) + + | otherwise -> internalError $ + "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 (BlockHeight, BlockHash)) +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 (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 + 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" + 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 (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 (internalError . T.pack) (return . return) $! runGetEitherS decodeBlockHash blob + [] -> internalError "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 $ ParentHeader 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 (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 (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 (Pact5.convRowKey k)] + [RText, RBlob] + case r of + [[SText key, SBlob value]] -> Just <$> Pact5.toTxLog (Pact5.renderDomain d) key value + [] -> pure Nothing + _ -> internalError $ "lookupHistorical: expected single-row result, got " <> sshow r diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs deleted file mode 100644 index 78b3ef88f7..0000000000 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ /dev/null @@ -1,639 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - --- | --- Module: Chainweb.Pact.PactService.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.ExecBlock - ( execBlock - , execTransactions - , execTransactionsOnly - , toHashCommandResult - , minerReward - , toPayloadWithOutputs - , validateChainwebTxs - , validateHashes - , throwCommandInvalidError - , initModuleCacheForBlock - , runCoinbase - , CommandInvalidError(..) - ) where - -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 qualified Data.Aeson as A -import qualified Data.Aeson.Encode.Pretty as A -import qualified Data.ByteString.Short as SB -import qualified Data.ByteString.Lazy as BL -import Data.Decimal -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 qualified Data.Text.Encoding 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 P -import qualified Pact.Types.Command as P -import Pact.Types.ExpParser (mkTextInfo, ParseEnv(..)) -import qualified Pact.Types.Hash as P -import Pact.Types.RPC -import qualified Pact.Types.Runtime as P -import qualified Pact.Types.SPV as P - -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Logger -import Chainweb.Mempool.Mempool as Mempool -import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.NoCoinbase -import Chainweb.Pact.Service.Types -import Chainweb.Pact.SPV -import Chainweb.Pact.TransactionExec -import Chainweb.Pact.Types -import Chainweb.Pact.Validations -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Time -import Chainweb.Transaction -import Chainweb.Utils hiding (check) -import Chainweb.Version -import Chainweb.Version.Guards - --- | 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 (P.Gas, PayloadWithOutputs) -execBlock currHeader payload = do - let plData = checkablePayloadToPayloadData payload - dbEnv <- view psBlockDbEnv - miner <- decodeStrictOrThrow' (_minerData $ view payloadDataMiner plData) - trans <- liftIO $ transactionsFromPayload - (pactParserVersion v (view blockChainId 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 - valids <- liftIO $ V.zip trans <$> - validateChainwebTxs logger v cid dbEnv txValidationTime - (view blockHeight currHeader) trans skipDebitGas - - case foldr handleValids [] valids of - [] -> return () - errs -> throwM $ TransactionValidationException errs - - logInitCache - - !results <- go miner trans >>= throwCommandInvalidError - - let !totalGasUsed = sumOf (folded . to P._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 - logDebug $ "execBlock: initCache: " <> sshow mc - - instr (md,_) = preview (P._MDModule . P.mHash) $ P._mdModule md - - handleValids (tx,Left e) es = (P._cmdHash tx, sshow e):es - handleValids _ es = es - - v = _chainwebVersion currHeader - cid = _chainId currHeader - - isGenesisBlock = isGenesisBlockHeader currHeader - - go m txs = if isGenesisBlock - then do - -- GENESIS VALIDATE COINBASE: Reject bad coinbase, use date rule for precompilation - execTransactions True m txs - (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled False) blockGasLimit Nothing - else do - -- VALIDATE COINBASE: back-compat allow failures, use date rule for precompilation - execTransactions False m txs - (EnforceCoinbaseFailure False) (CoinbaseUsePrecompiled False) blockGasLimit Nothing - -throwCommandInvalidError - :: Transactions (Either CommandInvalidError a) - -> PactBlockM logger tbl (Transactions a) -throwCommandInvalidError = (transactionPairs . traverse . _2) throwGasFailure - where - throwGasFailure = \case - Left (CommandInvalidGasPurchaseFailure e) -> throwM (BuyGasFailure 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 principal validation logic for groups of Pact Transactions. --- --- Skips validation for genesis transactions, since gas accounts, etc. don't --- exist yet. --- -validateChainwebTxs - :: (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> CurrentBlockDbEnv logger - -> ParentCreationTime - -- ^ reference time for tx validation. - -> BlockHeight - -- ^ Current block height - -> Vector ChainwebTransaction - -> RunGas - -> IO ValidateTxs -validateChainwebTxs logger v cid dbEnv txValidationTime bh txs doBuyGas - | bh == genesisHeight v cid = pure $! V.map Right txs - | V.null txs = pure V.empty - | otherwise = go - where - go = V.mapM validations initTxList >>= doBuyGas - - validations t = - runValid checkUnique t - >>= runValid checkTxHash - >>= runValid checkTxSigs - >>= runValid checkTimes - >>= runValid (return . checkCompile v cid bh) - - checkUnique :: ChainwebTransaction -> IO (Either InsertError ChainwebTransaction) - checkUnique t = do - found <- HashMap.lookup (P._cmdHash t) <$> _cpLookupProcessedTx dbEnv (V.singleton $ P._cmdHash t) - case found of - Nothing -> pure $ Right t - Just _ -> pure $ Left InsertErrorDuplicate - - checkTimes :: ChainwebTransaction -> IO (Either InsertError ChainwebTransaction) - checkTimes t - | skipTxTimingValidation v cid bh = - return $ Right t - | not (assertTxNotInFuture txValidationTime (payloadObj <$> t)) = - return $ Left InsertErrorTimeInFuture - | not (assertTxTimeRelativeToParent txValidationTime (payloadObj <$> t)) = - return $ Left InsertErrorTTLExpired - | otherwise = - return $ Right t - - checkTxHash :: ChainwebTransaction -> IO (Either InsertError ChainwebTransaction) - checkTxHash t = - case P.verifyHash (P._cmdHash t) (SB.fromShort $ payloadBytes $ P._cmdPayload t) of - Left _ - | doCheckTxHash v cid bh -> return $ Left InsertErrorInvalidHash - | otherwise -> do - logDebug_ logger "ignored legacy tx-hash failure" - return $ Right t - Right _ -> pure $ Right t - - - checkTxSigs :: ChainwebTransaction -> IO (Either InsertError ChainwebTransaction) - checkTxSigs t - | isRight (assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs) = pure $ Right t - | otherwise = return $ Left InsertErrorInvalidSigs - where - hsh = P._cmdHash t - sigs = P._cmdSigs t - signers = P._pSigners $ payloadObj $ P._cmdPayload t - validSchemes = validPPKSchemes v cid bh - webAuthnPrefixLegal = isWebAuthnPrefixLegal v cid bh - - initTxList :: ValidateTxs - initTxList = V.map Right txs - - runValid :: Monad m => (a -> m (Either e a)) -> Either e a -> m (Either e a) - runValid f (Right r) = f r - runValid _ l@Left{} = pure l - - -type ValidateTxs = Vector (Either InsertError ChainwebTransaction) -type RunGas = ValidateTxs -> IO ValidateTxs - -checkCompile - :: ChainwebVersion - -> ChainId - -> BlockHeight - -> ChainwebTransaction - -> Either InsertError ChainwebTransaction -checkCompile v cid bh tx = case payload of - Exec (ExecMsg parsedCode _) -> - case compileCode parsedCode of - Left perr -> Left $ InsertErrorCompilationFailed (sshow perr) - Right _ -> Right tx - _ -> Right tx - where - payload = P._pPayload $ payloadObj $ P._cmdPayload tx - compileCode p = - let e = ParseEnv (chainweb216Pact v cid bh) - in compileExps e (mkTextInfo (P._pcCode p)) (P._pcExps p) - -skipDebitGas :: RunGas -skipDebitGas = return - -execTransactions - :: (Logger logger) - => Bool - -> Miner - -> Vector ChainwebTransaction - -> EnforceCoinbaseFailure - -> CoinbaseUsePrecompiled - -> Maybe P.Gas - -> Maybe Micros - -> PactBlockM logger tbl (Transactions (Either CommandInvalidError (P.CommandResult [P.TxLogJson]))) -execTransactions isGenesis miner ctxs enfCBFail usePrecomp gasLimit timeLimit = do - mc <- initModuleCacheForBlock isGenesis - -- for legacy reasons (ask Emily) we don't use the module cache resulting - -- from coinbase to run the pact cmds - coinOut <- runCoinbase isGenesis miner enfCBFail usePrecomp mc - T2 txOuts _mcOut <- applyPactCmds isGenesis ctxs miner mc gasLimit timeLimit - return $! Transactions (V.zip ctxs txOuts) coinOut - -execTransactionsOnly - :: (Logger logger) - => Miner - -> Vector ChainwebTransaction - -> ModuleCache - -> Maybe Micros - -> PactBlockM logger tbl - (T2 (Vector (ChainwebTransaction, Either CommandInvalidError (P.CommandResult [P.TxLogJson]))) ModuleCache) -execTransactionsOnly miner ctxs mc txTimeLimit = do - T2 txOuts mcOut <- applyPactCmds False ctxs miner mc Nothing txTimeLimit - return $! T2 (V.force (V.zip ctxs txOuts)) mcOut - -initModuleCacheForBlock :: (Logger logger) => Bool -> PactBlockM logger tbl ModuleCache -initModuleCacheForBlock isGenesis = do - PactServiceState{..} <- get - pbh <- views psParentHeader (view blockHeight . _parentHeader) - case Map.lookupLE pbh _psInitCache of - Nothing -> if isGenesis - then return mempty - else do - mc <- readInitModules - updateInitCacheM mc - return mc - Just (_,mc) -> return mc - -runCoinbase - :: (Logger logger) - => Bool - -> Miner - -> EnforceCoinbaseFailure - -> CoinbaseUsePrecompiled - -> ModuleCache - -> PactBlockM logger tbl (P.CommandResult [P.TxLogJson]) -runCoinbase True _ _ _ _ = return noCoinbase -runCoinbase False miner enfCBFail usePrecomp mc = do - logger <- view (psServiceEnv . psLogger) - rs <- view (psServiceEnv . psMinerRewards) - v <- view chainwebVersion - txCtx <- getTxContext P.noPublicMeta - - let !bh = ctxCurrentBlockHeight txCtx - - reward <- liftIO $! minerReward v rs bh - dbEnv <- view psBlockDbEnv - - T2 cr upgradedCacheM <- - liftIO $ applyCoinbase v logger (_cpPactDbEnv dbEnv) miner reward txCtx enfCBFail usePrecomp mc - mapM_ upgradeInitCache upgradedCacheM - liftPactServiceM $ debugResult "runCoinbase" (P.crLogs %~ fmap J.Array $ cr) - return $! cr - - where - - upgradeInitCache newCache = do - liftPactServiceM $ logInfo "Updating init cache for upgrade" - updateInitCacheM newCache - -data CommandInvalidError - = CommandInvalidGasPurchaseFailure !GasPurchaseFailure - | 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) - => Bool - -> Vector ChainwebTransaction - -> Miner - -> ModuleCache - -> Maybe P.Gas - -> Maybe Micros - -> PactBlockM logger tbl (T2 (Vector (Either CommandInvalidError (P.CommandResult [P.TxLogJson]))) ModuleCache) -applyPactCmds isGenesis cmds miner startModuleCache blockGas txTimeLimit = do - let txsGas txs = fromIntegral $ sumOf (traversed . _Right . to P._crGas) txs - (txOuts, T2 mcOut _) <- tracePactBlockM' "applyPactCmds" () (txsGas . fst) $ - flip runStateT (T2 startModuleCache blockGas) $ - go [] (V.toList cmds) - return $! T2 (V.fromList . List.reverse $ txOuts) mcOut - where - go - :: [Either CommandInvalidError (P.CommandResult [P.TxLogJson])] - -> [ChainwebTransaction] - -> StateT - (T2 ModuleCache (Maybe P.Gas)) - (PactBlockM logger tbl) - [Either CommandInvalidError (P.CommandResult [P.TxLogJson])] - go !acc = \case - [] -> do - pure acc - tx : rest -> do - r <- applyPactCmd isGenesis 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) - => Bool - -> Miner - -> Maybe Micros - -> ChainwebTransaction - -> StateT - (T2 ModuleCache (Maybe P.Gas)) - (PactBlockM logger tbl) - (Either CommandInvalidError (P.CommandResult [P.TxLogJson])) -applyPactCmd isGenesis miner txTimeLimit cmd = StateT $ \(T2 mcache maybeBlockGasRemaining) -> do - dbEnv <- view psBlockDbEnv - prevBlockState <- liftIO $ fmap _benvBlockState $ - readMVar $ pdPactDbVar $ _cpPactDbEnv dbEnv - logger <- view (psServiceEnv . psLogger) - gasLogger <- view (psServiceEnv . psGasLogger) - gasModel <- view (psServiceEnv . psGasModel) - txFailuresCounter <- view (psServiceEnv . psTxFailuresCounter) - v <- view chainwebVersion - let - -- for errors so fatal that the tx doesn't make it in the block - onFatalError e - | Just (BuyGasFailure 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 $ P.modifyMVar' - (pdPactDbVar $ _cpPactDbEnv dbEnv) - (benvBlockState .~ prevBlockState) - pure (Left (CommandInvalidTxTimeout t), T2 mcache maybeBlockGasRemaining) - | otherwise = throwM e - requestedTxGasLimit = view cmdGasLimit (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 (succ blockGasRemaining)) requestedTxGasLimit - gasLimitedCmd = - set cmdGasLimit newTxGasLimit (payloadObj <$> cmd) - initialGas = initialGasOf (P._cmdPayload cmd) - let !hsh = P._cmdHash cmd - - handle onFatalError $ do - T2 result mcache' <- do - txCtx <- getTxContext (publicMetaOf gasLimitedCmd) - if isGenesis - then liftIO $! applyGenesisCmd logger (_cpPactDbEnv dbEnv) P.noSPVSupport txCtx gasLimitedCmd - else do - bhdb <- view (psServiceEnv . psBlockHeaderDb) - parent <- view psParentHeader - let spv = pactSPV bhdb (_parentHeader parent) - let - !timeoutError = TxTimeout (requestKeyToTransactionHash $ P.cmdToRequestKey cmd) - txTimeout = case txTimeLimit of - Nothing -> id - Just limit -> - maybe (throwM timeoutError) return <=< timeout (fromIntegral limit) - let txGas (T3 r _ _) = fromIntegral $ P._crGas r - T3 r c _warns <- - tracePactBlockM' "applyCmd" (J.toJsonViaEncode hsh) txGas $ do - liftIO $ txTimeout $ - applyCmd v logger gasLogger txFailuresCounter (_cpPactDbEnv dbEnv) miner (gasModel txCtx) txCtx spv gasLimitedCmd initialGas mcache ApplySend - pure $ T2 r c - - if isGenesis - then updateInitCacheM mcache' - else liftPactServiceM $ debugResult "applyPactCmd" (P.crLogs %~ fmap J.Array $ result) - - -- mark the tx as processed at the checkpointer. - liftIO $ _cpRegisterProcessedTx dbEnv hsh - case maybeBlockGasRemaining of - Just blockGasRemaining -> - when (P._crGas result >= succ blockGasRemaining) $ - -- this tx attempted to consume more gas than remains in the - -- block, so the block is invalid. we don't know how much gas it - -- would've consumed, because we stop early, so we guess that it - -- needed its entire original gas limit. - throwM $ BlockGasLimitExceeded (blockGasRemaining - fromIntegral requestedTxGasLimit) - Nothing -> return () - let maybeBlockGasRemaining' = (\g -> g - P._crGas result) <$> maybeBlockGasRemaining - pure (Right result, T2 mcache' maybeBlockGasRemaining') - -transactionsFromPayload - :: PactParserVersion - -> PayloadData - -> IO (Vector ChainwebTransaction) -transactionsFromPayload 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 (chainwebPayloadCodec ppv) $ - _transactionBytes bs)) - -debugResult :: J.Encode a => Logger logger => Text -> a -> PactServiceM logger tbl () -debugResult msg result = - logDebug $ 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. 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 - -> MinerRewards - -> BlockHeight - -> IO P.ParsedDecimal -minerReward v (MinerRewards rs) bh = - case Map.lookupGE bh rs of - Nothing -> err - Just (_, m) -> pure $! P.ParsedDecimal (roundTo 8 (m / n)) - where - !n = int . order $ chainGraphAt v bh - err = internalError "block heights have been exhausted" -{-# INLINE minerReward #-} - - -data CRLogPair = CRLogPair P.Hash [P.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 (P.CommandResult [P.TxLogJson]) - -> Either PactException PayloadWithOutputs -validateHashes bHeader payload miner transactions = - if newHash == prevHash - then Right actualPwo - else Left $ BlockValidationFailure $ BlockValidationFailureMsg $ - prettyJson $ J.encodeText $ J.object - [ "header" J..= J.encodeWithAeson (ObjectEncoded bHeader) - , "mismatch" J..= errorMsg "Payload hash" prevHash newHash - , "details" J..= difference - ] - where - prettyJson txt = case A.eitherDecodeStrict @A.Value (T.encodeUtf8 txt) of - Right obj -> T.cons '\n' $ T.decodeUtf8 $ BL.toStrict $ A.encodePretty obj - Left err -> error $ "validateHashes: impossible JSON decode failure: " <> show err - - actualPwo = toPayloadWithOutputs miner transactions - - newHash = _payloadWithOutputsPayloadHash actualPwo - prevHash = view blockPayloadHash bHeader - - -- The following JSON encodings are used in the BlockValidationFailure message - - transactionBytesToCommand :: Chainweb.Payload.Transaction -> P.Command T.Text - transactionBytesToCommand txBytes = case A.decodeStrict' (_transactionBytes txBytes) of - Just cmd -> cmd - Nothing -> error $ "validateHashes.transactionBytesToCommand: Failed to decode transaction bytes as Command Text" - - transactionOutputsToCommandResult :: Chainweb.Payload.TransactionOutput -> P.CommandResult A.Value - transactionOutputsToCommandResult txOuts = case A.decodeStrict' (_transactionOutputBytes txOuts) of - Just cmdRes -> cmdRes - Nothing -> error $ "validateHashes.transactionOutputsToJson: Failed to decode transaction output bytes as CommandResult Text" - - errorMsg :: (A.ToJSON a) => T.Text -> a -> a -> J.Builder - errorMsg desc expect actual = J.object - [ "type" J..= J.text desc - , "actual" J..= J.encodeWithAeson actual - , "expected" J..= J.encodeWithAeson expect - ] - - payloadDataToJSON pd = J.object - [ "miner" J..= J.encodeWithAeson (view payloadDataMiner pd) - , "txs" J..= J.array - [ J.array - -- only works because these are valid utf8, they may not be in future! - [ J.build $ transactionBytesToCommand cmd - | cmd <- V.toList (view payloadDataTransactions pd) - ] - ] - , "hash" J..= J.string (show (view payloadDataPayloadHash pd)) - ] - - payloadWithOutputsToJSON pwo = J.object - [ "miner" J..= J.encodeWithAeson (_payloadWithOutputsMiner pwo) - , "txs" J..= J.array - [ J.array - -- only works because these are valid utf8, they may not be in future! - [ J.build $ transactionBytesToCommand cmd - , J.build $ transactionOutputsToCommandResult cr - ] - | (cmd, cr) <- V.toList (_payloadWithOutputsTransactions pwo) - ] - , "hash" J..= J.string (show (_payloadWithOutputsPayloadHash pwo)) - ] - - expectedJSON = case payload of - CheckablePayloadWithOutputs expected -> - payloadWithOutputsToJSON expected - CheckablePayload expected -> - payloadDataToJSON expected - - actualJSON = - payloadWithOutputsToJSON actualPwo - - difference = J.object - [ "expected" J..= expectedJSON - , "actual" J..= actualJSON - ] diff --git a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs new file mode 100644 index 0000000000..38806cc8b2 --- /dev/null +++ b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs @@ -0,0 +1,953 @@ +{-# 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 #-} + +-- | +-- 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 + , minerReward + , 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.Aeson as A +import qualified Data.ByteString.Short as SB +import Data.Decimal +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.Exp (ParsedCode(..)) +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.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 + checkTxSigs logger v cid bh tx + checkTimes logger v cid bh txValidationTime tx + _ <- checkCompile logger v cid bh tx + return () + +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) + if | isRight (Pact4.assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs) -> pure () + | otherwise -> throwError InsertErrorInvalidSigs + 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) + rs <- view (psServiceEnv . psMinerRewards) + v <- view chainwebVersion + txCtx <- getTxContext miner Pact4.noPublicMeta + + let !bh = ctxCurrentBlockHeight txCtx + + reward <- liftIO $! minerReward v rs 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 [] (V.toList cmds) + return $! T2 (V.fromList . List.reverse $ txOuts) mcOut + where + go + :: [Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])] + -> [Pact4.Transaction] + -> StateT + (T2 ModuleCache (Maybe Pact4.Gas)) + (PactBlockM logger tbl) + [Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])] + go !acc = \case + [] -> do + pure acc + tx : rest -> do + r <- applyPactCmd 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) + => Miner + -> Maybe Micros + -> Pact4.Transaction + -> StateT + (T2 ModuleCache (Maybe Pact4.Gas)) + (PactBlockM logger tbl) + (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])) +applyPactCmd 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 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. 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 + -> MinerRewards + -> BlockHeight + -> IO Pact4.ParsedDecimal +minerReward v (MinerRewards rs) bh = + case Map.lookupGE bh rs of + Nothing -> err + Just (_, m) -> pure $! Pact4.ParsedDecimal (roundTo 8 (m / n)) + where + !n = int . order $ chainGraphAt v bh + err = internalError "block heights have been exhausted" +{-# 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 pwo + else Left $ BlockValidationFailure $ BlockValidationFailureMsg $ + J.encodeText $ J.object + [ "header" J..= J.encodeWithAeson (ObjectEncoded bHeader) + , "mismatch" J..= errorMsg "Payload hash" prevHash newHash + , "details" J..= details + ] + where + + pwo = toPayloadWithOutputs Pact4T miner transactions + + newHash = _payloadWithOutputsPayloadHash pwo + prevHash = view blockPayloadHash bHeader + + -- The following JSON encodings are used in the BlockValidationFailure message + + check :: Eq a => A.ToJSON a => T.Text -> [Maybe J.KeyValue] -> a -> a -> Maybe J.Builder + check desc extra expect actual + | expect == actual = Nothing + | otherwise = Just $ J.object + $ "mismatch" J..= errorMsg desc expect actual + : extra + + errorMsg :: A.ToJSON a => T.Text -> a -> a -> J.Builder + errorMsg desc expect actual = J.object + [ "type" J..= J.text desc + , "actual" J..= J.encodeWithAeson actual + , "expected" J..= J.encodeWithAeson expect + ] + + details = case payload of + CheckablePayload pData -> J.Array $ catMaybes + [ check "Miner" + [] + (view payloadDataMiner pData) + (_payloadWithOutputsMiner pwo) + , check "TransactionsHash" + [ "txs" J..?= + (J.Array <$> traverse (uncurry $ check "Tx" []) (zip + (toList $ fst <$> _payloadWithOutputsTransactions pwo) + (toList $ view payloadDataTransactions pData) + )) + ] + (view payloadDataTransactionsHash pData) + (_payloadWithOutputsTransactionsHash pwo) + , check "OutputsHash" + [ "outputs" J..= J.object + [ "coinbase" J..= toPairCR (_transactionCoinbase transactions) + , "txs" J..= J.array (addTxOuts <$> _transactionPairs transactions) + ] + ] + (view payloadDataOutputsHash pData) + (_payloadWithOutputsOutputsHash pwo) + ] + + CheckablePayloadWithOutputs localPwo -> J.Array $ catMaybes + [ check "Miner" + [] + (_payloadWithOutputsMiner localPwo) + (_payloadWithOutputsMiner pwo) + , Just $ J.object + [ "transactions" J..= J.object + [ "txs" J..?= + (J.Array <$> traverse (uncurry $ check "Tx" []) (zip + (toList $ _payloadWithOutputsTransactions pwo) + (toList $ _payloadWithOutputsTransactions localPwo) + )) + , "coinbase" J..= + check "Coinbase" [] + (_payloadWithOutputsCoinbase pwo) + (_payloadWithOutputsCoinbase localPwo) + ] + ] + ] + + addTxOuts :: (Pact4.Transaction, Pact4.CommandResult [Pact4.TxLogJson]) -> J.Builder + addTxOuts (tx,cr) = J.object + [ "tx" J..= fmap (fmap _pcCode . Pact4.payloadObj) tx + , "result" J..= toPairCR cr + ] + + toPairCR cr = over (Pact4.crLogs . _Just) + (CRLogPair (fromJuste $ Pact4._crLogs (hashPact4TxLogs cr))) cr + +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/PactService/Pact5/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs new file mode 100644 index 0000000000..93fe5a7224 --- /dev/null +++ b/src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs @@ -0,0 +1,721 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} + +module Chainweb.Pact.PactService.Pact5.ExecBlock + ( runCoinbase + , continueBlock + , execExistingBlock + , validateRawChainwebTx + , validateParsedChainwebTx + + ) where + +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.Logger +import Chainweb.Mempool.Mempool(BlockFill (..), pact5RequestKeyToTransactionHash, InsertError (..)) +import Chainweb.Miner.Pact +import Chainweb.Pact5.Backend.ChainwebPactDb (Pact5Db(doPact5DbTransaction)) +import Chainweb.Pact5.SPV qualified as Pact5 +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 +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.ByteString (ByteString) +import Data.Coerce +import Data.Decimal +import Data.Either (partitionEithers) +import Data.Foldable +import Data.Map qualified as Map +import Data.Maybe +import Data.Text qualified as T +import Data.Vector (Vector) +import Data.Vector qualified as V +import Data.Void +import Numeric.Natural +import Pact.Core.ChainData hiding (ChainId) +import Pact.Core.Command.Types qualified as Pact5 +import Pact.Core.Persistence qualified as Pact5 +import Pact.Core.Hash +import Control.Exception.Safe +import qualified Pact.Core.Gas as Pact5 +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.Aeson as A +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 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 qualified Pact.Core.Errors as Pact5 +import qualified Pact.Core.Evaluate as Pact5 +import Chainweb.Pact.Backend.Types + +-- | 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 + -> MinerRewards + -> BlockHeight + -> IO Decimal +minerReward v (MinerRewards rs) bh = + case Map.lookupGE bh rs of + Nothing -> err + Just (_, m) -> pure $! roundTo 8 (m / n) + where + !n = int @Natural @Decimal . order $ chainGraphAt v bh + err = internalError "block heights have been exhausted" +{-# INLINE minerReward #-} + +runCoinbase + :: (Logger logger) + => Miner + -> PactBlockM logger tbl (Either Pact5CoinbaseError (Pact5.CommandResult [Pact5.TxLog ByteString] Void)) +runCoinbase miner = do + isGenesis <- view psIsGenesis + if isGenesis + then return $ Right noCoinbase + else do + logger <- view (psServiceEnv . psLogger) + rs <- view (psServiceEnv . psMinerRewards) + v <- view chainwebVersion + txCtx <- TxContext <$> view psParentHeader <*> pure miner + + let !bh = ctxCurrentBlockHeight txCtx + + reward <- liftIO $ minerReward v rs bh + -- 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)) + +-- | Continue adding transactions to an existing block. +continueBlock + :: forall logger tbl + . (Logger logger, CanReadablePayloadCas tbl) + => MemPoolAccess + -> BlockInProgress Pact5 + -> PactBlockM logger tbl (BlockInProgress Pact5) +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) <> ")" + , "(parent hash = " <> sshow (view blockHash blockParentHeader) <> ")" + ] + Nothing -> + liftPactServiceM $ logInfoPact "Continuing genesis block" + + blockGasLimit <- view (psServiceEnv . psBlockGasLimit) + mTxTimeLimit <- view (psServiceEnv . psTxTimeLimit) + 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 . pact5RequestKeyToTransactionHash . view Pact5.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 pact5RequestKeyToTransactionHash $ concat invalids) + + liftPactServiceM $ logDebugPact $ "Order of completed transactions: " <> sshow (map (Pact5.unRequestKey . Pact5._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 (Pact5.unRequestKey . Pact5._crReqKey . snd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) + + return blockInProgress' + + where + maybeBlockParentHeader = _parentHeader <$> _blockInProgressParentHeader blockInProgress + refill fetchLimit txTimeLimit blockFillState = over _2 reverse <$> go [] [] blockFillState + where + go + :: [CompletedTransactions] + -> [InvalidTransactions] + -> BlockFill + -> PactBlockM logger tbl (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 <> ")" + pure stop + | prevRemainingGas < 0 = + throwM $ MempoolFillFailure $ "Internal error, negative gas limit: " <> sshow prevBlockFillState + | prevRemainingGas == 0 = + pure stop + | otherwise = do + newTxs <- getBlockTxs prevBlockFillState + liftPactServiceM $ logDebugPact $ "Refill: fetched transaction: " <> sshow (V.length newTxs) + if V.null newTxs + then do + liftPactServiceM $ logDebugPact $ "Refill: no new transactions" + pure stop + else do + (newCompletedTransactions, newInvalidTransactions, newBlockGasLimit, timedOut) <- + 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) + + let newBlockFillState = BlockFill + { _bfCount = succ prevFillCount + , _bfGasLimit = newBlockGasLimit + , _bfTxHashes = + flip + (foldr (S.insert . pact5RequestKeyToTransactionHash . view (_2 . Pact5.crReqKey))) + newCompletedTransactions + $ flip + (foldr (S.insert . pact5RequestKeyToTransactionHash)) + newInvalidTransactions + $ prevTxHashes + } + let completedTransactions' = newCompletedTransactions : completedTransactions + let invalidTransactions' = newInvalidTransactions : invalidTransactions + if timedOut + then + -- stop; we've used so much time already that we should just return what we have + pure (newBlockFillState, completedTransactions', invalidTransactions') + else + go completedTransactions' invalidTransactions' newBlockFillState + + where + stop = (prevBlockFillState, completedTransactions, invalidTransactions) + + execNewTransactions + :: Miner + -> Pact4.GasLimit + -> Micros + -> Vector Pact5.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 + logger' <- view (psServiceEnv . psLogger) + isGenesis <- view psIsGenesis + ((txResults, timedOut), (finalBlockHandle, Identity finalRemainingGas)) <- + liftIO $ flip runStateT (startBlockHandle, Identity p5RemainingGas) $ foldr + (\tx rest -> StateT $ \s -> do + let logger = addLabel ("transactionHash", sshow (Pact5._cmdHash tx)) logger' + let env' = env & psServiceEnv . psLogger .~ logger + let timeoutFunc runTx = + if isGenesis + then do + logFunctionText logger Info $ "Running genesis command" + fmap Just runTx + else + newTimeout (fromIntegral @Micros @Int timeLimit) runTx + m <- liftIO $ timeoutFunc + $ runExceptT $ runStateT (applyPactCmd env' miner tx) s + case m of + Nothing -> do + logFunctionJson logger Warn $ Aeson.object + [ "type" Aeson..= Aeson.String "newblock timeout" + , "hash" Aeson..= Aeson.String (sshow (Pact5._cmdHash tx)) + , "payload" Aeson..= Aeson.String ( + T.decodeUtf8 $ SB.fromShort $ tx ^. Pact5.cmdPayload . Pact5.payloadBytes + ) + ] + return (([Left (Pact5._cmdHash tx)], True), s) + Just (Left err) -> do + logFunctionText logger Debug $ + "applyCmd failed to buy gas " <> sshow err + ((as, timedOut), s') <- runStateT rest s + return ((Left (Pact5._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'') + ) + (return ([], False)) + 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) + + getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector Pact5.Transaction) + getBlockTxs blockFillState = do + liftPactServiceM $ logDebugPact "Refill: fetching transactions" + v <- view chainwebVersion + cid <- view chainId + 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 . validateRawChainwebTx logger v cid dbEnv (_blockInProgressHandle blockInProgress) (ParentCreationTime 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] + +-- Apply a Pact command in the current block. +-- This function completely ignores timeouts! +applyPactCmd + :: (Traversable t, Logger logger) + => PactBlockEnv logger Pact5 tbl + -> Miner -> Pact5.Transaction + -> StateT + (BlockHandle, t P.GasLimit) + (ExceptT Pact5GasPurchaseFailure IO) + (Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info)) +applyPactCmd env miner 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) + resultOrGasError <- liftIO $ runReaderT + (unsafeApplyPactCmd blockHandle + (initialGasOf (tx ^. Pact5.cmdPayload)) + alteredTx) + env + case resultOrGasError of + Left err -> throwError err + Right (result, nextHandle) + -- 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 + , 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) + | otherwise -> do + let subtractGasLimit limit subtrahend = + let limitGas = limit ^. Pact5._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 $ + "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) + blockGasRemaining' <- + traverse (`subtractGasLimit` (Pact5._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.Gas + -> Pact5.Command (Pact5.Payload PublicMeta Pact5.ParsedCode) + -> ReaderT + (PactBlockEnv logger Pact5 tbl) + IO + (Either Pact5GasPurchaseFailure + (Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.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 + -- TODO: trace more info? + let rk = Pact5.RequestKey $ Pact5._cmdHash cmd + (resultOrError, blockHandle') <- + liftIO $ trace' (logFunction logger) "applyCmd" computeTrace (\_ -> 0) $ + doPact5DbTransaction dbEnv blockHandle (Just rk) $ \pactDb -> + if _psIsGenesis env + then do + logFunctionText logger Debug "running genesis command!" + r <- runGenesisPayload logger pactDb spv txCtx cmd + case r of + Left genesisError -> throwM $ Pact5GenesisCommandFailed (Pact5._cmdHash cmd) (sshow genesisError) + -- 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 spv initialGas cmd + liftIO $ case resultOrError of + -- unknown exceptions are logged specially, because they indicate bugs in Pact or chainweb + Right + Pact5.CommandResult + { + _crResult = + Pact5.PactResultErr (Pact5.PEExecutionError (Pact5.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 + } -> + liftIO $ logFunction logger Debug + (Pact5TxFailureLog 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) + , "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 _ -> + "success" + Pact5.PactResultErr _ -> + "failure" + ) + , "hash" Aeson..= J.toJsonViaEncode (Pact5._cmdHash cmd) + ] + +-- | 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 + -> Pact5Db + -> BlockHandle + -> ParentCreationTime + -- ^ reference time for tx validation. + -> BlockHeight + -- ^ Current block height + -> Bool + -- ^ Genesis? + -> Pact5.Transaction + -> ExceptT InsertError IO () +validateParsedChainwebTx _logger v cid db _blockHandle txValidationTime bh isGenesis tx + | isGenesis = pure () + | otherwise = do + checkUnique tx + checkTxHash tx + checkTxSigs tx + checkTimes tx + return () + where + + checkUnique :: Pact5.Transaction -> ExceptT InsertError IO () + checkUnique t = do + found <- liftIO $ + HashMap.lookup (coerce $ Pact5._cmdHash t) <$> + Pact5.lookupPactTransactions db + (V.singleton $ coerce $ Pact5._cmdHash t) + case found of + Nothing -> pure () + Just _ -> throwError InsertErrorDuplicate + + checkTimes :: Pact5.Transaction -> ExceptT InsertError IO () + checkTimes t = do + if | skipTxTimingValidation v cid bh -> pure () + | not (Pact5.assertTxNotInFuture txValidationTime (view Pact5.payloadObj <$> t)) -> do + throwError InsertErrorTimeInFuture + | not (Pact5.assertTxTimeRelativeToParent txValidationTime (view Pact5.payloadObj <$> t)) -> do + throwError InsertErrorTTLExpired + | otherwise -> do + pure () + + checkTxHash :: Pact5.Transaction -> ExceptT InsertError IO () + checkTxHash t = do + case Pact5.verifyHash (Pact5._cmdHash t) (SB.fromShort $ view Pact5.payloadBytes $ Pact5._cmdPayload t) of + Left _ + | doCheckTxHash v cid bh -> throwError InsertErrorInvalidHash + | otherwise -> pure () + Right _ -> pure () + + + checkTxSigs :: Pact5.Transaction -> ExceptT InsertError IO () + checkTxSigs t = do + if | Pact5.assertValidateSigs hsh signers sigs -> pure () + | otherwise -> throwError InsertErrorInvalidSigs + where + hsh = Pact5._cmdHash t + sigs = Pact5._cmdSigs t + signers = Pact5._pSigners $ view Pact5.payloadObj $ Pact5._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 + -> 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 . sshow) return $ Pact5.parsePact4Command tx + liftIO $ do + logDebug_ logger $ "validateRawChainwebTx: parse succeeded" + validateParsedChainwebTx logger v cid db blockHandle parentTime bh isGenesis tx' + return $! tx' + +execExistingBlock + :: (CanReadablePayloadCas tbl, Logger logger) + => BlockHeader + -> CheckablePayload + -> PactBlockM logger tbl (P.Gas, PayloadWithOutputs) +execExistingBlock currHeader payload = do + parentBlockHeader <- view psParentHeader + let plData = checkablePayloadToPayloadData payload + miner :: Miner <- decodeStrictOrThrow (_minerData $ view payloadDataMiner plData) + txs <- liftIO $ 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) + errors <- liftIO $ flip foldMap txs $ \tx -> do + errorOrSuccess <- runExceptT $ + validateParsedChainwebTx logger v cid db blockHandlePreCoinbase txValidationTime + (view blockHeight currHeader) + isGenesis + tx + case errorOrSuccess of + Right () -> return [] + Left err -> return [(Pact5._cmdHash tx, sshow err)] + case NEL.nonEmpty errors of + Nothing -> return () + Just errorsNel -> throwM $ Pact5TransactionValidationException errorsNel + + coinbaseResult <- runCoinbase miner >>= \case + Left err -> throwM $ CoinbaseFailure (Pact5CoinbaseFailure 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) + + env <- ask + (results, (finalHandle, _finalBlockGasLimit)) <- + liftIO $ flip runStateT (postCoinbaseBlockHandle, blockGasLimit) $ + forM txs $ \tx -> + (tx,) <$> mapStateT + (either (throwM . Pact5BuyGasFailure) return <=< runExceptT) + (applyPactCmd env miner tx) + -- incorporate the final state of the transactions into the block state + pbBlockHandle .= finalHandle + + let !totalGasUsed = foldOf (folded . _2 . to Pact5._crGas) results + + pwo <- either throwM return $ + validateHashes currHeader payload miner (Transactions results coinbaseResult) + return (totalGasUsed, pwo) + +-- | Check that the two payloads agree. If we have access to the outputs, we +-- check those too. +validateHashes + :: BlockHeader + -> 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 + Right actualPwo + else do + let jsonText = + J.encodeText $ J.object + [ "header" J..= J.encodeWithAeson (ObjectEncoded bHeader) + , "mismatch" J..= errorMsg "Payload hash" prevHash newHash + , "details" J..= difference + ] + + Left (BlockValidationFailure $ BlockValidationFailureMsg jsonText) + where + + actualPwo = toPayloadWithOutputs Pact5T miner transactions + + newHash = _payloadWithOutputsPayloadHash actualPwo + prevHash = view blockPayloadHash bHeader + + -- The following JSON encodings are used in the BlockValidationFailure message + + errorMsg :: (A.ToJSON a) => T.Text -> a -> a -> J.Builder + errorMsg desc expect actual = J.object + [ "type" J..= J.text desc + , "actual" J..= J.encodeWithAeson actual + , "expected" J..= J.encodeWithAeson expect + ] + + payloadDataToJSON pd = J.object + [ "miner" J..= J.encodeWithAeson (view payloadDataMiner pd) + , "txs" J..= J.array + -- only works because these are valid utf8, they may not be in future! + [ J.build $ T.decodeUtf8 $ _transactionBytes cmd + | cmd <- V.toList (view payloadDataTransactions pd) + ] + , "hash" J..= J.string (show (view payloadDataPayloadHash pd)) + ] + + payloadWithOutputsToJSON pwo = J.object + [ "miner" J..= J.encodeWithAeson (_payloadWithOutputsMiner pwo) + , "txs" J..= J.array + [ J.array + -- only works because these are valid utf8, they may not be in future! + [ J.build $ T.decodeUtf8 $ _transactionBytes cmd + , J.build $ T.decodeUtf8 $ _transactionOutputBytes cr + ] + | (cmd, cr) <- V.toList (_payloadWithOutputsTransactions pwo) + ] + , "hash" J..= J.string (show (_payloadWithOutputsPayloadHash pwo)) + ] + + expectedJSON = case payload of + CheckablePayloadWithOutputs expected -> + payloadWithOutputsToJSON expected + CheckablePayload expected -> + payloadDataToJSON expected + + actualJSON = + payloadWithOutputsToJSON actualPwo + + difference = J.object + [ "expected" J..= expectedJSON + , "actual" J..= actualJSON + ] + +data CRLogPair = CRLogPair Hash [Pact5.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 + ] + ] + {-# INLINE build #-} + +-- | 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.hs b/src/Chainweb/Pact/RestAPI.hs index d42091419a..16a4ffff30 100644 --- a/src/Chainweb/Pact/RestAPI.hs +++ b/src/Chainweb/Pact/RestAPI.hs @@ -28,8 +28,6 @@ module Chainweb.Pact.RestAPI , pactListenApi , PactSendApi , pactSendApi -, PactPollApi -, pactPollApi , PactLocalWithQueryApi , pactLocalWithQueryApi , PactPollWithQueryApi @@ -59,8 +57,7 @@ module Chainweb.Pact.RestAPI import Data.Text (Text) import qualified Pact.Types.Command as Pact -import Pact.Server.API as API -import Pact.Types.API (Poll, PollResponses) +import qualified Pact.Server.API as Pact4 import Pact.Utils.Servant import Servant @@ -70,10 +67,11 @@ import Servant import Chainweb.ChainId import Chainweb.Pact.RestAPI.EthSpv import Chainweb.Pact.RestAPI.SPV -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.RestAPI.Utils import Chainweb.SPV.PayloadProof import Chainweb.Version +import qualified Pact.Core.Command.Server as Pact5 -- -------------------------------------------------------------------------- -- -- @POST /chainweb///chain//pact/@ @@ -83,7 +81,7 @@ type PactApi_ = "pact" :> "api" :> "v1" - :> ( ApiSend + :> ( Pact4.ApiSend :<|> PactPollWithQueryApi_ :<|> ApiListen :<|> PactLocalWithQueryApi_ @@ -108,10 +106,11 @@ type PactV1ApiEndpoint (v :: ChainwebVersionT) (c :: ChainIdT) api :> "v1" :> api -type PactLocalApi v c = PactV1ApiEndpoint v c ApiLocal -type PactSendApi v c = PactV1ApiEndpoint v c ApiSend +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 PactPollApi v c = PactV1ApiEndpoint v c ApiPoll + +type ApiListen = ("listen" :> ReqBody '[PactJson] Pact5.ListenRequest :> Post '[PactJson] Pact5.ListenResponse) pactLocalApi :: forall (v :: ChainwebVersionT) (c :: ChainIdT) @@ -128,11 +127,6 @@ pactListenApi . Proxy (PactListenApi v c) pactListenApi = Proxy -pactPollApi - :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . Proxy (PactPollApi v c) -pactPollApi = Proxy - -- -------------------------------------------------------------------------- -- -- POST Queries for Pact Local Pre-flight @@ -157,8 +151,8 @@ pactLocalWithQueryApi = Proxy type PactPollWithQueryApi_ = "poll" :> QueryParam "confirmationDepth" ConfirmationDepth - :> ReqBody '[PactJson] Poll - :> Post '[PactJson] PollResponses + :> ReqBody '[PactJson] Pact5.PollRequest + :> Post '[PactJson] Pact5.PollResponse type PactPollWithQueryApi v c = PactV1ApiEndpoint v c PactPollWithQueryApi_ diff --git a/src/Chainweb/Pact/RestAPI/Client.hs b/src/Chainweb/Pact/RestAPI/Client.hs index 0bc957af57..e416b0961d 100644 --- a/src/Chainweb/Pact/RestAPI/Client.hs +++ b/src/Chainweb/Pact/RestAPI/Client.hs @@ -19,8 +19,6 @@ module Chainweb.Pact.RestAPI.Client , pactSpv2ApiClient , ethSpvApiClient_ , ethSpvApiClient -, pactPollApiClient_ -, pactPollApiClient , pactListenApiClient_ , pactListenApiClient , pactSendApiClient_ @@ -48,9 +46,10 @@ import Chainweb.ChainId import Chainweb.Pact.RestAPI import Chainweb.Pact.RestAPI.EthSpv import Chainweb.Pact.RestAPI.SPV -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.SPV.PayloadProof import Chainweb.Version +import qualified Pact.Core.Command.Server as Pact5 -- -------------------------------------------------------------------------- -- -- Pact Spv Transaction Output Proof Client @@ -184,15 +183,15 @@ pactListenApiClient_ :: forall (v :: ChainwebVersionT) (c :: ChainIdT) . KnownChainwebVersionSymbol v => KnownChainIdSymbol c - => ListenerRequest - -> ClientM ListenResponse + => Pact5.ListenRequest + -> ClientM Pact5.ListenResponse pactListenApiClient_ = client (pactListenApi @v @c) pactListenApiClient :: ChainwebVersion -> ChainId - -> ListenerRequest - -> ClientM ListenResponse + -> Pact5.ListenRequest + -> ClientM Pact5.ListenResponse pactListenApiClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) @@ -222,40 +221,20 @@ pactSendApiClient -- -------------------------------------------------------------------------- -- -- Pact Poll -pactPollApiClient_ - :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . KnownChainwebVersionSymbol v - => KnownChainIdSymbol c - => Poll - -> ClientM PollResponses -pactPollApiClient_ = client (pactPollApi @v @c) - -pactPollApiClient - :: ChainwebVersion - -> ChainId - -> Poll - -> ClientM PollResponses -pactPollApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = pactPollApiClient_ @v @c - pactPollWithQueryApiClient_ :: forall (v :: ChainwebVersionT) (c :: ChainIdT) . KnownChainwebVersionSymbol v => KnownChainIdSymbol c => Maybe ConfirmationDepth - -> Poll - -> ClientM PollResponses + -> Pact5.PollRequest + -> ClientM Pact5.PollResponse pactPollWithQueryApiClient_ = client (pactPollWithQueryApi @v @c) pactPollWithQueryApiClient :: ChainwebVersion -> ChainId -> Maybe ConfirmationDepth - -> Poll - -> ClientM PollResponses -pactPollWithQueryApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = pactPollWithQueryApiClient_ @v @c + -> Pact5.PollRequest + -> ClientM Pact5.PollResponse +pactPollWithQueryApiClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) confirmationDepth poll = do + pactPollWithQueryApiClient_ @v @c confirmationDepth poll diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index 460fcf1817..251d5582a3 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -6,6 +6,7 @@ {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} @@ -29,13 +30,14 @@ module Chainweb.Pact.RestAPI.Server , somePactServer , somePactServers , validateCommand +, validatePact5Command ) where import Control.Applicative import Control.Concurrent.STM (atomically) import Control.Concurrent.STM.TVar import Control.DeepSeq -import Control.Lens (set, view, preview, itraverse) +import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Catch hiding (Handler) import Control.Monad.Reader @@ -43,10 +45,11 @@ import Control.Monad.State.Strict import Control.Monad.Trans.Except (ExceptT, runExceptT, except) import Data.Aeson as Aeson -import Data.Bifunctor (first, second) +import Data.Bifunctor (second) 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) import Data.Foldable import Data.Function import Data.HashMap.Strict (HashMap) @@ -95,12 +98,12 @@ import qualified Chainweb.CutDB as CutDB import Chainweb.Graph import Chainweb.Logger import Chainweb.Mempool.Mempool - (InsertError(..), InsertType(..), MempoolBackend(..), TransactionHash(..), requestKeyToTransactionHash) + (InsertError(..), InsertType(..), MempoolBackend(..), TransactionHash(..), pact5RequestKeyToTransactionHash) import Chainweb.Pact.RestAPI import Chainweb.Pact.RestAPI.EthSpv import Chainweb.Pact.RestAPI.SPV -import Chainweb.Pact.Service.Types -import Chainweb.Pact.SPV +import Chainweb.Pact.Types +import Chainweb.Pact4.SPV qualified as Pact4 import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.RestAPI.Orphans () @@ -110,31 +113,37 @@ import Chainweb.SPV.CreateProof import Chainweb.SPV.EventProof import Chainweb.SPV.OutputProof import Chainweb.SPV.PayloadProof -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 hiding (parsePact) import qualified Chainweb.TreeDB as TreeDB import Chainweb.Utils import Chainweb.Version -import Chainweb.Pact.Validations (assertCommand, displayAssertCommandError) -import Chainweb.Version.Guards (isWebAuthnPrefixLegal, pactParserVersion, validPPKSchemes) +import qualified Chainweb.Pact4.Validations as Pact4 +import Chainweb.Version.Guards (isWebAuthnPrefixLegal, validPPKSchemes) import Chainweb.WebPactExecutionService import qualified Pact.JSON.Encode as J -import qualified Pact.Parse as Pact -import Pact.Types.API -import qualified Pact.Types.ChainId as Pact -import Pact.Types.Command -import Pact.Types.Hash (Hash(..)) -import Pact.Types.Info (noInfo) -import qualified Pact.Types.Hash as Pact -import Pact.Types.PactError (PactError(..), PactErrorType(..)) -import Pact.Types.Pretty (pretty) -import Control.Error (note, rights) +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.Pact5.Transaction as Pact5 +import qualified Chainweb.Pact5.Validations as Pact5 +import Data.Coerce +import qualified Pact.Core.Command.Server as Pact5 +import qualified Pact.Core.Evaluate as Pact5 +import qualified Pact.Core.Errors as Pact5 +import qualified Pact.Core.Hash as Pact5 +import qualified Pact.Core.Gas as Pact5 -- -------------------------------------------------------------------------- -- data PactServerData logger tbl = PactServerData { _pactServerDataCutDb :: !(CutDB.CutDb tbl) - , _pactServerDataMempool :: !(MempoolBackend ChainwebTransaction) + , _pactServerDataMempool :: !(MempoolBackend Pact4.UnparsedTransaction) , _pactServerDataLogger :: !logger , _pactServerDataPact :: !PactExecutionService } @@ -184,13 +193,12 @@ pactServer d = logger = _pactServerDataLogger d pact = _pactServerDataPact d cdb = _pactServerDataCutDb d - v = _chainwebVersion cdb pactApiHandlers - = sendHandler logger v cid mempool + = sendHandler logger mempool :<|> pollHandler logger cdb cid pact mempool :<|> listenHandler logger cdb cid pact mempool - :<|> localHandler logger v cid pact + :<|> localHandler logger pact pactSpvHandler = spvHandler logger cdb cid pactSpv2Handler = spv2Handler logger cdb cid @@ -210,10 +218,10 @@ somePactServers v = mconcat . fmap (somePactServer . uncurry (somePactServerData v)) data PactCmdLog - = PactCmdLogSend (NonEmpty (Command Text)) + = PactCmdLogSend (NonEmpty (Pact4.Command Text)) | PactCmdLogPoll (NonEmpty Text) | PactCmdLogListen Text - | PactCmdLogLocal (Command Text) + | PactCmdLogLocal (Pact4.Command Text) | PactCmdLogSpv Text deriving (Show, Generic, NFData) @@ -243,32 +251,33 @@ instance ToJSON PactCmdLog where -- -------------------------------------------------------------------------- -- -- Send Handler +-- TODO: convert to Pact 5 sendHandler :: Logger logger => logger - -> ChainwebVersion - -> ChainId - -> MempoolBackend ChainwebTransaction - -> SubmitBatch - -> Handler RequestKeys -sendHandler logger v cid mempool (SubmitBatch cmds) = Handler $ do + -> MempoolBackend Pact4.UnparsedTransaction + -> Pact4.SubmitBatch + -> Handler Pact4.RequestKeys +sendHandler logger mempool (Pact4.SubmitBatch cmds) = Handler $ do liftIO $ logg Info (PactCmdLogSend cmds) - case itraverse (\i cmd -> first (i,) (validateCommand v cid cmd)) cmds of - Right enriched -> do - let txs = V.fromList $ NEL.toList enriched - -- If any of the txs in the batch fail validation, we reject them all. - liftIO (mempoolInsertCheck mempool txs) >>= checkResult - liftIO (mempoolInsert mempool UncheckedInsert txs) - return $! RequestKeys $ NEL.map cmdToRequestKey enriched - Left (pos, err) -> failWith $ "Validation of command at position " <> sshow pos <> " failed: " <> T.pack err + case (traverse . traverse) (\t -> (encodeUtf8 t,) <$> eitherDecodeStrictText t) cmds 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 (mempoolInsertCheck 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 where failWith :: Text -> ExceptT ServerError IO a - failWith err = throwError $ setErrText err err400 + failWith err = do + liftIO $ logFunctionText logger Info err + throwError $ setErrText err err400 logg = logFunctionJson (setComponent "send-handler" logger) - toPactHash :: TransactionHash -> Pact.TypedHash h - toPactHash (TransactionHash h) = Pact.TypedHash h + toPactHash :: TransactionHash -> Pact4.TypedHash h + toPactHash (TransactionHash h) = Pact4.TypedHash h checkResult :: Either (T2 TransactionHash InsertError) () -> ExceptT ServerError IO () checkResult (Right _) = pure () @@ -282,23 +291,20 @@ sendHandler logger v cid mempool (SubmitBatch cmds) = Handler $ do -- -------------------------------------------------------------------------- -- -- Poll Handler +-- TODO: convert to Pact 5? pollHandler - :: HasCallStack - => CanReadablePayloadCas tbl - => Logger logger + :: (HasCallStack, CanReadablePayloadCas tbl, Logger logger) => logger -> CutDB.CutDb tbl -> ChainId -> PactExecutionService - -> MempoolBackend ChainwebTransaction + -> MempoolBackend Pact4.UnparsedTransaction -> Maybe ConfirmationDepth - -> Poll - -> Handler PollResponses -pollHandler logger cdb cid pact mem confDepth (Poll request) = do - traverse_ validateRequestKey request - - liftIO $! logg Info $ PactCmdLogPoll $ fmap requestKeyToB16Text request - PollResponses <$!> liftIO (internalPoll pdb bdb mem pact confDepth request) + -> Pact5.PollRequest + -> Handler Pact5.PollResponse +pollHandler logger cdb 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 @@ -307,45 +313,43 @@ pollHandler logger cdb cid pact mem confDepth (Poll request) = do -- -------------------------------------------------------------------------- -- -- Listen Handler +-- TODO: convert to Pact 5? listenHandler - :: CanReadablePayloadCas tbl - => Logger logger + :: (CanReadablePayloadCas tbl, Logger logger) => logger -> CutDB.CutDb tbl -> ChainId -> PactExecutionService - -> MempoolBackend ChainwebTransaction - -> ListenerRequest - -> Handler ListenResponse -listenHandler logger cdb cid pact mem (ListenerRequest key) = do - validateRequestKey key - - liftIO $ logg Info $ PactCmdLogListen $ requestKeyToB16Text key - liftIO (registerDelay defaultTimeout >>= runListen) + -> MempoolBackend Pact4.UnparsedTransaction + -> Pact5.ListenRequest + -> Handler Pact5.ListenResponse +listenHandler logger cdb 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 -> IO ListenResponse + runListen :: TVar Bool -> Handler Pact5.ListenResponse runListen timedOut = do - startCut <- CutDB._cut cdb + startCut <- liftIO $ CutDB._cut cdb case HM.lookup cid (_cutMap startCut) of - Nothing -> pure $! ListenTimeout defaultTimeout + Nothing -> throwError err504 Just bh -> poll bh where - go :: BlockHeader -> IO ListenResponse + go :: BlockHeader -> Handler Pact5.ListenResponse go !prevBlock = do - m <- waitForNewBlock prevBlock + m <- liftIO $ waitForNewBlock prevBlock case m of - Nothing -> pure $! ListenTimeout defaultTimeout + Nothing -> throwError err504 Just block -> poll block - poll :: BlockHeader -> IO ListenResponse + poll :: BlockHeader -> Handler Pact5.ListenResponse poll bh = do - hm <- internalPoll pdb bdb mem pact Nothing (pure key) + hm <- liftIO $ internalPoll logger pdb bdb mem pact Nothing (pure key) if HM.null hm then go bh - else pure $! ListenResponse $ snd $ unsafeHead "Chainweb.Pact.RestAPI.Server.listenHandler.poll" $ HM.toList hm + else pure $! Pact5.ListenResponse $ snd $ unsafeHead "Chainweb.Pact.RestAPI.Server.listenHandler.poll" $ HM.toList hm waitForNewBlock :: BlockHeader -> IO (Maybe BlockHeader) waitForNewBlock lastBlockHeader = atomically $ do @@ -362,11 +366,10 @@ listenHandler logger cdb cid pact mem (ListenerRequest key) = do -- -------------------------------------------------------------------------- -- -- Local Handler +-- TODO: convert to Pact 5? localHandler :: Logger logger => logger - -> ChainwebVersion - -> ChainId -> PactExecutionService -> Maybe LocalPreflightSimulation -- ^ Preflight flag @@ -374,9 +377,9 @@ localHandler -- ^ No sig verification flag -> Maybe RewindDepth -- ^ Rewind depth - -> Command Text + -> Pact4.Command Text -> Handler LocalResult -localHandler logger v cid pact preflight sigVerify rewindDepth cmd = do +localHandler logger pact preflight sigVerify rewindDepth cmd = do liftIO $ logg Info $ PactCmdLogLocal cmd cmd' <- case validatedCommand of Right c -> return c @@ -398,20 +401,20 @@ localHandler logger v cid pact preflight sigVerify rewindDepth cmd = do | Just NoVerify <- sigVerify = do -- -- desnote(emily): This workflow is 'Pact.Types.Command.verifyCommand' - -- lite - only decode and parse the pact command, no sig checking. + -- lite - only decode the pact command, no sig checking. -- We at least check the consistency of the payload hash. Further -- down in the 'execLocal' code, 'noSigVerify' triggers a nop on -- checking again if 'preflight' is set. -- - let payloadBS = encodeUtf8 (_cmdPayload cmd) + let payloadBS = encodeUtf8 (Pact4._cmdPayload cmd) - void $ Pact.verifyHash @'Pact.Blake2b_256 (_cmdHash cmd) payloadBS + void $ Pact4.verifyHash @'Pact4.Blake2b_256 (Pact4._cmdHash cmd) payloadBS decoded <- eitherDecodeStrict' payloadBS - payloadParsed <- traverse Pact.parsePact decoded - let cmd' = cmd { _cmdPayload = (payloadBS, payloadParsed) } - pure $ mkPayloadWithText cmd' - | otherwise = validateCommand v cid cmd + let cmd' = cmd { Pact4._cmdPayload = (payloadBS, decoded) } + pure $ Pact4.mkPayloadWithText cmd' + | otherwise = Pact4.mkPayloadWithText <$> + traverse (\bs -> (encodeUtf8 bs,) <$> eitherDecodeStrictText bs) cmd -- -------------------------------------------------------------------------- -- -- Cross Chain SPV Handler @@ -433,19 +436,19 @@ spvHandler -- Also contains the request key of of the cross-chain transfer -- tx request. -> Handler TransactionOutputProofB64 -spvHandler l cdb cid (SpvRequest rk (Pact.ChainId ptid)) = do +spvHandler l cdb cid (SpvRequest rk (Pact4.ChainId ptid)) = do validateRequestKey rk liftIO $! logg (sshow ph) - T2 bhe _bha <- liftIO (try $ _pactLookup pe cid Nothing (pure ph)) >>= \case + 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 ph v of + Right v -> case HM.lookup (coerce $ Pact4.toUntypedHash ph) v of Nothing -> toErr $ "Transaction hash not found: " <> sshow ph Just t -> return t - idx <- liftIO (getTxIdx bdb pdb bhe ph) >>= \case + idx <- liftIO (Pact4.getTxIdx bdb pdb bhe ph) >>= \case Left e -> toErr $ "Internal error: Index lookup for hash failed: " <> sshow e @@ -464,7 +467,7 @@ spvHandler l cdb cid (SpvRequest rk (Pact.ChainId ptid)) = do return $! b64 p where pe = _webPactExecutionService $ view CutDB.cutDbPactService cdb - ph = Pact.fromUntypedHash $ unRequestKey rk + ph = Pact4.fromUntypedHash $ Pact4.unRequestKey rk bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb pdb = view CutDB.cutDbPayloadDb cdb b64 = TransactionOutputProofB64 @@ -512,15 +515,15 @@ spv2Handler l cdb cid r = case _spvSubjectIdType sid of :: forall a . MerkleHashAlgorithm a => MerkleHashAlgorithmName a - => (BlockHeaderDb -> PayloadDb tbl -> Natural -> BlockHash -> RequestKey -> IO (PayloadProof a)) + => (BlockHeaderDb -> PayloadDb tbl -> Natural -> BlockHash -> Pact4.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 ph)) >>= \case + 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 ph v of + Right v -> case HM.lookup (coerce ph) v of Nothing -> toErr $ "Transaction hash not found: " <> sshow ph Just t -> return t @@ -534,7 +537,7 @@ spv2Handler l cdb cid r = case _spvSubjectIdType sid of rk = _spvSubjectIdReqKey sid pe = _webPactExecutionService $ view CutDB.cutDbPactService cdb - ph = Pact.fromUntypedHash $ unRequestKey rk + ph = Pact4.unRequestKey rk bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb pdb = view CutDB.cutDbPayloadDb cdb @@ -598,76 +601,96 @@ ethSpvHandler req = do -- Poll Helper internalPoll - :: CanReadablePayloadCas tbl - => PayloadDb tbl + :: (CanReadablePayloadCas tbl, Logger logger) + => logger + -> PayloadDb tbl -> BlockHeaderDb - -> MempoolBackend ChainwebTransaction + -> MempoolBackend Pact4.UnparsedTransaction -> PactExecutionService -> Maybe ConfirmationDepth - -> NonEmpty RequestKey - -> IO (HashMap RequestKey (CommandResult Hash)) -internalPoll pdb bhdb mempool pactEx confDepth requestKeys0 = do + -> NonEmpty Pact5.RequestKey + -> IO (HashMap Pact5.RequestKey (Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info)))) +internalPoll logger pdb bhdb mempool pactEx 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 requestKeys + results0 <- _pactLookup pactEx cid 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 (Pact.fromUntypedHash $ unRequestKey rk) results0)) requestKeysV + let results1 = V.map (\rk -> (rk, HM.lookup (coerce $ Pact5.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) + dbg $ "internalPoll.badlisted: " <> sshow badlisted vs <- mapM lookup present - let good = rights $ V.toList vs + let (errs, notErrs) = partitionEithers $ V.toList vs + let good = catMaybes notErrs + logFunctionJson logger Warn errs return $! HM.fromList (good ++ badlisted) where cid = _chainId bhdb !requestKeysV = V.fromList $ NEL.toList requestKeys0 - !requestKeys = V.map (Pact.fromUntypedHash . unRequestKey) requestKeysV + !requestKeys = V.map Pact5.unRequestKey requestKeysV lookup - :: (RequestKey, T2 BlockHeight BlockHash) - -> IO (Either String (RequestKey, CommandResult Hash)) - lookup (key, T2 _ ha) = fmap (key,) <$> lookupRequestKey key ha + :: (Pact5.RequestKey, T2 BlockHeight BlockHash) + -> IO (Either String (Maybe (Pact5.RequestKey, Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info))))) + 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 + -> BlockHash + -> IO (Either String (Maybe (Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info))))) lookupRequestKey key bHash = runExceptT $ do - let keyHash = unRequestKey key - let pactHash = Pact.fromUntypedHash keyHash - let matchingHash = (== pactHash) . _cmdHash . fst - blockHeader <- liftIO $ TreeDB.lookupM bhdb bHash + let pactHash = Pact5.unRequestKey key + let matchingHash = (== pactHash) . Pact5._cmdHash . fst + blockHeader <- liftIO (TreeDB.lookup bhdb bHash) >>= \case + Nothing -> throwError $ "missing block header: " <> sshow key + Just x -> return x + let payloadHash = view blockPayloadHash blockHeader - (_payloadWithOutputsTransactions -> txsBs) <- barf "tablelookupFailed" =<< liftIO (lookupPayloadWithHeight pdb (Just $ view blockHeight blockHeader) payloadHash) + (_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) !txs <- mapM fromTx txsBs case find matchingHash txs of Just (_cmd, TransactionOutput output) -> do - out <- barf "decodeStrict' output" $! decodeStrict' output - when (_crReqKey out /= key) $ - fail "internal error: Transaction output doesn't match its hash!" - enrichCR blockHeader out - Nothing -> throwError $ "Request key not found: " <> sshow keyHash - - fromTx :: (Transaction, TransactionOutput) -> ExceptT String IO (Command Text, TransactionOutput) - fromTx (!tx, !out) = do - !tx' <- except $ toPactTx tx + out <- case eitherDecodeStrict' output of + Left err -> throwError $ + "error decoding tx output for command " <> sshow (Pact5._cmdHash _cmd) <> ": " <> err + Right decodedOutput -> return decodedOutput + when (Pact5._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 txBytes, !out) = do + !tx' <- except $ eitherDecodeStrict' txBytes & _Left %~ + (\decodeErr -> "Transaction failed to decode: " <> decodeErr) return (tx', out) - checkBadList :: Vector RequestKey -> IO (Vector (RequestKey, CommandResult Hash)) + checkBadList :: Vector Pact5.RequestKey -> IO (Vector (Pact5.RequestKey, Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info)))) checkBadList rkeys = do - let !hashes = V.map requestKeyToTransactionHash rkeys + let !hashes = V.map pact5RequestKeyToTransactionHash rkeys out <- mempoolCheckBadList mempool hashes - let bad = V.map (RequestKey . Hash . unTransactionHash . fst) $ + let bad = V.map (Pact5.RequestKey . Pact5.Hash . unTransactionHash . fst) $ V.filter snd $ V.zip hashes out return $! V.map hashIsOnBadList bad - hashIsOnBadList :: RequestKey -> (RequestKey, CommandResult Hash) + hashIsOnBadList :: Pact5.RequestKey -> (Pact5.RequestKey, Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info))) hashIsOnBadList rk = - let res = PactResult (Left err) - err = PactError TxFailure noInfo [] doc - doc = pretty (T.pack $ show InsertErrorBadlisted) - !cr = CommandResult rk Nothing res 0 Nothing Nothing Nothing [] + let res = Pact5.PactResultErr err + err = Pact5.PELegacyError $ + Pact5.LegacyPactError Pact5.LegacyTxFailure "" [] "Transaction is badlisted because it previously failed to validate." + !cr = Pact5.CommandResult rk Nothing res (mempty :: Pact5.Gas) Nothing Nothing Nothing [] in (rk, cr) - enrichCR :: BlockHeader -> CommandResult Hash -> ExceptT String IO (CommandResult Hash) - enrichCR bh = return . set crMetaData + enrichCR :: BlockHeader -> Pact5.CommandResult i e -> Pact5.CommandResult i e + enrichCR bh = set Pact5.crMetaData (Just $ object [ "blockHeight" .= view blockHeight bh , "blockTime" .= view blockCreationTime bh @@ -681,33 +704,42 @@ internalPoll pdb bhdb mempool pactEx confDepth requestKeys0 = do barf :: Monad m => e -> Maybe a -> ExceptT e m a barf e = maybe (throwError e) return -toPactTx :: Transaction -> Either String (Command Text) -toPactTx (Transaction b) = note "toPactTx failure" (decodeStrict' b) - -- 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 -> Command Text -> Either String ChainwebTransaction +validateCommand :: ChainwebVersion -> ChainId -> Pact4.Command Text -> Either String Pact4.Transaction validateCommand v cid (fmap encodeUtf8 -> cmdBs) = case parsedCmd of - Right (commandParsed :: ChainwebTransaction) -> - case assertCommand commandParsed (validPPKSchemes v cid bh) (isWebAuthnPrefixLegal v cid bh) of - Left err -> Left $ "Command failed validation: " ++ displayAssertCommandError err + 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 () -> Right commandParsed Left e -> Left $ "Pact parsing error: " ++ e where bh = maxBound :: BlockHeight decodeAndParse bs = - traverse (parsePact (pactParserVersion v cid bh)) =<< Aeson.eitherDecodeStrict' bs - parsedCmd = mkPayloadWithText <$> - cmdPayload (\bs -> (bs,) <$> decodeAndParse bs) cmdBs + traverse (Pact4.parsePact) =<< Aeson.eitherDecodeStrict' bs + parsedCmd = Pact4.mkPayloadWithText <$> + Pact4.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 -> ChainId -> Pact5.Command Text -> Either String Pact5.Transaction +validatePact5Command _v _cid cmdText = case parsedCmd of + Right (commandParsed :: Pact5.Transaction) -> + if Pact5.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 :: RequestKey -> Handler () -validateRequestKey (RequestKey h'@(Hash h)) +validateRequestKey :: Pact4.RequestKey -> Handler () +validateRequestKey (Pact4.RequestKey h'@(Pact4.Hash h)) | keyLength == blakeHashLength = return () | otherwise = throwError $ setErrText ( "Request Key " - <> Pact.hashToText h' + <> Pact4.hashToText h' <> " has incorrect hash of length " <> sshow keyLength ) err400 @@ -720,5 +752,5 @@ validateRequestKey (RequestKey h'@(Hash h)) -- Blake2b_256 hash -- blakeHashLength :: Int - blakeHashLength = Pact.hashLength Pact.Blake2b_256 + blakeHashLength = Pact4.hashLength Pact4.Blake2b_256 {-# INLINE validateRequestKey #-} diff --git a/src/Chainweb/Pact/Service/BlockValidation.hs b/src/Chainweb/Pact/Service/BlockValidation.hs index 224126ea7a..9137e762f1 100644 --- a/src/Chainweb/Pact/Service/BlockValidation.hs +++ b/src/Chainweb/Pact/Service/BlockValidation.hs @@ -31,9 +31,7 @@ module Chainweb.Pact.Service.BlockValidation import Data.Vector (Vector) import Data.HashMap.Strict (HashMap) -import Pact.Types.Hash -import Pact.Types.Persistence (RowKey, TxLog, Domain) -import Pact.Types.RowData (RowData) +import qualified Pact.Core.Persistence as Pact5 import Chainweb.BlockHash import Chainweb.BlockHeader @@ -41,22 +39,30 @@ import Chainweb.BlockHeight import Chainweb.Mempool.Mempool (InsertError) import Chainweb.Miner.Pact import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Payload -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils - - -newBlock :: Miner -> NewBlockFill -> ParentHeader -> PactQueue -> IO (Historical BlockInProgress) +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 :: Miner -> NewBlockFill -> ParentHeader -> PactQueue -> IO (Historical (ForSomePactVersion BlockInProgress)) newBlock mi fill parent reqQ = do - let !msg = NewBlockMsg NewBlockReq + let + !msg = NewBlockMsg NewBlockReq { _newBlockMiner = mi , _newBlockFill = fill , _newBlockParent = parent } submitRequestAndWait reqQ msg -continueBlock :: BlockInProgress -> PactQueue -> IO (Historical BlockInProgress) +continueBlock :: BlockInProgress pv -> PactQueue -> IO (Historical (BlockInProgress pv)) continueBlock bip reqQ = do let !msg = ContinueBlockMsg (ContinueBlockReq bip) submitRequestAndWait reqQ msg @@ -77,7 +83,7 @@ local :: Maybe LocalPreflightSimulation -> Maybe LocalSignatureVerification -> Maybe RewindDepth - -> ChainwebTransaction + -> Pact4.UnparsedTransaction -> PactQueue -> IO LocalResult local preflight sigVerify rd ct reqQ = do @@ -91,9 +97,9 @@ local preflight sigVerify rd ct reqQ = do lookupPactTxs :: Maybe ConfirmationDepth - -> Vector PactHash + -> Vector ShortByteString -> PactQueue - -> IO (HashMap PactHash (T2 BlockHeight BlockHash)) + -> IO (HashMap ShortByteString (T2 BlockHeight BlockHash)) lookupPactTxs confDepth txs reqQ = do let !req = LookupPactTxsReq confDepth txs let !msg = LookupPactTxsMsg req @@ -112,9 +118,9 @@ pactReadOnlyReplay l u reqQ = do submitRequestAndWait reqQ msg pactPreInsertCheck - :: Vector ChainwebTransaction + :: Vector (Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text)) -> PactQueue - -> IO (Vector (Either InsertError ())) + -> IO (Vector (Maybe InsertError)) pactPreInsertCheck txs reqQ = do let !req = PreInsertCheckReq txs let !msg = PreInsertCheckMsg req @@ -122,7 +128,7 @@ pactPreInsertCheck txs reqQ = do pactBlockTxHistory :: BlockHeader - -> Domain RowKey RowData + -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info -> PactQueue -> IO (Historical BlockTxHistory) pactBlockTxHistory bh d reqQ = do @@ -132,10 +138,10 @@ pactBlockTxHistory bh d reqQ = do pactHistoricalLookup :: BlockHeader - -> Domain RowKey RowData - -> RowKey + -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info + -> Pact5.RowKey -> PactQueue - -> IO (Historical (Maybe (TxLog RowData))) + -> IO (Historical (Maybe (Pact5.TxLog Pact5.RowData))) pactHistoricalLookup bh d k reqQ = do let !req = HistoricalLookupReq bh d k let !msg = HistoricalLookupMsg req diff --git a/src/Chainweb/Pact/Service/PactInProcApi.hs b/src/Chainweb/Pact/Service/PactInProcApi.hs index 1f9ecfdbff..490b3e5fa9 100644 --- a/src/Chainweb/Pact/Service/PactInProcApi.hs +++ b/src/Chainweb/Pact/Service/PactInProcApi.hs @@ -22,6 +22,7 @@ module Chainweb.Pact.Service.PactInProcApi ( withPactService , withPactService' + , pactMemPoolAccess ) where import Control.Concurrent.Async @@ -39,13 +40,13 @@ import Chainweb.ChainId import Chainweb.Logger import Chainweb.Mempool.Consensus import Chainweb.Mempool.Mempool -import Chainweb.Pact.Backend.Types + import Chainweb.Pact.Backend.Utils -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import qualified Chainweb.Pact.PactService as PS import Chainweb.Pact.Service.PactQueue import Chainweb.Payload.PayloadStore -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils import Chainweb.Version @@ -53,6 +54,8 @@ 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 @@ -130,12 +133,12 @@ pactMemPoolGetBlock => MempoolConsensus -> logger -> BlockFill - -> (MempoolPreBlockCheck ChainwebTransaction + -> (MempoolPreBlockCheck Pact4.UnparsedTransaction to -> BlockHeight -> BlockHash - -> BlockHeader - -> IO (Vector ChainwebTransaction)) -pactMemPoolGetBlock mpc theLogger bf validate height hash _bHeader = do + -> 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 diff --git a/src/Chainweb/Pact/Service/PactQueue.hs b/src/Chainweb/Pact/Service/PactQueue.hs index 330b5ddffd..a489973d6e 100644 --- a/src/Chainweb/Pact/Service/PactQueue.hs +++ b/src/Chainweb/Pact/Service/PactQueue.hs @@ -46,7 +46,7 @@ import Numeric.Natural -- internal modules -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Time import Chainweb.Utils diff --git a/src/Chainweb/Pact/Service/Types.hs b/src/Chainweb/Pact/Service/Types.hs deleted file mode 100644 index ea5edb8f9f..0000000000 --- a/src/Chainweb/Pact/Service/Types.hs +++ /dev/null @@ -1,619 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveFunctor #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DeriveTraversable #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TemplateHaskell #-} --- | --- Module: Chainweb.Pact.Service.Types --- Copyright: Copyright © 2018 Kadena LLC. --- License: See LICENSE file --- Maintainer: Mark Nichols --- Stability: experimental --- --- Types module for Pact execution API - -module Chainweb.Pact.Service.Types - ( NewBlockReq(..) - , NewBlockFill(..) - , ContinueBlockReq(..) - , ValidateBlockReq(..) - , SyncToBlockReq(..) - , LocalReq(..) - , LookupPactTxsReq(..) - , PreInsertCheckReq(..) - , BlockTxHistoryReq(..) - , HistoricalLookupReq(..) - , ReadOnlyReplayReq(..) - - , RequestMsg(..) - , SubmittedRequestMsg(..) - , RequestStatus(..) - , RequestCancelled(..) - , PactServiceConfig(..) - - , LocalPreflightSimulation(..) - , LocalSignatureVerification(..) - , RewindDepth(..) - , ConfirmationDepth(..) - , RewindLimit(..) - - , BlockValidationFailureMsg(..) - , LocalResult(..) - , _LocalResultLegacy - , BlockTxHistory(..) - - , PactException(..) - , PactExceptionTag(..) - , GasPurchaseFailure(..) - , gasPurchaseFailureHash - , SpvRequest(..) - - , TransactionOutputProofB64(..) - - , internalError - , throwIfNoHistory - - , ModuleCache(..) - , filterModuleCacheByKey - , moduleCacheToHashMap - , moduleCacheFromHashMap - , moduleCacheKeys - , cleanModuleCache - - , BlockInProgress(..) - , blockInProgressPendingData - , blockInProgressTxId - , blockInProgressModuleCache - , blockInProgressParentHeader - , blockInProgressRemainingGasLimit - , blockInProgressMiner - , blockInProgressTransactions - , emptyBlockInProgressForTesting - , blockInProgressToPayloadWithOutputs - , Transactions(..) - , transactionPairs - , transactionCoinbase - , toPayloadWithOutputs - , toHashCommandResult - , module Chainweb.Pact.Backend.Types - ) where - -import Control.DeepSeq -import Control.Exception (asyncExceptionFromException, asyncExceptionToException) -import Control.Concurrent.STM -import Control.Lens hiding ((.=)) -import Control.Monad.Catch -import Control.Applicative - -import Data.Aeson -import qualified Data.ByteString.Short as SB -import Data.HashMap.Strict (HashMap) -import qualified Data.List.NonEmpty as NE -import Data.Text (Text, unpack) -import qualified Data.Text.Encoding as T -import Data.Vector (Vector) -import Data.Word (Word64) - -import GHC.Generics -import Numeric.Natural (Natural) - --- internal pact modules - -import qualified Pact.Types.ChainId as Pact -import Pact.Types.Command -import Pact.Types.PactError -import Pact.Types.Gas -import Pact.Types.Hash -import Pact.Types.Persistence -import Pact.Types.RowData -import Pact.Types.Runtime hiding (ChainId) -import qualified Pact.JSON.Encode as J -import qualified Pact.JSON.Legacy.HashMap as LHM -import qualified Pact.Utils.StableHashMap as SHM - --- internal chainweb modules - -import Chainweb.BlockHash ( BlockHash ) -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Mempool.Mempool (InsertError(..),TransactionHash) -import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.DbCache -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.NoCoinbase -import Chainweb.Payload -import Chainweb.Time -import Chainweb.Transaction -import Chainweb.Utils -import Chainweb.Version -import Chainweb.Version.Mainnet -import GHC.Stack - --- | Value that represents a limitation for rewinding. -newtype RewindLimit = RewindLimit { _rewindLimit :: Word64 } - deriving (Eq, Ord) - deriving newtype (Show, FromJSON, ToJSON, Enum, Bounded) - --- | Value that represents how far to go backwards while rewinding. -newtype RewindDepth = RewindDepth { _rewindDepth :: Word64 } - deriving (Eq, Ord) - deriving newtype (Show, FromJSON, ToJSON, Enum, Bounded) - -newtype ConfirmationDepth = ConfirmationDepth { _confirmationDepth :: Word64 } - deriving (Eq, Ord) - deriving newtype (Show, FromJSON, ToJSON, Enum, Bounded) - --- | 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 - , _pactBlockGasLimit :: !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. - } deriving (Eq,Show) - -data GasPurchaseFailure = GasPurchaseFailure TransactionHash PactError - deriving (Eq,Generic) -instance Show GasPurchaseFailure where show = unpack . J.encodeText - -instance J.Encode GasPurchaseFailure where - build (GasPurchaseFailure h e) = J.build (J.Array (h, e)) - -gasPurchaseFailureHash :: GasPurchaseFailure -> TransactionHash -gasPurchaseFailureHash (GasPurchaseFailure h _) = h - --- | Used by /local to trigger user signature verification --- -data LocalSignatureVerification - = Verify - | NoVerify - deriving stock (Eq, Show, Generic) - --- | Used by /local to trigger preflight simulation --- -data LocalPreflightSimulation - = PreflightSimulation - | LegacySimulation - deriving stock (Eq, Show, Generic) - -newtype BlockValidationFailureMsg = BlockValidationFailureMsg Text - deriving (Eq, Ord, Generic) - deriving newtype (J.Encode) - --- | The type of local results (used in /local endpoint) --- -data LocalResult - = MetadataValidationFailure !(NE.NonEmpty Text) - | LocalResultWithWarns !(CommandResult Hash) ![Text] - | LocalResultLegacy !(CommandResult Hash) - | LocalTimeout - deriving (Show, Generic) - -makePrisms ''LocalResult - -instance NFData LocalResult where - rnf (MetadataValidationFailure t) = rnf t - rnf (LocalResultWithWarns cr ws) = rnf cr `seq` rnf ws - rnf (LocalResultLegacy cr) = rnf cr - rnf LocalTimeout = () - -instance J.Encode LocalResult where - build (MetadataValidationFailure e) = J.object - [ "preflightValidationFailures" J..= J.Array (J.text <$> e) - ] - build (LocalResultLegacy cr) = J.build cr - build (LocalResultWithWarns cr ws) = J.object - [ "preflightResult" J..= cr - , "preflightWarnings" J..= J.Array (J.text <$> ws) - ] - build LocalTimeout = J.text "Transaction timed out" - {-# INLINE build #-} - -instance FromJSON LocalResult where - parseJSON v = - withText - "LocalResult" - (\s -> if s == "Transaction timed out" then pure LocalTimeout else fail "Invalid LocalResult") - v - <|> withObject - "LocalResult" - (\o -> metaFailureParser o - <|> localWithWarnParser o - <|> legacyFallbackParser o - ) - v - where - metaFailureParser o = - MetadataValidationFailure <$> o .: "preflightValidationFailure" - localWithWarnParser o = LocalResultWithWarns - <$> o .: "preflightResult" - <*> o .: "preflightWarnings" - legacyFallbackParser _ = LocalResultLegacy <$> parseJSON v - --- | 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 !PactHash !Text - | CoinbaseFailure !Text - | NoBlockValidatedYet - | TransactionValidationException ![(PactHash, Text)] - | PactDuplicateTableError !Text - | TransactionDecodeFailure !Text - | RewindLimitExceeded - { _rewindExceededLimit :: !RewindLimit - -- ^ Rewind limit - , _rewindExceededLast :: !(Maybe BlockHeader) - -- ^ current header - , _rewindExceededTarget :: !(Maybe BlockHeader) - -- ^ target header - } - | BlockHeaderLookupFailure !Text - | BuyGasFailure !GasPurchaseFailure - | MempoolFillFailure !Text - | BlockGasLimitExceeded !Gas - | FullHistoryRequired - { _earliestBlockHeight :: !BlockHeight - , _genesisHeight :: !BlockHeight - } - deriving stock Generic - -instance Show PactException where - show = 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 (TransactionValidationException 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 (BuyGasFailure failure) = tagged "BuyGasFailure" failure - build (MempoolFillFailure msg) = tagged "MempoolFillFailure" msg - 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) - ] - -tagged :: J.Encode v => Text -> v -> J.Builder -tagged t v = J.object - [ "tag" J..= t - , "contents" J..= v - ] - -instance Exception PactException - --- | 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" - - -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 --- --- +-------------------+ 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 -> RequestMsg (Historical BlockInProgress) - NewBlockMsg :: !NewBlockReq -> RequestMsg (Historical BlockInProgress) - ValidateBlockMsg :: !ValidateBlockReq -> RequestMsg PayloadWithOutputs - LocalMsg :: !LocalReq -> RequestMsg LocalResult - LookupPactTxsMsg :: !LookupPactTxsReq -> RequestMsg (HashMap PactHash (T2 BlockHeight BlockHash)) - PreInsertCheckMsg :: !PreInsertCheckReq -> RequestMsg (Vector (Either InsertError ())) - BlockTxHistoryMsg :: !BlockTxHistoryReq -> RequestMsg (Historical BlockTxHistory) - HistoricalLookupMsg :: !HistoricalLookupReq -> RequestMsg (Historical (Maybe (TxLog 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 - { _newBlockMiner :: !Miner - , _newBlockFill :: !NewBlockFill - -- ^ whether to fill this block with transactions; if false, the block - -- will be empty. - , _newBlockParent :: !ParentHeader - -- ^ the parent to use for the new block - } deriving stock Show - -data NewBlockFill = NewBlockFill | NewBlockEmpty - deriving stock Show - -newtype ContinueBlockReq - = ContinueBlockReq BlockInProgress - deriving stock Show - -data ValidateBlockReq = ValidateBlockReq - { _valBlockHeader :: !BlockHeader - , _valCheckablePayload :: !CheckablePayload - } deriving stock Show - -data LocalReq = LocalReq - { _localRequest :: !ChainwebTransaction - , _localPreflight :: !(Maybe LocalPreflightSimulation) - , _localSigVerification :: !(Maybe LocalSignatureVerification) - , _localRewindDepth :: !(Maybe RewindDepth) - } -instance Show LocalReq where show LocalReq{..} = show _localRequest - -data LookupPactTxsReq = LookupPactTxsReq - { _lookupConfirmationDepth :: !(Maybe ConfirmationDepth) - , _lookupKeys :: !(Vector PactHash) - } -instance Show LookupPactTxsReq where - show (LookupPactTxsReq m _) = - "LookupPactTxsReq@" ++ show m - -data PreInsertCheckReq = PreInsertCheckReq - { _preInsCheckTxs :: !(Vector ChainwebTransaction) - } -instance Show PreInsertCheckReq where - show (PreInsertCheckReq v) = - "PreInsertCheckReq@" ++ show v - -data BlockTxHistoryReq = BlockTxHistoryReq - { _blockTxHistoryHeader :: !BlockHeader - , _blockTxHistoryDomain :: !(Domain RowKey RowData) - } -instance Show BlockTxHistoryReq where - show (BlockTxHistoryReq h d) = - "BlockTxHistoryReq@" ++ show h ++ ", " ++ show d - -data HistoricalLookupReq = HistoricalLookupReq - { _historicalLookupHeader :: !BlockHeader - , _historicalLookupDomain :: !(Domain RowKey RowData) - , _historicalLookupRowKey :: !RowKey - } -instance Show HistoricalLookupReq where - show (HistoricalLookupReq h d k) = - "HistoricalLookupReq@" ++ show h ++ ", " ++ show d ++ ", " ++ show k - -data ReadOnlyReplayReq = ReadOnlyReplayReq - { _readOnlyReplayLowerBound :: !BlockHeader - , _readOnlyReplayUpperBound :: !(Maybe BlockHeader) - } -instance Show ReadOnlyReplayReq where - show (ReadOnlyReplayReq l u) = - "ReadOnlyReplayReq@" ++ show l ++ ", " ++ show u - -data SyncToBlockReq = SyncToBlockReq - { _syncToBlockHeader :: !BlockHeader - } -instance Show SyncToBlockReq where show SyncToBlockReq{..} = show _syncToBlockHeader - -data SpvRequest = SpvRequest - { _spvRequestKey :: !RequestKey - , _spvTargetChainId :: !Pact.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 FromJSON SpvRequest where - parseJSON = withObject "SpvRequest" $ \o -> SpvRequest - <$> o .: "requestKey" - <*> o .: "targetChainId" - {-# INLINE parseJSON #-} - -newtype TransactionOutputProofB64 = TransactionOutputProofB64 Text - deriving stock (Eq, Show, Generic) - deriving newtype (ToJSON, FromJSON) - --- -------------------------------------------------------------------------- -- --- Module Cache - --- | 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 . onChain cid of - ForkAtBlockHeight bh' -> bh == bh' - ForkAtGenesis -> bh == genesisHeight v cid - ForkNever -> False - --- State from a block in progress, which is used to extend blocks after --- running their payloads. -data BlockInProgress = BlockInProgress - { _blockInProgressPendingData :: !SQLitePendingData - , _blockInProgressTxId :: !TxId - , _blockInProgressModuleCache :: !ModuleCache - , _blockInProgressParentHeader :: !ParentHeader - , _blockInProgressRemainingGasLimit :: !GasLimit - , _blockInProgressMiner :: !Miner - , _blockInProgressTransactions :: !(Transactions (CommandResult [TxLogJson])) - } deriving stock (Eq, Show) - --- This block is not really valid, don't use it outside tests. -emptyBlockInProgressForTesting :: BlockInProgress -emptyBlockInProgressForTesting = BlockInProgress - { _blockInProgressPendingData = emptySQLitePendingData - , _blockInProgressTxId = TxId 0 - , _blockInProgressModuleCache = mempty - , _blockInProgressParentHeader = - ParentHeader (genesisBlockHeader mainnet (unsafeChainId 0)) - , _blockInProgressRemainingGasLimit = GasLimit 0 - , _blockInProgressMiner = noMiner - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = noCoinbase - , _transactionPairs = mempty - } - } - -blockInProgressToPayloadWithOutputs :: BlockInProgress -> PayloadWithOutputs -blockInProgressToPayloadWithOutputs bip = toPayloadWithOutputs - (_blockInProgressMiner bip) - (_blockInProgressTransactions bip) - -toPayloadWithOutputs :: Miner -> Transactions (CommandResult [TxLogJson]) -> PayloadWithOutputs -toPayloadWithOutputs mi ts = - let oldSeq = _transactionPairs ts - trans = cmdBSToTx . fst <$> oldSeq - transOuts = toOutputBytes . toHashCommandResult . snd <$> oldSeq - - miner = toMinerData mi - cb = CoinbaseOutput $ J.encodeStrict $ toHashCommandResult $ _transactionCoinbase ts - blockTrans = snd $ newBlockTransactions miner trans - cmdBSToTx = toTransactionBytes - . fmap (T.decodeUtf8 . SB.fromShort . payloadBytes) - blockOuts = snd $ newBlockOutputs cb transOuts - - blockPL = blockPayload blockTrans blockOuts - plData = payloadData blockTrans blockPL - in payloadWithOutputs plData cb transOuts - -toTransactionBytes :: Command Text -> Transaction -toTransactionBytes cwTrans = - let plBytes = J.encodeStrict cwTrans - in Transaction { _transactionBytes = plBytes } - -toOutputBytes :: CommandResult Hash -> TransactionOutput -toOutputBytes cr = - let outBytes = J.encodeStrict cr - in TransactionOutput { _transactionOutputBytes = outBytes } - -toHashCommandResult :: CommandResult [TxLogJson] -> CommandResult Hash -toHashCommandResult = over (crLogs . _Just) $ pactHash . encodeTxLogJsonArray - -data Transactions r = Transactions - { _transactionPairs :: !(Vector (ChainwebTransaction, r)) - , _transactionCoinbase :: !(CommandResult [TxLogJson]) - } - deriving stock (Functor, Foldable, Traversable, Eq, Show, Generic) - deriving anyclass NFData -makeLenses 'Transactions -makeLenses 'BlockInProgress diff --git a/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs index f027f015f3..41ddd3695f 100644 --- a/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs +++ b/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.CoinV3Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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 index d2bc73f404..c12a24b8f4 100644 --- a/src/Chainweb/Pact/Transactions/CoinV4Transactions.hs +++ b/src/Chainweb/Pact/Transactions/CoinV4Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.CoinV4Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t in unsafePerformIO $ mapM decodeTx [ "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0" , diff --git a/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs index 39c9d259a5..19bd9563d6 100644 --- a/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs +++ b/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.CoinV5Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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 index ad5d8a6cb3..2eea8935e4 100644 --- a/src/Chainweb/Pact/Transactions/CoinV6Transactions.hs +++ b/src/Chainweb/Pact/Transactions/CoinV6Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.CoinV6Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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 index 6ba5186ede..e4c81f1994 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet0Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet1Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs index 09024cf524..c89811abb8 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet1Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet2Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs index 2ed4c34336..8504c9af74 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet2Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet3Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs index cce8a14b2c..eb3a4dec14 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet3Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet4Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs index 114ad275ff..8259c60c75 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet4Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet5Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs index c24ec780d1..633f7fe335 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet5Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet6Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs index 0183e4d61d..2475deda9a 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet6Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet7Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs index 383a27cfde..b12a2eda30 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet7Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet8Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs index e18f1a3f43..87e87b4208 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet8Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Mainnet9Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs index b4bb7b5cc4..bd9910d976 100644 --- a/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs +++ b/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.Mainnet9Transactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/MainnetKADTransactions.hs b/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs index 4e25546216..9f161a9c0c 100644 --- a/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs +++ b/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.MainnetKADTransactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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 index 1b33431b73..02c9ecfec0 100644 --- a/src/Chainweb/Pact/Transactions/OtherTransactions.hs +++ b/src/Chainweb/Pact/Transactions/OtherTransactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.OtherTransactions ( transactions ) where import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/RecapDevelopmentTransactions.hs b/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs index 5313f8b995..c87145ed38 100644 --- a/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs +++ b/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs @@ -7,13 +7,13 @@ module Chainweb.Pact.Transactions.RecapDevelopmentTransactions ( transactions ) import Data.Bifunctor (first) import System.IO.Unsafe -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -transactions :: [ChainwebTransaction] +transactions :: [Pact4.Transaction] transactions = let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (chainwebPayloadCodec maxBound) =<< decodeB64UrlNoPaddingText 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/Types.hs b/src/Chainweb/Pact/Types.hs index c0433d5433..b3bacc5077 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -9,12 +9,18 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StrictData #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE GADTs #-} -- | -- Module: Chainweb.Pact.Types -- Copyright: Copyright © 2018 Kadena LLC. @@ -25,57 +31,16 @@ -- Pact Types module for Chainweb -- module Chainweb.Pact.Types - ( -- * Pact Db State - PactDbStatePersist(..) - , pdbspRestoreFile - , pdbspPactDbState - - -- * Misc helpers - - , GasSupply(..) - , GasId(..) - , EnforceCoinbaseFailure(..) - , CoinbaseUsePrecompiled(..) - , cleanModuleCache - - -- * 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 + -- ( Pact4GasSupply(..) + -- , Pact5GasSupply(..) + -- , cleanModuleCache -- * Pact Service Env - , PactServiceEnv(..) + ( PactServiceEnv(..) , psMempoolAccess , psCheckpointer , psPdb , psBlockHeaderDb - , psGasModel , psMinerRewards , psReorgLimit , psPreInsertCheckTimeout @@ -87,66 +52,121 @@ module Chainweb.Pact.Types , psBlockGasLimit , psEnableLocalTimeout , psTxFailuresCounter - - -- * TxContext - , TxContext(..) - , ctxToPublicData - , ctxToPublicData' - , ctxBlockHeader - , ctxCurrentBlockHeight - , ctxChainId - , ctxVersion - , guardCtx - , getTxContext - + , psTxTimeLimit + -- -- * 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(..) + , LocalReq(..) + , ReadOnlyReplayReq(..) + , ConfirmationDepth(..) + , LocalPreflightSimulation(..) + , _MetadataValidationFailure + , _LocalResultWithWarns + , _LocalResultLegacy + , _LocalTimeout + , SyncToBlockReq(..) + , RequestMsg(..) + , RewindLimit(..) + , LookupPactTxsReq(..) + , BlockTxHistoryReq(..) + , PreInsertCheckReq(..) + , SpvRequest(..) + , HistoricalLookupReq(..) + , TransactionOutputProofB64(..) + , RequestStatus(..) + , internalError + , LocalSignatureVerification(..) + , Pact4GasPurchaseFailure(..) + , CoinbaseFailure(..) + , Pact5CoinbaseError(..) + , Pact5BuyGasError(..) + , _BuyGasPactError + , Pact5RedeemGasError(..) + , _RedeemGasPactError + , Pact5GasPurchaseFailure(..) + , _BuyGasError + , _RedeemGasError + , _PurchaseGasTxTooBigForGasLimit + , Transactions(..) + , transactionPairs + , transactionCoinbase + , MemPoolAccess(..) + , ModuleCacheFor(..) + , BlockValidationFailureMsg(..) + , toPayloadWithOutputs + , hashPact4TxLogs + , hashPact5TxLogs + , PactServiceConfig(..) + , RequestCancelled(..) + , convertPact5Error + -- * Module cache , ModuleInitCache - , getInitCache - , updateInitCache - , updateInitCacheM -- * Pact Service Monad , PactServiceM(..) , runPactServiceM , evalPactServiceM , execPactServiceM + , PactDbFor - , PactBlockM(..) - , liftPactServiceM , PactBlockEnv(..) , psBlockDbEnv , psParentHeader + , psIsGenesis , psServiceEnv - , runPactBlockM -- * Logging with Pact logger - , tracePactBlockM - , tracePactBlockM' + -- , tracePactBlockM + -- , tracePactBlockM' , pactLoggers , logg_ , logInfo_ , logWarn_ , logError_ , logDebug_ - , logg - , logInfo - , logWarn - , logError - , logDebug + , logPact + , logInfoPact + , logWarnPact + , logErrorPact + , logDebugPact , logJsonTrace_ - , logJsonTrace - , localLabel - , localLabelBlock + , logJsonTracePact + , localLabelPact -- * types , TxTimeout(..) , ApplyCmdExecutionContext(..) - , TxFailureLog(..) + , Pact4TxFailureLog(..) + , Pact5TxFailureLog(..) -- * miscellaneous , defaultOnFatalError @@ -154,8 +174,6 @@ module Chainweb.Pact.Types , testPactServiceConfig , testBlockGasLimit , defaultModuleCacheLimit - , catchesPactError - , UnexpectedErrorPrinting(..) , defaultPreInsertCheckTimeout , withPactState ) where @@ -171,9 +189,10 @@ import Control.Monad.State.Strict import Data.Aeson hiding (Error,(.=)) import Data.IORef import Data.LogMessage -import Data.Set (Set) import qualified Data.Map.Strict as M -import Data.Text (pack, unpack, Text) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Encoding as T import GHC.Generics (Generic) @@ -181,195 +200,235 @@ import System.LogLevel -- internal pact modules -import Pact.Interpreter (PactDbEnv) -import qualified Pact.JSON.Encode as J -import Pact.Parse (ParsedDecimal) -import Pact.Types.ChainId (NetworkId) -import Pact.Types.ChainMeta -import Pact.Types.Command -import Pact.Types.Gas -import Pact.Types.Info (noInfo) -import Pact.Types.Persistence (ExecutionMode, TxLogJson) -import Pact.Types.Pretty (viaShow) -import Pact.Types.Runtime (ExecutionConfig(..), PactWarning, PactError(..), PactErrorType(..)) -import Pact.Types.SPV -import Pact.Types.Term -import qualified Pact.Types.Logger as P +import qualified Pact.Core.Builtin as Pact5 +import qualified Pact.Core.Errors as Pact5 +import qualified Pact.Core.Evaluate 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.Counter -import Chainweb.Mempool.Mempool (TransactionHash) +import Chainweb.Mempool.Mempool (TransactionHash, BlockFill, MempoolPreBlockCheck, InsertError) import Chainweb.Miner.Pact import Chainweb.Logger import Chainweb.Pact.Backend.DbCache import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Service.Types + import Chainweb.Payload.PayloadStore import Chainweb.Time import Chainweb.Utils import Chainweb.Version -import Utils.Logging.Trace +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 -data PactDbStatePersist = PactDbStatePersist - { _pdbspRestoreFile :: !(Maybe FilePath) - , _pdbspPactDbState :: !PactDbState - } -makeLenses ''PactDbStatePersist + +-- | 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 ParentHeader -> IO (a, BlockHeader)) + | Pact5RunnableBlock (PactDbFor logger Pact5 -> Maybe ParentHeader -> BlockHandle -> IO ((a, BlockHeader), BlockHandle)) -- -------------------------------------------------------------------------- -- -- Coinbase output utils --- | 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 - -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 - -- -------------------------------------------------------------------- -- -- Local vs. Send execution context flag data ApplyCmdExecutionContext = ApplyLocal | ApplySend --- -------------------------------------------------------------------- -- --- Tx Execution Service Monad - --- | Transaction execution state +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 + +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 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")) +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 } -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 + deriving stock (Show, Generic) + deriving anyclass (Exception) + +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 + +-- | 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 ()) + } --- | 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 +instance Semigroup MemPoolAccess where + MemPoolAccess f g h i <> MemPoolAccess t u v w = + MemPoolAccess (f <> t) (g <> u) (h <> v) (i <> w) --- | 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 - - - --- | 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 - } deriving Show - -instance HasChainId TxContext where - _chainId = _chainId . _tcParentHeader -instance HasChainwebVersion TxContext where - _chainwebVersion = _chainwebVersion . _tcParentHeader +instance Monoid MemPoolAccess where + mempty = MemPoolAccess mempty mempty mempty mempty --- -------------------------------------------------------------------- -- --- Pact Service Monad data PactServiceEnv logger tbl = PactServiceEnv { _psMempoolAccess :: !(Maybe MemPoolAccess) , _psCheckpointer :: !(Checkpointer logger) , _psPdb :: !(PayloadDb tbl) , _psBlockHeaderDb :: !BlockHeaderDb - , _psGasModel :: !(TxContext -> GasModel) , _psMinerRewards :: !MinerRewards , _psPreInsertCheckTimeout :: !Micros -- ^ Maximum allowed execution time for the transactions validation. @@ -381,10 +440,11 @@ data PactServiceEnv logger tbl = PactServiceEnv , _psLogger :: !logger , _psGasLogger :: !(Maybe logger) - , _psBlockGasLimit :: !GasLimit + , _psBlockGasLimit :: !Pact4.GasLimit , _psEnableLocalTimeout :: !Bool , _psTxFailuresCounter :: !(Maybe (Counter "txFailures")) + , _psTxTimeLimit :: !(Maybe Micros) } makeLenses ''PactServiceEnv @@ -409,6 +469,43 @@ defaultPreInsertCheckTimeout = 1000000 -- 1 second 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. + } deriving (Eq,Show) + + -- | NOTE this is only used for tests/benchmarks. DO NOT USE IN PROD testPactServiceConfig :: PactServiceConfig testPactServiceConfig = PactServiceConfig @@ -418,24 +515,25 @@ testPactServiceConfig = PactServiceConfig , _pactResetDb = True , _pactAllowReadsInLocal = False , _pactUnlimitedInitialRewind = False - , _pactBlockGasLimit = testBlockGasLimit + , _pactNewBlockGasLimit = testBlockGasLimit , _pactLogGas = False , _pactModuleCacheLimit = defaultModuleCacheLimit , _pactFullHistoryRequired = False , _pactEnableLocalTimeout = False , _pactPersistIntraBlockWrites = DoNotPersistIntraBlockWrites + , _pactTxTimeLimit = Nothing } -- | This default value is only relevant for testing. In a chainweb-node the @GasLimit@ -- is initialized from the @_configBlockGasLimit@ value of @ChainwebConfiguration@. -- -testBlockGasLimit :: GasLimit +testBlockGasLimit :: Pact4.GasLimit testBlockGasLimit = 100000 newtype ReorgLimitExceeded = ReorgLimitExceeded Text instance Show ReorgLimitExceeded where - show (ReorgLimitExceeded t) = "reorg limit exceeded: \n" <> unpack t + show (ReorgLimitExceeded t) = "reorg limit exceeded: \n" <> T.unpack t instance Exception ReorgLimitExceeded where fromException = asyncExceptionFromException @@ -445,106 +543,58 @@ newtype TxTimeout = TxTimeout TransactionHash deriving Show instance Exception TxTimeout -data TxFailureLog = TxFailureLog !RequestKey !PactError !Text +data Pact4TxFailureLog = Pact4TxFailureLog !Pact4.RequestKey !Pact4.PactError !Text deriving stock (Generic) deriving anyclass (NFData, Typeable) -instance LogMessage TxFailureLog where - logText (TxFailureLog rk err msg) = +instance LogMessage Pact4TxFailureLog where + logText (Pact4TxFailureLog rk err msg) = msg <> ": " <> sshow rk <> ": " <> sshow err -instance Show TxFailureLog where - show m = unpack (logText m) +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 = pack (show pex) <> "\n" <> t + errMsg = T.pack (show pex) <> "\n" <> t -type ModuleInitCache = M.Map BlockHeight ModuleCache - -data PactBlockEnv logger tbl = PactBlockEnv - { _psServiceEnv :: !(PactServiceEnv logger tbl) - , _psParentHeader :: !ParentHeader - , _psBlockDbEnv :: !(CurrentBlockDbEnv logger) - } +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 :: !ParentHeader + , _psIsGenesis :: !Bool + , _psBlockDbEnv :: !(PactDbFor logger pv) + } + makeLenses ''PactBlockEnv -instance HasChainwebVersion (PactBlockEnv logger tbl) where +instance HasChainwebVersion (PactBlockEnv logger db tbl) where chainwebVersion = psServiceEnv . chainwebVersion -instance HasChainId (PactBlockEnv logger tbl) where +instance HasChainId (PactBlockEnv logger db tbl) where chainId = psServiceEnv . chainId --- | 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@(TxContext _ pm) = PublicData - { _pdPublicMeta = pm - , _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' (TxContext ph pm) = 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 - --- | Retreive 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 = _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 :: PublicMeta -> PactBlockM logger tbl TxContext -getTxContext pm = view psParentHeader >>= \ph -> return (TxContext ph pm) - -- | 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 + ReaderT (PactServiceEnv logger tbl) (StateT PactServiceState IO) a } deriving newtype ( Functor, Applicative, Monad , MonadReader (PactServiceEnv logger tbl) @@ -576,67 +626,6 @@ withPactState inner = bracket captureState releaseState $ \ref -> do captureState = liftIO . newIORef =<< get releaseState = liftIO . readIORef >=> put --- | 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 PactServiceState IO) a - } deriving newtype - ( Functor, Applicative, Monad - , MonadReader (PactBlockEnv logger 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 (magnify psServiceEnv a) - --- | 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 - --- | 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 - --- | 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 -> CurrentBlockDbEnv logger - -> PactBlockM logger tbl a -> PactServiceM logger tbl a -runPactBlockM pctx dbEnv (PactBlockM r) = PactServiceM $ ReaderT $ \e -> StateT $ \s -> - runStateT (runReaderT r (PactBlockEnv e pctx dbEnv)) s - -- | Run a 'PactServiceM' computation given some initial -- reader and state values, returning final value and -- final program state @@ -650,7 +639,6 @@ 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 -- @@ -673,18 +661,6 @@ execPactServiceM execPactServiceM st env act = execStateT (runReaderT (_unPactServiceM act) env) st -tracePactBlockM :: (Logger logger, ToJSON param) => Text -> param -> Int -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM label param weight a = tracePactBlockM' label param (const weight) a - -tracePactBlockM' :: (Logger logger, ToJSON param) => Text -> param -> (a -> Int) -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM' label param calcWeight a = do - e <- ask - s <- get - (r, s') <- liftIO $ trace' (logJsonTrace_ (_psLogger $ _psServiceEnv e)) label param (calcWeight . fst) - $ runStateT (runReaderT (_unPactBlockM a) e) s - put s' - return r - -- -------------------------------------------------------------------------- -- -- Pact Logger @@ -697,13 +673,13 @@ pactLogLevel _ = Info -- | Create Pact Loggers that use the the chainweb logging system as backend. -- -pactLoggers :: Logger logger => logger -> P.Loggers -pactLoggers logger = P.Loggers $ P.mkLogger (error "ignored") fun (P.LogRules mempty) +pactLoggers :: Logger logger => logger -> Pact4.Loggers +pactLoggers logger = Pact4.Loggers $ Pact4.mkLogger (error "ignored") fun (Pact4.LogRules mempty) where - fun :: P.LoggerLogFun - fun _ (P.LogName n) cat msg = do - let namedLogger = addLabel ("logger", pack n) logger - logFunctionText namedLogger (pactLogLevel cat) $ pack msg + 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 -- @@ -729,43 +705,499 @@ logJsonTrace_ logger level msg = liftIO $ logFunction logger level msg -- | Write log message using the logger in Checkpointer environment -- -logg :: (Logger logger) => LogLevel -> Text -> PactServiceM logger tbl () -logg level msg = view psLogger >>= \l -> logg_ l level msg +logPact :: (Logger logger) => LogLevel -> Text -> PactServiceM logger tbl () +logPact level msg = view psLogger >>= \l -> logg_ l level msg -logInfo :: (Logger logger) => Text -> PactServiceM logger tbl () -logInfo msg = view psLogger >>= \l -> logInfo_ l msg +logInfoPact :: (Logger logger) => Text -> PactServiceM logger tbl () +logInfoPact msg = view psLogger >>= \l -> logInfo_ l msg -logWarn :: (Logger logger) => Text -> PactServiceM logger tbl () -logWarn msg = view psLogger >>= \l -> logWarn_ l msg +logWarnPact :: (Logger logger) => Text -> PactServiceM logger tbl () +logWarnPact msg = view psLogger >>= \l -> logWarn_ l msg -logError :: (Logger logger) => Text -> PactServiceM logger tbl () -logError msg = view psLogger >>= \l -> logError_ l msg +logErrorPact :: (Logger logger) => Text -> PactServiceM logger tbl () +logErrorPact msg = view psLogger >>= \l -> logError_ l msg -logDebug :: (Logger logger) => Text -> PactServiceM logger tbl () -logDebug msg = view psLogger >>= \l -> logDebug_ l msg +logDebugPact :: (Logger logger) => Text -> PactServiceM logger tbl () +logDebugPact msg = view psLogger >>= \l -> logDebug_ l msg -logJsonTrace :: (ToJSON a, Typeable a, NFData a, Logger logger) => LogLevel -> JsonLog a -> PactServiceM logger tbl () -logJsonTrace level msg = view psLogger >>= \l -> logJsonTrace_ l level 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 -localLabel :: (Logger logger) => (Text, Text) -> PactServiceM logger tbl x -> PactServiceM logger tbl x -localLabel lbl x = do +localLabelPact :: (Logger logger) => (Text, Text) -> PactServiceM logger tbl x -> PactServiceM logger tbl x +localLabelPact lbl x = do locally psLogger (addLabel lbl) x -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 - ] + +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) + deriving newtype (Show, FromJSON, ToJSON, Enum, Bounded) + +newtype ConfirmationDepth = ConfirmationDepth { _confirmationDepth :: Word64 } + deriving (Eq, Ord) + deriving newtype (Show, FromJSON, ToJSON, Enum, Bounded) + +-- | Used by /local to trigger user signature verification +-- +data LocalSignatureVerification + = Verify + | NoVerify + deriving stock (Eq, Show, Generic) + +-- | Used by /local to trigger preflight simulation +-- +data LocalPreflightSimulation + = PreflightSimulation + | LegacySimulation + deriving stock (Eq, Show, Generic) + +-- | The type of local results (used in /local endpoint) +-- +data LocalResult + = MetadataValidationFailure !(NE.NonEmpty Text) + | LocalResultWithWarns !(Pact4.CommandResult Pact4.Hash) ![Text] + | LocalResultLegacy !(Pact4.CommandResult Pact4.Hash) + | LocalPact5ResultLegacy !(Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info))) + | LocalPact5PreflightResult !(Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info))) ![Text] + | LocalTimeout + deriving stock (Show, Generic) + +makePrisms ''LocalResult + +instance J.Encode LocalResult where + build (MetadataValidationFailure e) = J.object + [ "preflightValidationFailures" J..= J.Array (J.text <$> e) + ] + build (LocalResultLegacy cr) = J.build cr + build (LocalPact5ResultLegacy cr) = J.build cr + build (LocalResultWithWarns cr ws) = J.object + [ "preflightResult" J..= cr + , "preflightWarnings" J..= J.Array (J.text <$> ws) + ] + build (LocalPact5PreflightResult cr ws) = J.object + [ "preflightResult" J..= fmap (sshow @_ @Text) cr + , "preflightWarnings" J..= J.Array (J.text <$> ws) + ] + build LocalTimeout = J.text "Transaction timed out" + {-# INLINE build #-} + +instance FromJSON LocalResult where + parseJSON v = + withText + "LocalResult" + (\s -> if s == "Transaction timed out" then pure LocalTimeout else fail "Invalid LocalResult") + v + <|> withObject + "LocalResult" + (\o -> metaFailureParser o + <|> localWithWarnParser o + <|> legacyFallbackParser o + ) + v + where + metaFailureParser o = + MetadataValidationFailure <$> o .: "preflightValidationFailure" + localWithWarnParser o = LocalResultWithWarns + <$> o .: "preflightResult" + <*> o .: "preflightWarnings" + legacyFallbackParser _ = LocalResultLegacy <$> parseJSON 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 +-- +-- +-------------------+ 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 + { _newBlockMiner :: !Miner + , _newBlockFill :: !NewBlockFill + -- ^ whether to fill this block with transactions; if false, the block + -- will be empty. + , _newBlockParent :: !ParentHeader + -- ^ 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 LookupPactTxsReq = LookupPactTxsReq + { _lookupConfirmationDepth :: !(Maybe ConfirmationDepth) + , _lookupKeys :: !(Vector ShortByteString) + } +instance Show LookupPactTxsReq where + show (LookupPactTxsReq m _) = + "LookupPactTxsReq@" ++ show m + +data PreInsertCheckReq = PreInsertCheckReq + { _preInsCheckTxs :: !(Vector (Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text))) + } +instance Show PreInsertCheckReq where + show (PreInsertCheckReq v) = + "PreInsertCheckReq@" ++ show v + +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 + +data ReadOnlyReplayReq = ReadOnlyReplayReq + { _readOnlyReplayLowerBound :: !BlockHeader + , _readOnlyReplayUpperBound :: !(Maybe BlockHeader) + } +instance Show ReadOnlyReplayReq where + show (ReadOnlyReplayReq l u) = + "ReadOnlyReplayReq@" ++ show l ++ ", " ++ show u + +data SyncToBlockReq = SyncToBlockReq + { _syncToBlockHeader :: !BlockHeader + } +instance Show SyncToBlockReq where show SyncToBlockReq{..} = show _syncToBlockHeader + +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 FromJSON SpvRequest where + parseJSON = withObject "SpvRequest" $ \o -> SpvRequest + <$> o .: "requestKey" + <*> o .: "targetChainId" + {-# INLINE parseJSON #-} + +newtype TransactionOutputProofB64 = TransactionOutputProofB64 Text + deriving stock (Eq, Show, Generic) + deriving newtype (ToJSON, FromJSON) + + +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 + , _blockInProgressModuleCache :: !(ModuleCacheFor pv) + , _blockInProgressParentHeader :: !(Maybe ParentHeader) + , _blockInProgressChainwebVersion :: !ChainwebVersion + , _blockInProgressChainId :: !ChainId + , _blockInProgressRemainingGasLimit :: !Pact4.GasLimit + , _blockInProgressMiner :: !Miner + , _blockInProgressTransactions :: !(Transactions pv (CommandResultFor pv)) + , _blockInProgressPactVersion :: !(PactVersionT pv) + } +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' + +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)) + (_parentHeader <$> _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 . _parentHeader <$> _blockInProgressParentHeader bip)) + , show (_blockInProgressMiner bip ^. minerId) + , "# transactions " <> show (V.length (_transactionPairs $ _blockInProgressTransactions bip)) <> "," + , "# gas remaining " <> show (_blockInProgressRemainingGasLimit bip) + ] + +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 + } + +-- | 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 convertPact5Error cr) + +convertPact5Error :: Pact5.PactError Pact5.Info -> Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info) +convertPact5Error err = + Pact5.PEPact5Error $ + Pact5.pactErrorToLocatedErrorCode 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) + } + 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 diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs similarity index 65% rename from src/Chainweb/Pact/Backend/ChainwebPactDb.hs rename to src/Chainweb/Pact4/Backend/ChainwebPactDb.hs index 9ed2b9ee5b..3e34ed978e 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs @@ -1,37 +1,68 @@ {-# 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 #-} -- | --- Module: Chainweb.Pact.Backend.ChainwebPactDb +-- Module: Chainweb.Pact4.Backend.ChainwebPactDb -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Emmanuel Denloye-Ito -- Stability: experimental -- -module Chainweb.Pact.Backend.ChainwebPactDb +module Chainweb.Pact4.Backend.ChainwebPactDb ( chainwebPactDb , rewoundPactDb -, rewindDbTo -, rewindDbToBlock -, commitBlockStateToDatabase , initSchema , indexPactTransaction , vacuumDb , toTxLog -, getEndTxId -, getEndTxId' +, 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 ) where import Control.Applicative import Control.Lens import Control.Monad -import Control.Monad.Catch import Control.Monad.Reader import Control.Monad.State.Strict import Control.Monad.Trans.Maybe @@ -40,12 +71,10 @@ 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.Foldable (toList) import Data.List(sort) import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NE import qualified Data.HashMap.Strict as HashMap -import Data.HashSet (HashSet) import qualified Data.HashSet as HashSet import qualified Data.Map.Strict as M import Data.Maybe @@ -56,8 +85,6 @@ import qualified Data.Text.Encoding as T import Database.SQLite3.Direct as SQ3 -import GHC.Stack - import Prelude hiding (concat, log) -- pact @@ -67,7 +94,7 @@ import Pact.PersistPactDb hiding (db) import Pact.Types.Persistence import Pact.Types.RowData import Pact.Types.SQLite -import Pact.Types.Term (ModuleName(..), ObjectMap(..), TableName(..)) +import Pact.Types.Term (ModuleName(..), ObjectMap(..), TableName(..), KeySetName(..), NamespaceName(..), PactId(..)) import Pact.Types.Util (AsString(..)) import qualified Pact.JSON.Encode as J @@ -76,21 +103,151 @@ import qualified Pact.JSON.Legacy.HashMap as LHM -- chainweb import Chainweb.BlockHash -import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Pact.Backend.DbCache -import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils -import Chainweb.Pact.Service.Types -import Chainweb.Pact.Types (logDebug_, logError_) +import Chainweb.Pact.Types import Chainweb.Utils -import Chainweb.Utils.Serialization +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 + +domainTableName :: Domain k v -> SQ3.Utf8 +domainTableName = asStringUtf8 + +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 -tbl :: HasCallStack => Utf8 -> Utf8 -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 <> "]" +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 + , _bsPendingTx :: !(Maybe SQLitePendingData) + , _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 + , _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) @@ -131,7 +288,7 @@ forModuleNameFix f = view blockHandlerModuleNameFix >>= f tableExistsInDbAtHeight :: Utf8 -> BlockHeight -> BlockHandler logger Bool tableExistsInDbAtHeight tableName bh = do let knownTbls = - ["SYS:Pacts", "SYS:Modules", "SYS:KeySets", "SYS:Namespaces"] + ["SYS:Pacts", "SYS:Modules", "SYS:KeySets", "SYS:Namespaces", "SYS:ModuleSources"] if tableName `elem` knownTbls then return True else callDb "tableExists" $ \db -> do @@ -213,7 +370,7 @@ doReadRow mlim d k = forModuleNameFix $ \mnFix -> checkModuleCache u b = MaybeT $ do !txid <- use bsTxId -- cache priority mc <- use bsModuleCache - (r, mc') <- liftIO $ checkDbCache u b txid mc + (r, mc') <- liftIO $ checkDbCache u decodeStrict b txid mc modify' (bsModuleCache .~ mc') return r @@ -275,12 +432,6 @@ recordPendingUpdate (Utf8 key) (Utf8 tn) txid v = modifyPendingData modf (HashMap.singleton key (NE.singleton delta))) -markTableMutation :: Utf8 -> BlockHeight -> Database -> IO () -markTableMutation tablename blockheight db = do - exec' db mutq [SText tablename, SInt (fromIntegral blockheight)] - where - mutq = "INSERT OR IGNORE INTO VersionedTableMutation VALUES (?,?);" - checkInsertIsOK :: Maybe (BlockHeight, TxId) -- ^ the highest block we should be reading writes from @@ -524,6 +675,10 @@ doCommit = use bsMode >>= \case 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 @@ -552,7 +707,7 @@ doBegin m = do logger <- view blockHandlerLogger use bsMode >>= \case Just {} -> do - logError_ logger "beginTx: In transaction, rolling back" + logError_ logger "PactDb.beginTx: In transaction, rolling back" doRollback Nothing -> return () resetTemp @@ -601,14 +756,14 @@ doGetTxLog d txid = do , _deltaTxId writeForSomeKey == txid ] return latestWriteForSomeKey - mapM (\x -> toTxLog d (Utf8 $ _deltaRowKey x) (_deltaData x)) deltas + 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 d key value + [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) @@ -616,279 +771,18 @@ doGetTxLog d txid = do toTxLog :: MonadThrow m => - Domain k v -> Utf8 -> BS.ByteString -> m (TxLog RowData) + 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" + case Data.Aeson.decodeStrict' value of + Nothing -> internalError $ "toTxLog: Unexpected value, unable to deserialize log: " <> T.decodeUtf8 value Just v -> - return $! TxLog (asString d) (fromUtf8 key) 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 -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);" - --- | Delete any state from the database newer than the input parent header. --- Returns the ending txid of the input parent header. -rewindDbTo - :: SQLiteEnv - -> Maybe ParentHeader - -> IO TxId -rewindDbTo db Nothing = do - rewindDbToGenesis db - return 0 -rewindDbTo db mh@(Just (ParentHeader ph)) = do - !historicalEndingTxId <- getEndTxId "rewindDbToBlock" db mh - endingTxId <- case historicalEndingTxId of - NoHistory -> - throwM - $ BlockHeaderLookupFailure - $ "rewindDbTo.getEndTxId: not in db: " - <> sshow ph - Historical endingTxId -> - return endingTxId - rewindDbToBlock db (view blockHeight ph) endingTxId - return endingTxId - --- rewind before genesis, delete all user tables and all rows in all tables -rewindDbToGenesis - :: SQLiteEnv - -> IO () -rewindDbToGenesis db = 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];" - tblNames <- qry_ db "SELECT tablename FROM VersionedTableCreation;" [RText] - forM_ tblNames $ \t -> case t of - [SText tn] -> exec_ db ("DROP TABLE [" <> tn <> "];") - _ -> internalError "Something went wrong when resetting tables." - 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 - -> BlockHeight - -> TxId - -> IO () -rewindDbToBlock db bh endingTxId = do - tableMaintenanceRowsVersionedSystemTables - droppedtbls <- dropTablesAtRewind - vacuumTablesAtRewind droppedtbls - deleteHistory - clearTxIndex - where - dropTablesAtRewind :: IO (HashSet BS.ByteString) - dropTablesAtRewind = do - toDropTblNames <- qry db findTablesToDropStmt - [SInt (fromIntegral bh)] [RText] - tbls <- fmap HashSet.fromList . forM toDropTblNames $ \case - [SText tblname@(Utf8 tn)] -> do - exec_ db $ "DROP TABLE IF EXISTS " <> tbl tblname - return tn - _ -> internalError rewindmsg - exec' db - "DELETE FROM VersionedTableCreation WHERE createBlockheight > ?" - [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 = - exec' db "DELETE FROM BlockHistory WHERE blockheight > ?" - [SInt (fromIntegral bh)] - - vacuumTablesAtRewind :: HashSet BS.ByteString -> IO () - vacuumTablesAtRewind droppedtbls = do - let processMutatedTables ms = fmap HashSet.fromList . forM ms $ \case - [SText (Utf8 tn)] -> return tn - _ -> internalError "rewindBlock: vacuumTablesAtRewind: Couldn't resolve the name \ - \of the table to possibly vacuum." - mutatedTables <- qry db - "SELECT DISTINCT tablename FROM VersionedTableMutation WHERE blockheight > ?;" - [SInt (fromIntegral bh)] - [RText] - >>= processMutatedTables - let toVacuumTblNames = HashSet.difference mutatedTables droppedtbls - forM_ toVacuumTblNames $ \tblname -> - exec' db ("DELETE FROM " <> tbl (Utf8 tblname) <> " WHERE txid >= ?") - [SInt $! fromIntegral endingTxId] - exec' db "DELETE FROM VersionedTableMutation WHERE blockheight > ?;" - [SInt (fromIntegral bh)] - - tableMaintenanceRowsVersionedSystemTables :: IO () - tableMaintenanceRowsVersionedSystemTables = do - 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 - where - tx = [SInt $! fromIntegral endingTxId] - - -- | Delete all future transactions from the index - clearTxIndex :: IO () - clearTxIndex = - exec' db "DELETE FROM TransactionIndex WHERE blockheight > ?;" - [ SInt (fromIntegral bh) ] - -commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockHeight -> BlockState -> IO () -commitBlockStateToDatabase db hsh bh blockState = do - let newTables = _pendingTableCreation $ _bsPendingBlock blockState - mapM_ (\tn -> createUserTable (Utf8 tn)) newTables - let writeV = toChunks $ _pendingWrites (_bsPendingBlock blockState) - backendWriteUpdateBatch writeV - indexPendingPactTransactions - let nextTxId = _bsTxId blockState - blockHistoryInsert nextTxId - where - toChunks writes = - over _2 (concatMap toList . HashMap.elems) . - over _1 Utf8 <$> 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 db - where - q = "INSERT OR REPLACE INTO " <> tbl tableName <> "(rowkey,txid,rowdata) 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 :: Utf8 -> IO () - createUserTable tablename = do - createVersionedTable tablename db - 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 $ _bsPendingBlock blockState - 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 - - --- | Create all tables that exist pre-genesis -initSchema :: (Logger logger) => logger -> SQLiteEnv -> IO () -initSchema logger sql = - withSavepoint sql DbTransaction $ do - createBlockHistoryTable - createTableCreationTable - createTableMutationTable - createTransactionIndexTable - create (domainTableName KeySets) - create (domainTableName Modules) - create (domainTableName Namespaces) - create (domainTableName Pacts) - where - create tablename = do - logDebug_ logger $ "initSchema: " <> fromUtf8 tablename - createVersionedTable tablename sql - - createBlockHistoryTable :: 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));" - - createTableCreationTable :: 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));" - - createTableMutationTable :: 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));" - - createTransactionIndexTable :: 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)"; - -getEndTxId :: Text -> SQLiteEnv -> Maybe ParentHeader -> IO (Historical TxId) -getEndTxId msg sql pc = case pc of - Nothing -> return (Historical 0) - Just (ParentHeader ph) -> getEndTxId' msg sql (view blockHeight ph) (view blockHash ph) - -getEndTxId' :: Text -> SQLiteEnv -> BlockHeight -> BlockHash -> IO (Historical TxId) -getEndTxId' msg sql bh bhsh = do - r <- qry sql - "SELECT endingtxid FROM BlockHistory WHERE blockheight = ? and hash = ?;" - [ SInt $ fromIntegral bh - , SBlob $ runPutS (encodeBlockHash bhsh) - ] - [RInt] - case r of - [[SInt tid]] -> return $ Historical (TxId (fromIntegral tid)) - [] -> return NoHistory - _ -> internalError $ msg <> ".getEndTxId: expected single-row int result, got " <> sshow r -- | Careful doing this! It's expensive and for our use case, probably pointless. -- We should reserve vacuuming for an offline process 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/Pact/NoCoinbase.hs b/src/Chainweb/Pact4/NoCoinbase.hs similarity index 90% rename from src/Chainweb/Pact/NoCoinbase.hs rename to src/Chainweb/Pact4/NoCoinbase.hs index 790bfbed6f..5994f6335a 100644 --- a/src/Chainweb/Pact/NoCoinbase.hs +++ b/src/Chainweb/Pact4/NoCoinbase.hs @@ -2,7 +2,7 @@ {-# LANGUAGE ScopedTypeVariables #-} -- | --- Module: Chainweb.Pact.NoCoinbase +-- Module: Chainweb.Pact4.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.Pact.NoCoinbase +module Chainweb.Pact4.NoCoinbase ( noCoinbase ) where diff --git a/src/Chainweb/Pact/SPV.hs b/src/Chainweb/Pact4/SPV.hs similarity index 74% rename from src/Chainweb/Pact/SPV.hs rename to src/Chainweb/Pact4/SPV.hs index 9fe921d4c4..c0a2ddad3b 100644 --- a/src/Chainweb/Pact/SPV.hs +++ b/src/Chainweb/Pact4/SPV.hs @@ -10,7 +10,7 @@ {-# LANGUAGE ViewPatterns #-} -- | --- Module: Chainweb.Pact.PactService +-- Module: Chainweb.Pact4.SPV -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: See LICENSE file -- Maintainers: Emily Pillmore @@ -18,7 +18,7 @@ -- -- Pact Service SPV support -- -module Chainweb.Pact.SPV +module Chainweb.Pact4.SPV ( -- * spv support pactSPV , verifySPV @@ -64,7 +64,7 @@ import qualified Streaming.Prelude as S import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.BlockHeight -import Chainweb.Pact.Service.Types(internalError) +import Chainweb.Pact.Types(internalError) import Chainweb.Pact.Utils (aeson) import Chainweb.Payload import Chainweb.Payload.PayloadStore @@ -78,15 +78,16 @@ import qualified Chainweb.Version.Guards as CW -- internal pact modules import qualified Pact.JSON.Encode as J -import Pact.Types.Command -import Pact.Types.Hash -import Pact.Types.PactValue -import Pact.Types.Runtime -import Pact.Types.SPV +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) (view blockChainId bh) (view blockHeight 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 @@ -94,7 +95,7 @@ catchAndDisplaySPVError bh = forkedThrower :: BlockHeader -> Text -> ExceptT Text IO a forkedThrower bh = - if CW.chainweb219Pact (CW._chainwebVersion bh) (view blockChainId bh) (view blockHeight bh) + if CW.chainweb219Pact (CW._chainwebVersion bh) (CW._chainId bh) (view blockHeight bh) then throwError else internalError @@ -105,8 +106,8 @@ pactSPV -- ^ handle into the cutdb -> BlockHeader -- ^ the context for verifying the proof - -> SPVSupport -pactSPV bdb bh = SPVSupport (verifySPV bdb bh) (verifyCont bdb bh) + -> 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 @@ -120,9 +121,9 @@ verifySPV -> Text -- ^ TXOUT or TXIN - defines the type of proof -- used in validation - -> Object Name + -> Pact4.Object Pact4.Name -- ^ the proof object to validate - -> IO (Either Text (Object Name)) + -> IO (Either Text (Pact4.Object Pact4.Name)) verifySPV bdb bh typ proof = runExceptT $ go typ proof where cid = CW._chainId bdb @@ -131,8 +132,8 @@ verifySPV bdb bh typ proof = runExceptT $ go typ proof mkSPVResult' cr j | enableBridge = return $ mkSPVResult cr j - | otherwise = case fromPactValue j of - TObject o _ -> return o + | 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 @@ -158,16 +159,16 @@ 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 $ liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) + TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) - q <- case decodeStrict' p :: Maybe (CommandResult Hash) of + q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of Nothing -> forkedThrower bh "unable to decode spv transaction output" Just cr -> return cr - case _crResult q of - PactResult Left{} -> + case Pact4._crResult q of + Pact4.PactResult Left{} -> throwError "Failed command result in tx output proof" - PactResult (Right v) -> + Pact4.PactResult (Right v) -> mkSPVResult' q v t -> throwError $! "unsupported SPV types: " <> t @@ -253,14 +254,14 @@ verifyCont -- ^ handle into the cut db -> BlockHeader -- ^ the context for verifying the proof - -> ContProof + -> Pact4.ContProof -- ^ bytestring of 'TransactionOutputP roof' object to validate - -> IO (Either Text PactExec) -verifyCont bdb bh (ContProof cp) = runExceptT $ do + -> IO (Either Text Pact4.PactExec) +verifyCont bdb bh (Pact4.ContProof cp) = runExceptT $ do let errorMessageType = if CW.chainweb221Pact (CW._chainwebVersion bh) - (view blockChainId bh) + (CW._chainId bh) (view blockHeight bh) then Simplified else Legacy @@ -276,18 +277,18 @@ verifyCont bdb bh (ContProof cp) = runExceptT $ do -- -- 1. verify spv tx output proof via chainweb spv api -- - -- 2. Decode tx outputs to 'HashCommandResult' + -- 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 $ liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) + TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) - q <- case decodeStrict' p :: Maybe (CommandResult Hash) of + q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of Nothing -> forkedThrower bh "unable to decode spv transaction output" Just cr -> return cr - case _crContinuation q of + case Pact4._crContinuation q of Nothing -> throwError "no pact exec found in command result" Just pe -> return pe where @@ -295,14 +296,14 @@ verifyCont bdb bh (ContProof cp) = runExceptT $ do -- | Extract a 'TransactionOutputProof' from a generic pact object -- -extractProof :: Bool -> Object Name -> Either Text (TransactionOutputProof SHA512t_256) -extractProof False o = toPactValue (TObject o noInfo) >>= k +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 (Object (ObjectMap o) _ _ _) = case M.lookup "proof" o of - Just (TLitString proof) -> do +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" @@ -318,10 +319,10 @@ extractProof True (Object (ObjectMap o) _ _ _) = case M.lookup "proof" o of -- -- For details of the returned value see 'Ethereum.Receipt' -- -extractEthProof :: Object Name -> Either Text ReceiptProof -extractEthProof o = case M.lookup "proof" $ _objectMap $ _oObject o of +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 (TLitString p) -> do + 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" @@ -330,7 +331,7 @@ extractEthProof o = case M.lookup "proof" $ _objectMap $ _oObject o of where errMsg t = first (const t) -ethResultToPactValue :: ReceiptProofValidation -> Object Name +ethResultToPactValue :: ReceiptProofValidation -> Pact4.Object Pact4.Name ethResultToPactValue ReceiptProofValidation{..} = mkObject [ ("depth", tInt _receiptProofValidationDepth) , ("header", header _receiptProofValidationHeader) @@ -342,11 +343,11 @@ ethResultToPactValue ReceiptProofValidation{..} = mkObject where receipt Receipt{..} = obj [ ("cumulative-gas-used", tInt _receiptGasUsed) - , ("status",toTerm $ _receiptStatus == TxStatus 1) - , ("logs",toTList TyAny noInfo $ map rlog _receiptLogs)] + , ("status",Pact4.toTerm $ _receiptStatus == TxStatus 1) + , ("logs",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map rlog _receiptLogs)] rlog LogEntry{..} = obj [ ("address",jsonStr _logEntryAddress) - , ("topics",toTList TyAny noInfo $ map topic _logEntryTopics) + , ("topics",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map topic _logEntryTopics) , ("data",jsonStr _logEntryData)] topic t = jsonStr t header ch@EthHeader.ConsensusHeader{..} = obj @@ -367,8 +368,8 @@ ethResultToPactValue ReceiptProofValidation{..} = mkObject , ("transactions-root", jsonStr _hdrTransactionsRoot) ] jsonStr v = case toJSON v of - String s -> tStr s - _ -> tStr $ sshow v + String s -> Pact4.tStr s + _ -> Pact4.tStr $ sshow v ts (Timestamp t) = tInt t tix (TransactionIndex i) = tInt i {-# INLINE ethResultToPactValue #-} @@ -384,7 +385,7 @@ getTxIdx => BlockHeaderDb -> PayloadDb tbl -> BlockHeight - -> PactHash + -> Pact4.PactHash -> IO (Either Text Int) getTxIdx bdb pdb bh th = do -- get BlockPayloadHash @@ -409,11 +410,11 @@ getTxIdx bdb pdb bh th = do & fmap int & return where - toPactTx :: MonadThrow m => Transaction -> m (Command Text) + toPactTx :: MonadThrow m => Transaction -> m (Pact4.Command Text) toPactTx (Transaction b) = decodeStrictOrThrow' b - toTxHash :: MonadThrow m => Transaction -> m PactHash - toTxHash = fmap _cmdHash . toPactTx + 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) @@ -421,68 +422,68 @@ getTxIdx bdb pdb bh th = do 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 :: [(FieldKey, Term n)] -> Object n -mkObject ps = Object (ObjectMap (M.fromList ps)) TyAny Nothing noInfo +mkObject :: [(Pact4.FieldKey, Pact4.Term n)] -> Pact4.Object n +mkObject ps = Pact4.Object (Pact4.ObjectMap (M.fromList ps)) Pact4.TyAny Nothing Pact4.noInfo -obj :: [(FieldKey, Term n)] -> Term n -obj = toTObject TyAny noInfo +obj :: [(Pact4.FieldKey, Pact4.Term n)] -> Pact4.Term n +obj = Pact4.toTObject Pact4.TyAny Pact4.noInfo -tInt :: Integral i => i -> Term Name -tInt = toTerm . fromIntegral @_ @Integer +tInt :: Integral i => i -> Pact4.Term Pact4.Name +tInt = Pact4.toTerm . fromIntegral @_ @Integer -- | Encode a "successful" CommandResult into a Pact object. mkSPVResult - :: CommandResult Hash + :: Pact4.CommandResult Pact4.Hash -- ^ Full CR - -> PactValue + -> Pact4.PactValue -- ^ Success result - -> Object Name -mkSPVResult CommandResult{..} j = + -> Pact4.Object Pact4.Name +mkSPVResult Pact4.CommandResult{..} j = mkObject - [ ("result", fromPactValue j) - , ("req-key", tStr $ asString $ unRequestKey _crReqKey) - , ("txid", tStr $ maybe "" asString _crTxId) - , ("gas", toTerm $ (fromIntegral _crGas :: Integer)) + [ ("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", tStr $ asString _crLogs) + , ("logs", Pact4.tStr $ Pact4.asString _crLogs) , ("continuation", maybe empty contField _crContinuation) - , ("events", toTList TyAny noInfo $ map eventField _crEvents) + , ("events", Pact4.toTList Pact4.TyAny Pact4.noInfo $ map eventField _crEvents) ] where metaField v = case fromJSON v of Error _ -> obj [] - Success p -> fromPactValue p + Success p -> Pact4.fromPactValue p - contField (PactExec stepCount yield executed step pactId pactCont rollback _nested) = obj - [ ("step", toTerm step) - , ("step-count", toTerm stepCount) + 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", toTerm pactId) + , ("pact-id", Pact4.toTerm pactId) , ("cont",contField1 pactCont) - , ("step-has-rollback",toTerm rollback) - , ("executed",tStr $ maybe "" sshow executed) + , ("step-has-rollback",Pact4.toTerm rollback) + , ("executed",Pact4.tStr $ maybe "" sshow executed) ] - contField1 PactContinuation {..} = obj - [ ("name",tStr $ asString _pcDef) - , ("args",toTList TyAny noInfo $ map fromPactValue _pcArgs) + contField1 Pact4.PactContinuation {..} = obj + [ ("name",Pact4.tStr $ Pact4.asString _pcDef) + , ("args",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map Pact4.fromPactValue _pcArgs) ] - yieldField Yield {..} = obj - [ ("data",fromPactValue (PObject _yData)) + yieldField Pact4.Yield {..} = obj + [ ("data",Pact4.fromPactValue (Pact4.PObject _yData)) , ("provenance", maybe empty provField _yProvenance) ] - provField Provenance {..} = obj - [ ("target-chain", toTerm $ _chainId _pTargetChainId) - , ("module-hash", tStr $ asString $ _mhHash $ _pModuleHash) + provField Pact4.Provenance {..} = obj + [ ("target-chain", Pact4.toTerm $ Pact4._chainId _pTargetChainId) + , ("module-hash", Pact4.tStr $ Pact4.asString $ Pact4._mhHash $ _pModuleHash) ] - eventField PactEvent {..} = obj - [ ("name", toTerm _eventName) - , ("params", toTList TyAny noInfo (map fromPactValue _eventParams)) - , ("module", tStr $ asString _eventModule) - , ("module-hash", tStr $ asString _eventModuleHash) + 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/Pact/Templates.hs b/src/Chainweb/Pact4/Templates.hs similarity index 98% rename from src/Chainweb/Pact/Templates.hs rename to src/Chainweb/Pact4/Templates.hs index 106ebe2f55..a8f62002bb 100644 --- a/src/Chainweb/Pact/Templates.hs +++ b/src/Chainweb/Pact4/Templates.hs @@ -14,11 +14,12 @@ -- -- Prebuilt Term templates for automated operations (coinbase, gas buy) -- -module Chainweb.Pact.Templates +module Chainweb.Pact4.Templates ( mkFundTxTerm , mkBuyGasTerm , mkRedeemGasTerm , mkCoinbaseTerm + , mkCoinbaseCmd ) where @@ -39,8 +40,7 @@ import Pact.Types.Runtime import Chainweb.Miner.Pact import Chainweb.Pact.Types -import Chainweb.Pact.Service.Types - +import Chainweb.Pact4.Types (GasSupply) inf :: Info inf = Info $ Just (Code "",Parsed (Columns 0 0) 0) @@ -162,7 +162,6 @@ coinbaseTemplate = ) {-# NOINLINE coinbaseTemplate #-} - mkCoinbaseTerm :: MinerId -> MinerKeys -> ParsedDecimal -> (Term Name,ExecMsg ParsedCode) mkCoinbaseTerm (MinerId mid) (MinerKeys ks) reward = (populatedTerm, execMsg) where diff --git a/src/Chainweb/Transaction.hs b/src/Chainweb/Pact4/Transaction.hs similarity index 59% rename from src/Chainweb/Transaction.hs rename to src/Chainweb/Pact4/Transaction.hs index a84e350c44..eb74a6f31b 100644 --- a/src/Chainweb/Transaction.hs +++ b/src/Chainweb/Pact4/Transaction.hs @@ -6,14 +6,19 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE DeriveTraversable #-} -module Chainweb.Transaction - ( ChainwebTransaction +module Chainweb.Pact4.Transaction + ( Transaction + , UnparsedTransaction + , unparseTransaction , HashableTrans(..) , PayloadWithText , PactParserVersion(..) , IsWebAuthnPrefixLegal(..) - , chainwebPayloadCodec + , payloadCodec + , rawCommandCodec , encodePayload , decodePayload , cmdGasLimit @@ -22,6 +27,7 @@ module Chainweb.Transaction , cmdCreationTime , mkPayloadWithText , mkPayloadWithTextOld + , mkPayloadWithTextOldUnparsed , payloadBytes , payloadObj , parsePact @@ -52,50 +58,65 @@ import Chainweb.Utils import Chainweb.Utils.Serialization -- | A product type representing a `Payload PublicMeta ParsedCode` coupled with --- the Text that generated it, to make gossiping easier. +-- the text that it was parsed from, to make gossiping easier. -- -data PayloadWithText = PayloadWithText +data PayloadWithText meta code = PayloadWithText { _payloadBytes :: !SB.ShortByteString - , _payloadObj :: !(Payload PublicMeta ParsedCode) + , _payloadObj :: !(Payload meta code) } - deriving (Show, Eq, Generic) + deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic) deriving anyclass (NFData) -payloadBytes :: PayloadWithText -> SB.ShortByteString +payloadBytes :: PayloadWithText meta code -> SB.ShortByteString payloadBytes = _payloadBytes -payloadObj :: PayloadWithText -> Payload PublicMeta ParsedCode +payloadObj :: PayloadWithText meta code -> Payload meta code payloadObj = _payloadObj -mkPayloadWithText :: Command (ByteString, Payload PublicMeta ParsedCode) -> Command PayloadWithText +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 +mkPayloadWithTextOld :: Payload PublicMeta ParsedCode -> PayloadWithText PublicMeta ParsedCode mkPayloadWithTextOld p = PayloadWithText { _payloadBytes = SB.toShort $ J.encodeStrict $ toLegacyJsonViaEncode $ fmap _pcCode p , _payloadObj = p } -type ChainwebTransaction = Command PayloadWithText +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 ChainwebTransaction +-- | Hashable newtype of Transaction newtype HashableTrans a = HashableTrans { unHashable :: Command a } deriving (Eq, Functor, Ord) -instance Hashable (HashableTrans PayloadWithText) where +instance (Eq code, Eq meta) => Hashable (HashableTrans (PayloadWithText meta code)) where hashWithSalt s (HashableTrans t) = hashWithSalt s hashCode where (TypedHash hc) = _cmdHash t @@ -103,25 +124,39 @@ instance Hashable (HashableTrans PayloadWithText) where !hashCode = either error id $ decHC (B.take 8 $ SB.fromShort hc) {-# INLINE hashWithSalt #-} --- | A codec for (Command PayloadWithText) transactions. +-- | 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. -- -chainwebPayloadCodec +payloadCodec :: PactParserVersion - -> Codec (Command PayloadWithText) -chainwebPayloadCodec ppv = Codec enc dec - where + -> 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" + Just cmd -> traverse (decodePayload ppv . encodeUtf8) cmd + Nothing -> Left "decode PayloadWithText failed" -encodePayload :: PayloadWithText -> ByteString +encodePayload :: PayloadWithText meta code -> ByteString encodePayload = SB.fromShort . _payloadBytes decodePayload - :: PactParserVersion + :: Aeson.FromJSON meta + => PactParserVersion -> ByteString - -> Either String PayloadWithText + -> Either String (PayloadWithText meta ParsedCode) decodePayload ppv bs = case Aeson.decodeStrict' bs of Just payload -> do p <- traverse (parsePact ppv) payload diff --git a/src/Chainweb/Pact/TransactionExec.hs b/src/Chainweb/Pact4/TransactionExec.hs similarity index 86% rename from src/Chainweb/Pact/TransactionExec.hs rename to src/Chainweb/Pact4/TransactionExec.hs index 00ceae4949..d052dbd423 100644 --- a/src/Chainweb/Pact/TransactionExec.hs +++ b/src/Chainweb/Pact4/TransactionExec.hs @@ -9,8 +9,11 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE MonoLocalBinds #-} -- | --- Module : Chainweb.Pact.TransactionExec +-- Module : Chainweb.Pact4.TransactionExec -- Copyright : Copyright © 2018 Kadena LLC. -- License : (see the file LICENSE) -- Maintainer : Mark Nichols , Emily Pillmore @@ -18,38 +21,69 @@ -- -- Pact command execution and coin-contract transaction logic for Chainweb -- -module Chainweb.Pact.TransactionExec -( -- * 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 +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 @@ -112,18 +146,23 @@ import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger import qualified Chainweb.ChainId as Chainweb -import Chainweb.Mempool.Mempool (requestKeyToTransactionHash) +import Chainweb.Mempool.Mempool (pact4RequestKeyToTransactionHash) import Chainweb.Miner.Pact -import Chainweb.Pact.Service.Types -import Chainweb.Pact.Templates -import Chainweb.Pact.Types hiding (logError) -import Chainweb.Transaction -import Chainweb.Utils (encodeToByteString, sshow, tryAllSynchronous, T2(..), T3(..)) +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] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -134,6 +173,104 @@ import Pact.JSON.Encode (toJsonViaEncode) -- -------------------------------------------------------------------------- -- +-- -------------------------------------------------------------------- -- +-- 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. -- @@ -164,6 +301,9 @@ onChainErrorPrintingFor txCtx = -- | 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) @@ -245,7 +385,7 @@ applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx spv cm applyBuyGas = catchesPactError logger (onChainErrorPrintingFor txCtx) (buyGas txCtx cmd miner) >>= \case Left e -> view txRequestKey >>= \rk -> - throwM $ BuyGasFailure $ GasPurchaseFailure (requestKeyToTransactionHash rk) e + throwM $ Pact4BuyGasFailure $ Pact4GasPurchaseFailure (pact4RequestKeyToTransactionHash rk) e Right _ -> checkTooBigTx initialGas gasLimit applyVerifiers redeemAllGas displayPactError e = do @@ -264,10 +404,14 @@ applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx spv cm then do gasUsed <- use txGasUsed let initGasRemaining = fromIntegral gasLimit - gasUsed - verifierResult <- liftIO $ runVerifierPlugins (ctxVersion txCtx, cid, currHeight) logger allVerifiers initGasRemaining cmd + 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: " <> getVerifierError err + let errMsg = "Tx verifier error: " <> _verifierError err cmdResult <- failTxWith (PactError TxFailure noInfo [] (pretty errMsg)) errMsg @@ -409,8 +553,6 @@ applyCoinbase -- ^ Pact logger -> PactDbEnv p -- ^ Pact db environment - -> Miner - -- ^ The miner chosen to mine the block -> ParsedDecimal -- ^ Miner reward -> TxContext @@ -421,11 +563,11 @@ applyCoinbase -- ^ always enable precompilation -> ModuleCache -> IO (T2 (CommandResult [TxLogJson]) (Maybe ModuleCache)) -applyCoinbase v logger dbEnv (Miner mid mks@(MinerKeys mk)) reward@(ParsedDecimal d) txCtx +applyCoinbase v logger dbEnv reward@(ParsedDecimal d) txCtx (EnforceCoinbaseFailure enfCBFailure) (CoinbaseUsePrecompiled enablePC) mc | fork1_3InEffect || enablePC = do when chainweb213Pact' $ enforceKeyFormats - (\k -> throwM $ CoinbaseFailure $ "Invalid miner key: " <> sshow k) + (\k -> throwM $ CoinbaseFailure $ Pact4CoinbaseFailure $ "Invalid miner key: " <> sshow k) (validKeyFormats v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx)) mk let (cterm, cexec) = mkCoinbaseTerm mid mks reward @@ -437,6 +579,7 @@ applyCoinbase v logger dbEnv (Miner mid mks@(MinerKeys mk)) reward@(ParsedDecima 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 @@ -455,7 +598,7 @@ applyCoinbase v logger dbEnv (Miner mid mks@(MinerKeys mk)) reward@(ParsedDecima bh = ctxCurrentBlockHeight txCtx cid = Chainweb._chainId parent chash = Pact.Hash $ SB.toShort $ encodeToByteString $ view blockHash $ _parentHeader parent - -- NOTE: it holds that @ _pdPrevBlockHash pd == encode view blockHash@ + -- 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 @@ -464,7 +607,7 @@ applyCoinbase v logger dbEnv (Miner mid mks@(MinerKeys mk)) reward@(ParsedDecima case cr of Left e - | throwCritical -> throwM $ CoinbaseFailure $ sshow e + | throwCritical -> throwM $ CoinbaseFailure $ Pact4CoinbaseFailure $ sshow e | otherwise -> (`T2` Nothing) <$> failTxWith e "coinbase tx failure" Right er -> do debug @@ -504,7 +647,7 @@ applyLocal -- ^ tx metadata and parent header -> SPVSupport -- ^ SPV support (validates cont proofs) - -> Command PayloadWithText + -> Transaction -- ^ command with payload to execute -> ModuleCache -> ExecutionConfig @@ -535,10 +678,13 @@ applyLocal logger gasLogger dbEnv gasModel txCtx spv cmdIn mc execConfig = applyVerifiers m = do let initGasRemaining = fromIntegral gasLimit - gas0 - verifierResult <- liftIO $ runVerifierPlugins (v, cid, currHeight) logger allVerifiers initGasRemaining cmd + verifierResult <- + liftIO $ runVerifierPlugins + (v, cid, currHeight) logger allVerifiers initGasRemaining + (fromMaybe [] $ cmd ^. cmdPayload . pVerifiers) case verifierResult of Left err -> do - let errMsg = "Tx verifier error: " <> getVerifierError err + let errMsg = "Tx verifier error: " <> _verifierError err failTxWith (PactError TxFailure noInfo [] (pretty errMsg)) errMsg @@ -565,8 +711,8 @@ readInitModules => PactBlockM logger tbl ModuleCache readInitModules = do logger <- view (psServiceEnv . psLogger) - dbEnv <- _cpPactDbEnv <$> view psBlockDbEnv - txCtx <- getTxContext noPublicMeta + dbEnv <- view (psBlockDbEnv . to _cpPactDbEnv) + txCtx <- getTxContext noMiner noPublicMeta -- guarding chainweb 2.17 here to allow for -- cache purging everything but coin and its @@ -583,11 +729,12 @@ readInitModules = do nid = Nothing chash = pactInitialHash tenv = TransactionEnv Local dbEnv logger Nothing (ctxToPublicData txCtx) noSPVSupport nid 0.0 - rk 0 emptyExecutionConfig Nothing Nothing + rk 0 emptyExecutionConfig Nothing Nothing txst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty interp = defaultInterpreter die msg = internalError $ "readInitModules: " <> msg - mkCmd = buildExecParsedCode (pactParserVersion v cid h) Nothing + 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 @@ -639,9 +786,10 @@ readInitModules = do void $ run "load modules" coinDepCmd use txCache - if | chainweb224Pact' -> pure mempty - | chainweb217Pact' -> liftIO $ evalTransactionM tenv txst goCw217 - | otherwise -> liftIO $ evalTransactionM tenv txst go + 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. @@ -653,16 +801,19 @@ readInitModules = do -- which both hit the database. -- applyUpgrades - :: (Logger logger) + :: forall logger p + . (Logger logger) => ChainwebVersion -> Chainweb.ChainId -> BlockHeight -> TransactionM logger p (Maybe ModuleCache) applyUpgrades v cid height - | Just upg <- - v ^? versionUpgrades . onChain cid . at height . _Just = applyUpgrade upg - | cleanModuleCache v cid height = filterModuleCache - | otherwise = return Nothing + | 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) @@ -670,9 +821,10 @@ applyUpgrades v cid height mc <- use txCache pure $ Just $ filterModuleCacheByKey (== "coin") mc - applyUpgrade upg = do + applyUpgrade :: [Transaction] -> Bool -> TransactionM logger p (Maybe ModuleCache) + applyUpgrade upg isPrecocious = do infoLog "Applying upgrade!" - let payloads = map (fmap payloadObj) $ _upgradeTransactions upg + let payloads = map (fmap payloadObj) upg -- -- In order to prime the module cache with all new modules for subsequent @@ -681,7 +833,7 @@ applyUpgrades v cid height -- init cache in the pact service state (_psInitCache). -- - let flags = flagsFor v cid (if _legacyUpgradeIsPrecocious upg then height + 1 else height) + let flags = flagsFor v cid (if isPrecocious then height + 1 else height) caches <- local (txExecutionConfig .~ ExecutionConfig flags) (mapM applyTx payloads) @@ -712,7 +864,7 @@ failTxWith err msg = do l <- view txLogger liftIO $ logFunction l L.Debug - (TxFailureLog rk err msg) + (Pact4TxFailureLog rk err msg) liftIO . traverse_ inc =<< view txTxFailuresCounter @@ -1175,7 +1327,7 @@ gasInterpreter g = do -- | Initial gas charged for transaction size -- ignoring the size of a continuation proof, if present -- -initialGasOf :: PayloadWithText -> Gas +initialGasOf :: PayloadWithText PublicMeta ParsedCode -> Gas initialGasOf payload = gasFee where feePerByte :: Rational = 0.01 @@ -1234,10 +1386,11 @@ mkEvalEnv nsp msg = do <*> view txGasPrice <*> use txGasModel fmap (set eeSigCapBypass txCapBypass) - $ liftIO $ setupEvalEnv (_txDbEnv tenv) Nothing (_txMode tenv) - msg (versionedNativesRefStore (_txExecutionConfig tenv)) genv - nsp (_txSpvSupport tenv) (_txPublicData tenv) (_txExecutionConfig tenv) + $ 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)) @@ -1339,13 +1492,13 @@ buildExecParsedCode ppv value code = maybe (go Null) go value -- | Retrieve public metadata from a command -- -publicMetaOf :: Command (Payload PublicMeta ParsedCode) -> PublicMeta +publicMetaOf :: Command (Payload PublicMeta code) -> PublicMeta publicMetaOf = _pMeta . _cmdPayload {-# INLINE publicMetaOf #-} -- | Retrieve the optional Network identifier from a command -- -networkIdOf :: Command (Payload PublicMeta ParsedCode) -> Maybe NetworkId +networkIdOf :: Command (Payload PublicMeta code) -> Maybe NetworkId networkIdOf = _pNetworkId . _cmdPayload {-# INLINE networkIdOf #-} diff --git a/src/Chainweb/Pact4/Types.hs b/src/Chainweb/Pact4/Types.hs new file mode 100644 index 0000000000..0f49187bb3 --- /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 " <> sshow e) + 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/Pact/Validations.hs b/src/Chainweb/Pact4/Validations.hs similarity index 96% rename from src/Chainweb/Pact/Validations.hs rename to src/Chainweb/Pact4/Validations.hs index d66978cace..b6c00ad703 100644 --- a/src/Chainweb/Pact/Validations.hs +++ b/src/Chainweb/Pact4/Validations.hs @@ -5,7 +5,7 @@ {-# LANGUAGE TypeApplications #-} -- | --- Module: Chainweb.Pact.Validations +-- 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 @@ -16,7 +16,7 @@ -- - The codepath for adding transactions to the mempool -- - The codepath for letting users test their transaction via /local -- -module Chainweb.Pact.Validations +module Chainweb.Pact4.Validations ( -- * Local metadata _validation assertLocalMetadata -- * Validation checks @@ -56,13 +56,12 @@ import Data.Word (Word8) -- internal modules -import Chainweb.BlockHeader (ParentCreationTime(..), ParentHeader(..), blockCreationTime) +import Chainweb.BlockHeader import Chainweb.BlockCreationTime (BlockCreationTime(..)) import Chainweb.Pact.Types import Chainweb.Pact.Utils (fromPactChainId) -import Chainweb.Pact.Service.Types import Chainweb.Time (Seconds(..), Time(..), secondsToTimeSpan, scaleTimeSpan, second, add) -import Chainweb.Transaction (cmdTimeToLive, cmdCreationTime, PayloadWithText, payloadBytes, payloadObj, IsWebAuthnPrefixLegal(..)) +import Chainweb.Pact4.Transaction import Chainweb.Version import Chainweb.Version.Guards (isWebAuthnPrefixLegal, validPPKSchemes) @@ -73,6 +72,7 @@ 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 -- | Check whether a local Api request has valid metadata @@ -98,6 +98,7 @@ assertLocalMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do 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 @@ -277,7 +278,7 @@ displayAssertCommandError = \case -- | Assert that the command hash matches its payload and -- its signatures are valid, without parsing the payload. -assertCommand :: P.Command PayloadWithText -> [P.PPKScheme] -> IsWebAuthnPrefixLegal -> Either AssertCommandError () +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 diff --git a/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs new file mode 100644 index 0000000000..b186e9b9d4 --- /dev/null +++ b/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs @@ -0,0 +1,749 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE BlockArguments #-} + +-- TODO pact5: fix the orphan PactDbFor instance +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | The database operations that manipulate and read the Pact state. + +-- Note: [Pact Transactions] + +-- What is a "pact transaction"? There are three levels of transactions going on: +-- +-------------------------------------------------------------------------------------------------------------------------+ +-- | Block | +-- | | +-- | +-----------------+ +---------------------------------------------+ +-----------------------------------------------+ | +-- | | Apply Coinbase | | Apply Command | | Apply Command | | +-- | | | | |+------------+ +------------+ +------------+ | |+------------+ +------------+ +------------+ | | +-- | | | | || Buy | | Run | | Redeem | | || Buy | | Run | | Redeem | | | +-- | | v | || Gas | | Payload | | Gas | | || Gas | | Payload | | Gas | | | +-- | | Pact tx | || | | | | | | | | | || | | | | | | | | | | +-- | | (begin-tx) | || v | | v | | v | +-->|| v | | v | | v | | | +-- | +v----------------+ || Pact tx | | Pact tx | | Pact tx | | || Pact tx | | Pact tx | | Pact tx | | | +-- | Pact5Db tx || (begin-tx) | | (begin-tx) | | (begin-tx) | | || (begin-tx) | | (begin-tx) | | (begin-tx) | | | +-- | || | | | | | | || | | | | | | | +-- | |+------------+ +------------+ +------------+ | |+------------+ +------------+ +------------+ | | +-- | | | | | | +-- | +v--------------------------------------------+ +v----------------------------------------------+ | +-- | Pact5Db tx Pact5Db tx | +-- +v------------------------------------------------------------------------------------------------------------------------+ +-- SQLite tx (withSavepoint) +-- (in some cases multiple blocks in tx) +-- +-- +-- Transactions must be nested in this way. +-- +-- SQLite transaction ensures that the Pact5Db transaction +-- sees a consistent view of the database, especially if its +-- writes are committed later. +-- +-- Pact5Db 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 + ( chainwebPactBlockDb + , Pact5Db(..) + , BlockHandlerEnv(..) + , blockHandlerDb + , blockHandlerLogger + , toTxLog + , toPactTxLog + , domainTableName + , convRowKey + ) where + +import Data.Coerce +import Control.Applicative +import Control.Exception.Safe +import Control.Lens +import Control.Monad +import Control.Monad.Except +import Control.Monad.Morph +import Control.Monad.Reader +import Control.Monad.State.Strict +import Control.Monad.Trans.Maybe +import Control.Concurrent.MVar + +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.Text as T +import qualified Data.Text.Encoding as T +-- import Data.Default +import qualified Database.SQLite3.Direct as SQ3 + +import GHC.Stack + +import Prelude hiding (concat, log) + +-- pact + +import qualified Pact.JSON.Legacy.HashMap as LHM +import qualified Pact.Types.Persistence as Pact4 +import Pact.Types.SQLite hiding (liftEither) + + +import qualified Pact.Core.Evaluate as Pact +import qualified Pact.Core.Guards as Pact +import qualified Pact.Core.Names as Pact +import qualified Pact.Core.Persistence as Pact +import qualified Pact.Core.Serialise as Pact +import qualified Pact.Core.Builtin as Pact +import qualified Pact.Core.Errors as Pact +import qualified Pact.Core.Gas as Pact + +-- chainweb + +import Chainweb.BlockHeight +import Chainweb.Logger + +import Chainweb.Pact.Backend.Utils +import Chainweb.Pact.Backend.Types +import Chainweb.Utils (sshow, T2) +import Pact.Core.StableEncoding (encodeStable) +import Data.Text (Text) +import Chainweb.Version +import Chainweb.Version.Guards (enableModuleNameFix, chainweb217Pact, pact42) +import Data.DList (DList) +import Data.ByteString (ByteString) +import Chainweb.BlockHash +import Data.Vector (Vector) +import qualified Data.ByteString.Short as SB +import Data.HashMap.Strict (HashMap) +import Pact.Core.Command.Types (RequestKey (..)) +import Pact.Core.Hash +import qualified Data.HashMap.Strict as HM + +data BlockHandlerEnv logger = BlockHandlerEnv + { _blockHandlerDb :: !SQLiteEnv + , _blockHandlerLogger :: !logger + , _blockHandlerVersion :: !ChainwebVersion + , _blockHandlerBlockHeight :: !BlockHeight + , _blockHandlerChainId :: !ChainId + , _blockHandlerMode :: !Pact.ExecutionMode + , _blockHandlerPersistIntraBlockWrites :: !IntraBlockPersistence + } + +-- | 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 + , _bsPendingTx :: !(Maybe (SQLitePendingData, DList (Pact.TxLog ByteString))) + } + +makeLenses ''BlockState +makeLenses ''BlockHandlerEnv + +getPendingTxOrError :: Text -> BlockHandler logger (SQLitePendingData, DList (Pact.TxLog ByteString)) +getPendingTxOrError msg = do + use bsPendingTx >>= \case + 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 + -> 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. + } + +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 + { runBlockHandler + :: ReaderT (BlockHandlerEnv logger) + (StateT BlockState (Pact.GasM Pact.CoreBuiltin Pact.Info)) a + } deriving newtype + ( Functor + , Applicative + , Monad + , MonadState BlockState + , MonadThrow + , MonadIO + , MonadReader (BlockHandlerEnv logger) + ) + +callDb + :: (MonadThrow m, MonadReader (BlockHandlerEnv logger) m, MonadIO m) + => T.Text + -> (SQ3.Database -> IO b) + -> m b +callDb callerName action = do + c <- asks _blockHandlerDb + res <- liftIO $ tryAny $ action c + case res of + Left err -> internalDbError $ "callDb (" <> callerName <> "): " <> sshow err + Right r -> return r + +domainTableName :: Pact.Domain k v b i -> SQ3.Utf8 +domainTableName = toUtf8 . Pact.renderDomain + +tableName :: Pact.TableName -> SQ3.Utf8 +tableName = toUtf8 . Pact.renderTableName + +convKeySetName :: Pact.KeySetName -> SQ3.Utf8 +convKeySetName = toUtf8 . Pact.renderKeySetName + +convModuleName + :: Bool + -- ^ whether to apply module name fix + -> Pact.ModuleName + -> SQ3.Utf8 +convModuleName False (Pact.ModuleName name _) = toUtf8 name +convModuleName True mn = toUtf8 $ Pact.renderModuleName mn + +convNamespaceName :: Pact.NamespaceName -> SQ3.Utf8 +convNamespaceName (Pact.NamespaceName name) = toUtf8 name + +convRowKey :: Pact.RowKey -> SQ3.Utf8 +convRowKey (Pact.RowKey name) = toUtf8 name + +-- to match legacy keys +convPactId :: Pact.DefPactId -> SQ3.Utf8 +convPactId pid = "PactId \"" <> toUtf8 (Pact.renderDefPactId pid) <> "\"" + +convHashedModuleName :: Pact.HashedModuleName -> SQ3.Utf8 +convHashedModuleName = toUtf8 . Pact.renderHashedModuleName + + +newtype InternalDbException = InternalDbException Text + deriving newtype (Eq) + deriving stock (Show) + deriving anyclass (Exception) + +internalDbError :: MonadThrow m => Text -> m a +internalDbError = throwM . InternalDbException + +liftGas :: Pact.GasM Pact.CoreBuiltin Pact.Info a -> BlockHandler logger a +liftGas g = BlockHandler (lift (lift g)) + +runOnBlockGassed + :: BlockHandlerEnv logger -> MVar BlockState + -> BlockHandler logger a + -> Pact.GasM Pact.CoreBuiltin Pact.Info a +runOnBlockGassed env stateVar act = do + ge <- ask + r <- liftIO $ modifyMVar stateVar $ \s -> do + r <- runExceptT (runReaderT (Pact.runGasM (runStateT (runReaderT (runBlockHandler act) env) s)) ge) + let newState = either (\_ -> s) snd r + return (newState, fmap fst r) + liftEither r + +chainwebPactBlockDb :: (Logger logger) => Maybe (BlockHeight, Pact.TxId) -> BlockHandlerEnv logger -> Pact5Db +chainwebPactBlockDb maybeLimit env = Pact5Db + { doPact5DbTransaction = \blockHandle maybeRequestKey kont -> do + stateVar <- newMVar $ BlockState blockHandle Nothing + let basePactDb = Pact.PactDb + { Pact._pdbPurity = Pact.PImpure + , Pact._pdbRead = \d k -> runOnBlockGassed env stateVar $ doReadRow Nothing d k + , Pact._pdbWrite = \wt d k v -> + runOnBlockGassed env stateVar $ doWriteRow Nothing wt d k v + , Pact._pdbKeys = \d -> + runOnBlockGassed env stateVar $ doKeys Nothing d + , Pact._pdbCreateUserTable = \tn -> + runOnBlockGassed env stateVar $ doCreateUserTable Nothing tn + , Pact._pdbBeginTx = \m -> + runOnBlockGassed env stateVar $ doBegin m + , Pact._pdbCommitTx = + runOnBlockGassed env stateVar doCommit + , Pact._pdbRollbackTx = + runOnBlockGassed env stateVar doRollback + } + let maybeLimitedPactDb = case maybeLimit of + Just (bh, endTxId) -> basePactDb + { Pact._pdbRead = \d k -> runOnBlockGassed env stateVar $ doReadRow (Just (bh, endTxId)) d k + , Pact._pdbWrite = \wt d k v -> do + runOnBlockGassed env stateVar $ doWriteRow (Just (bh, endTxId)) wt d k v + , Pact._pdbKeys = \d -> runOnBlockGassed env stateVar $ doKeys (Just (bh, endTxId)) d + , Pact._pdbCreateUserTable = \tn -> do + runOnBlockGassed env stateVar $ doCreateUserTable (Just bh) tn + } + Nothing -> basePactDb + r <- kont maybeLimitedPactDb + finalState <- readMVar stateVar + -- TODO: this may not be wanted when we allow more unconstrained access + -- to the Pact state - which we may do in the future, to run Pact REPL files + -- with chainweb's Pact state. Perhaps we use ExecutionMode to flag this? + when (isJust (_bsPendingTx finalState)) $ + internalDbError "dangling transaction" + -- Register a successful transaction in the pending data for the block + let registerRequestKey = case maybeRequestKey of + Just requestKey -> HashSet.insert (SB.fromShort $ unHash $ unRequestKey requestKey) + Nothing -> id + let finalHandle = + _bsBlockHandle finalState & blockHandlePending . pendingSuccessfulTxs %~ registerRequestKey + + return (r, finalHandle) + , lookupPactTransactions = + fmap (HM.mapKeys (RequestKey . Hash)) . + doLookupSuccessful (_blockHandlerDb env) (_blockHandlerBlockHeight env) . + fmap (unHash . unRequestKey) + } + +getPendingData :: HasCallStack => Text -> BlockHandler logger [SQLitePendingData] +getPendingData msg = do + BlockHandle _ sql <- use bsBlockHandle + ptx <- view _1 <$> getPendingTxOrError msg + -- lookup in pending transactions first + return $ [ptx, sql] + +forModuleNameFix :: (Bool -> BlockHandler logger a) -> BlockHandler logger a +forModuleNameFix f = do + v <- view blockHandlerVersion + cid <- view blockHandlerChainId + bh <- view blockHandlerBlockHeight + f (enableModuleNameFix v cid bh) + +-- TODO: speed this up, cache it? +tableExistsInDbAtHeight :: SQ3.Utf8 -> 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 tablename] [RText] >>= \case + [] -> return False + _ -> return True + +doReadRow + :: Maybe (BlockHeight, Pact.TxId) + -- ^ the highest block we should be reading writes from + -> Pact.Domain k v Pact.CoreBuiltin Pact.Info + -> k + -> BlockHandler logger (Maybe v) +doReadRow mlim d k = forModuleNameFix $ \mnFix -> + case d of + Pact.DKeySets -> let f = (\v -> (view Pact.document <$> Pact._decodeKeySet Pact.serialisePact_lineinfo v)) in + lookupWithKey (convKeySetName k) f (noCache f) + -- TODO: This is incomplete (the modules case), due to namespace + -- resolution concerns + Pact.DModules -> let f = (\v -> (view Pact.document <$> Pact._decodeModuleData Pact.serialisePact_lineinfo v)) in + lookupWithKey (convModuleName mnFix k) f (noCacheChargeModuleSize f) + Pact.DNamespaces -> let f = (\v -> (view Pact.document <$> Pact._decodeNamespace Pact.serialisePact_lineinfo v)) in + lookupWithKey (convNamespaceName k) f (noCache f) + Pact.DUserTables _ -> let f = (\v -> (view Pact.document <$> Pact._decodeRowData Pact.serialisePact_lineinfo v)) in + lookupWithKey (convRowKey k) f (noCache f) + Pact.DDefPacts -> let f = (\v -> (view Pact.document <$> Pact._decodeDefPactExec Pact.serialisePact_lineinfo v)) in + lookupWithKey (convPactId k) f (noCache f) + Pact.DModuleSource -> let f = (\v -> (view Pact.document <$> Pact._decodeModuleCode Pact.serialisePact_lineinfo v)) in + lookupWithKey (convHashedModuleName k) f (noCache f) + where + tablename@(SQ3.Utf8 tableNameBS) = domainTableName d + + lookupWithKey + :: forall logger v . + SQ3.Utf8 + -> (BS.ByteString -> Maybe v) + -> (SQ3.Utf8 -> BS.ByteString -> MaybeT (BlockHandler logger) v) + -> BlockHandler logger (Maybe v) + lookupWithKey key f checkCache = do + pds <- getPendingData "read" + let lookPD = foldr1 (<|>) $ map (lookupInPendingData key f) pds + let lookDB = lookupInDb key f checkCache + runMaybeT (lookPD <|> lookDB) + + lookupInPendingData + :: forall logger v . + SQ3.Utf8 + -> (BS.ByteString -> Maybe v) + -> SQLitePendingData + -> MaybeT (BlockHandler logger) v + lookupInPendingData (SQ3.Utf8 rowkey) f p = do + -- we get the latest-written value at this rowkey + allKeys <- hoistMaybe $ HashMap.lookup tableNameBS (_pendingWrites p) + ddata <- _deltaData . NE.head <$> hoistMaybe (HashMap.lookup rowkey allKeys) + MaybeT $ return $! f ddata + + lookupInDb + :: forall logger v . + SQ3.Utf8 + -> (BS.ByteString -> Maybe v) + -> (SQ3.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 "read" 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 [] (\(Pact.TxId txid) -> [SInt $ fromIntegral txid]) (snd <$> mlim) + let queryStmt = + "SELECT rowdata FROM " <> tbl 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 -> internalDbError $ + "doReadRow: Expected (at most) a single result, but got: " <> + T.pack (show err) + + noCache + :: (BS.ByteString -> Maybe v) + -> SQ3.Utf8 + -> BS.ByteString + -> MaybeT (BlockHandler logger) v + noCache f _key rowdata = MaybeT $ return $! f rowdata + + noCacheChargeModuleSize + :: (BS.ByteString -> Maybe (Pact.ModuleData Pact.CoreBuiltin Pact.Info)) + -> SQ3.Utf8 + -> BS.ByteString + -> MaybeT (BlockHandler logger) (Pact.ModuleData Pact.CoreBuiltin Pact.Info) + noCacheChargeModuleSize f _key rowdata = do + lift $ BlockHandler $ lift $ lift (Pact.chargeGasM (Pact.GModuleOp (Pact.MOpLoadModule (BS.length rowdata)))) + MaybeT $ return $! f rowdata + + +checkDbTablePendingCreation :: Text -> SQ3.Utf8 -> MaybeT (BlockHandler logger) () +checkDbTablePendingCreation msg (SQ3.Utf8 tablename) = do + pds <- lift (getPendingData msg) + forM_ pds $ \p -> + when (HashSet.member tablename (_pendingTableCreation p)) mzero + +latestTxId :: Lens' BlockState Pact.TxId +latestTxId = bsBlockHandle . blockHandleTxId . coerced + +writeSys + :: Pact.Domain k v Pact.CoreBuiltin Pact.Info + -> k + -> v + -> BlockHandler logger () +writeSys d k v = do + txid <- use latestTxId + (kk, vv) <- forModuleNameFix $ \mnFix -> pure $ case d of + Pact.DKeySets -> (convKeySetName k, Pact._encodeKeySet Pact.serialisePact_lineinfo v) + Pact.DModules -> (convModuleName mnFix k, Pact._encodeModuleData Pact.serialisePact_lineinfo v) + Pact.DNamespaces -> (convNamespaceName k, Pact._encodeNamespace Pact.serialisePact_lineinfo v) + Pact.DDefPacts -> (convPactId k, Pact._encodeDefPactExec Pact.serialisePact_lineinfo v) + Pact.DUserTables _ -> error "impossible" + Pact.DModuleSource -> (convHashedModuleName k, Pact._encodeModuleCode Pact.serialisePact_lineinfo v) + recordPendingUpdate kk (toUtf8 tablename) txid vv + recordTxLog d kk vv + where + tablename = Pact.renderDomain d + +recordPendingUpdate + :: SQ3.Utf8 + -> SQ3.Utf8 + -> Pact.TxId + -> BS.ByteString + -> BlockHandler logger () +recordPendingUpdate (SQ3.Utf8 key) (SQ3.Utf8 tn) txid vs = modifyPendingData "write" modf + where + delta = SQLiteRowDelta tn (coerce txid) key vs + + modf = over pendingWrites upd + upd = HashMap.unionWith + HashMap.union + (HashMap.singleton tn + (HashMap.singleton key (NE.singleton delta))) + +checkInsertIsOK + :: Maybe (BlockHeight, Pact.TxId) + -> Pact.TableName + -- ^ the highest block we should be reading writes from + -> Pact.WriteType + -> Pact.Domain Pact.RowKey Pact.RowData Pact.CoreBuiltin Pact.Info + -> Pact.RowKey + -> BlockHandler logger (Maybe Pact.RowData) +checkInsertIsOK mlim tn wt d k = do + olds <- doReadRow mlim d k + case (olds, wt) of + (Nothing, Pact.Insert) -> return Nothing + (Just _, Pact.Insert) -> liftGas $ Pact.throwDbOpErrorGasM (Pact.RowFoundError tn k) + (Nothing, Pact.Write) -> return Nothing + (Just old, Pact.Write) -> return $ Just old + (Just old, Pact.Update) -> return $ Just old + (Nothing, Pact.Update) -> liftGas $ Pact.throwDbOpErrorGasM (Pact.NoRowFound tn k) + +writeUser + :: Maybe (BlockHeight, Pact.TxId) + -- ^ the highest block we should be reading writes from + -> Pact.WriteType + -> Pact.Domain Pact.RowKey Pact.RowData Pact.CoreBuiltin Pact.Info + -> Pact.RowKey + -> Pact.RowData + -> BlockHandler logger () +writeUser mlim wt d k rowdata@(Pact.RowData row) = do + Pact.TxId txid <- use latestTxId + let (Pact.DUserTables tname) = d + m <- checkInsertIsOK mlim tname wt d k + row' <- case m of + Nothing -> ins txid + Just old -> upd txid old + liftGas (Pact._encodeRowData Pact.serialisePact_lineinfo row') >>= + \encoded -> recordTxLog d (convRowKey k) encoded + where + tn = Pact.renderDomain d + + upd txid (Pact.RowData oldrow) = do + let row' = Pact.RowData (M.union row oldrow) + liftGas (Pact._encodeRowData Pact.serialisePact_lineinfo row') >>= + \encoded -> do + recordPendingUpdate (convRowKey k) (toUtf8 tn) (Pact.TxId txid) encoded + return row' + + ins txid = do + liftGas (Pact._encodeRowData Pact.serialisePact_lineinfo rowdata) >>= + \encoded -> do + recordPendingUpdate (convRowKey k) (toUtf8 tn) (Pact.TxId txid) encoded + return rowdata + +doWriteRow + :: Maybe (BlockHeight, Pact.TxId) + -- ^ the highest block we should be reading writes from + -> Pact.WriteType + -> Pact.Domain k v Pact.CoreBuiltin Pact.Info + -> k + -> v + -> BlockHandler logger () +doWriteRow mlim wt d k v = case d of + (Pact.DUserTables _) -> writeUser mlim wt d k v + _ -> writeSys d k v + +doKeys + :: forall k v logger . + Maybe (BlockHeight, Pact.TxId) + -- ^ the highest block we should be reading writes from + -> Pact.Domain k v Pact.CoreBuiltin Pact.Info + -> BlockHandler logger [k] +doKeys mlim d = do + msort <- asks $ \e -> + if pact42 (_blockHandlerVersion e) (_blockHandlerChainId e) (_blockHandlerBlockHeight e) + then sort + else id + dbKeys <- getDbKeys + pb <- use (bsBlockHandle . blockHandlePending) + (mptx, _) <- getPendingTxOrError "keys" + + let memKeys = fmap (T.decodeUtf8 . _deltaRowKey) + $ collect pb ++ collect mptx + + let !allKeys = msort -- becomes available with Pact42Upgrade + $ LHM.sort + $ dbKeys ++ memKeys + case d of + Pact.DKeySets -> do + let parsed = map Pact.parseAnyKeysetName allKeys + case sequence parsed of + Left msg -> internalDbError $ "doKeys.DKeySets: unexpected decoding " <> T.pack msg + Right v -> pure v + Pact.DModules -> do + let parsed = map Pact.parseModuleName allKeys + case sequence parsed of + Nothing -> internalDbError $ "doKeys.DModules: unexpected decoding" + Just v -> pure v + Pact.DNamespaces -> pure $ map Pact.NamespaceName allKeys + Pact.DDefPacts -> pure $ map Pact.DefPactId allKeys + Pact.DUserTables _ -> pure $ map Pact.RowKey allKeys + Pact.DModuleSource -> do + let parsed = map Pact.parseHashedModuleName allKeys + case sequence parsed of + Just v -> pure v + Nothing -> internalDbError $ "doKeys.DModuleSources: unexpected decoding" + + where + blockLimitStmt = maybe "" (const " WHERE txid < ?;") mlim + blockLimitParam = maybe [] (\(Pact.TxId txid) -> [SInt (fromIntegral txid)]) (snd <$> mlim) + + getDbKeys = do + m <- runMaybeT $ checkDbTablePendingCreation "keys" 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 tn <> blockLimitStmt <> " ORDER BY rowkey") blockLimitParam [RText] + forM ks $ \row -> do + case row of + [SText k] -> return $ fromUtf8 k + _ -> internalDbError "doKeys: The impossible happened." + + tn@(SQ3.Utf8 tnBS) = toUtf8 $ Pact.renderDomain d + collect p = + concatMap NE.toList $ HashMap.elems $ fromMaybe mempty $ HashMap.lookup tnBS (_pendingWrites p) + +failIfTableDoesNotExistInDbAtHeight + :: T.Text -> SQ3.Utf8 -> 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 $ + internalDbError $ "callDb (" <> caller <> "): user error (Database error: ErrorError)" + +recordTxLog + :: Pact.Domain k v Pact.CoreBuiltin Pact.Info + -> SQ3.Utf8 + -> BS.ByteString + -> BlockHandler logger () +recordTxLog d (SQ3.Utf8 k) v = do + -- are we in a tx? if not, error. + (pendingSQLite, txlogs) <- getPendingTxOrError "write" + modify' (bsPendingTx .~ Just (pendingSQLite, DL.snoc txlogs newLog)) + + where + !newLog = Pact.TxLog (Pact.renderDomain d) (T.decodeUtf8 k) v + +recordTableCreationTxLog :: Pact.TableName -> BlockHandler logger () +recordTableCreationTxLog tn = do + (pendingSQLite, txlogs) <- getPendingTxOrError "create table" + modify' $ bsPendingTx .~ Just (pendingSQLite, DL.snoc txlogs newLog) + where + !newLog = Pact.TxLog "SYS:usertables" (Pact._tableName tn) (encodeStable uti) + !uti = Pact.UserTableInfo (Pact._tableModuleName tn) + +modifyPendingData + :: Text + -> (SQLitePendingData -> SQLitePendingData) + -> BlockHandler logger () +modifyPendingData msg f = do + (pending, txlogs) <- getPendingTxOrError msg + modify' $ set bsPendingTx $ Just (f pending, txlogs) + +doCreateUserTable + :: Maybe BlockHeight + -- ^ the highest block we should be seeing tables from + -> Pact.TableName + -> BlockHandler logger () +doCreateUserTable mbh tn = do + -- first check if tablename already exists in pending queues + m <- runMaybeT $ checkDbTablePendingCreation "create table" (tableName tn) + case m of + Nothing -> + liftGas $ Pact.throwDbOpErrorGasM $ Pact.TableAlreadyExists tn + Just () -> do + -- then check if it is in the db + lcTables <- asks $ \e -> + chainweb217Pact + (_blockHandlerVersion e) + (_blockHandlerChainId e) + (_blockHandlerBlockHeight e) + cond <- inDb lcTables $ SQ3.Utf8 $ T.encodeUtf8 $ Pact.renderTableName tn + when cond $ + liftGas $ Pact.throwDbOpErrorGasM $ Pact.TableAlreadyExists tn + + modifyPendingData "create table" + $ over pendingTableCreation (HashSet.insert (T.encodeUtf8 $ Pact.renderTableName tn)) + recordTableCreationTxLog tn + where + inDb lcTables t = do + r <- callDb "doCreateUserTable" $ \db -> + qry db (tableLookupStmt lcTables) [SText t] [RText] + case r of + [[SText 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(?);" + +doRollback :: BlockHandler logger () +doRollback = modify' + $ set bsPendingTx Nothing + +-- | Commit a Pact transaction +doCommit :: BlockHandler logger [Pact.TxLog B8.ByteString] +doCommit = view blockHandlerMode >>= \case + m -> do + txrs <- if m == Pact.Transactional + then do + modify' $ over latestTxId (\(Pact.TxId tid) -> Pact.TxId (succ tid)) + pending <- getPendingTxOrError "commit" + persistIntraBlockWrites <- view blockHandlerPersistIntraBlockWrites + -- merge pending tx into pending block data + modify' $ over (bsBlockHandle . blockHandlePending) (merge persistIntraBlockWrites (fst pending)) + let txLogs = snd pending + modify' $ set bsPendingTx Nothing + return txLogs + else doRollback >> return mempty + return $! DL.toList txrs + where + merge persistIntraBlockWrites txPending blockPending = SQLitePendingData + { _pendingTableCreation = HashSet.union (_pendingTableCreation txPending) (_pendingTableCreation blockPending) + , _pendingWrites = HashMap.unionWith (HashMap.unionWith mergeAtRowKey) (_pendingWrites txPending) (_pendingWrites blockPending) + -- pact4-specific, txlogs are not stored in SQLitePendingData in pact5 + , _pendingTxLogMap = mempty + , _pendingSuccessfulTxs = _pendingSuccessfulTxs blockPending + } + where + mergeAtRowKey txWrites blockWrites = + let lastTxWrite = NE.head txWrites + in case persistIntraBlockWrites of + PersistIntraBlockWrites -> lastTxWrite `NE.cons` blockWrites + DoNotPersistIntraBlockWrites -> lastTxWrite :| [] + +-- | Begin a Pact transaction. Note that we don't actually use the ExecutionMode anymore. +doBegin :: (Logger logger) => Pact.ExecutionMode -> BlockHandler logger (Maybe Pact.TxId) +doBegin _m = do + use bsPendingTx >>= \case + Just _ -> do + txid <- use latestTxId + liftGas $ Pact.throwDbOpErrorGasM (Pact.TxAlreadyBegun ("TxId " <> sshow (Pact._txId txid))) + Nothing -> do + modify' + $ set bsPendingTx (Just (emptySQLitePendingData, mempty)) + Just <$> use latestTxId + +toTxLog :: MonadThrow m => T.Text -> SQ3.Utf8 -> BS.ByteString -> m (Pact.TxLog Pact.RowData) +toTxLog d key value = + case fmap (view Pact.document) $ Pact._decodeRowData Pact.serialisePact_lineinfo value of + 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 diff --git a/src/Chainweb/Pact5/NoCoinbase.hs b/src/Chainweb/Pact5/NoCoinbase.hs new file mode 100644 index 0000000000..62e47813f1 --- /dev/null +++ b/src/Chainweb/Pact5/NoCoinbase.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | +-- Module: Chainweb.Pact5.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.Pact5.NoCoinbase +( noCoinbase +) where + +import Data.Void +import Pact.Core.Command.Types +import Pact.Core.Gas +import Pact.Core.Hash +import Pact.Core.PactValue + +-- | No-op coinbase payload +-- +noCoinbase :: CommandResult a Void +noCoinbase = CommandResult + (RequestKey pactInitialHash) Nothing + (PactResultOk (PString "NO_COINBASE")) + (Gas 0) Nothing Nothing Nothing [] +{-# NOINLINE noCoinbase #-} diff --git a/src/Chainweb/Pact5/SPV.hs b/src/Chainweb/Pact5/SPV.hs new file mode 100644 index 0000000000..84917207cf --- /dev/null +++ b/src/Chainweb/Pact5/SPV.hs @@ -0,0 +1,133 @@ +{-# language + ImportQualifiedPost + , LambdaCase + , OverloadedStrings + , ScopedTypeVariables + , TypeApplications +#-} + +module Chainweb.Pact5.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.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) +import Data.Aeson qualified as Aeson +import Data.Text (Text) +import Data.Text.Encoding qualified as Text +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.StableEncoding (encodeStable) + +pactSPV :: BlockHeaderDb -> BlockHeader -> SPVSupport +pactSPV bdb bh = SPVSupport + { _spvSupport = \proofType proof -> verifySPV bdb bh proofType proof + , _spvVerifyContinuation = \contProof -> verifyCont bdb bh contProof + } + +-- | 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 + -> ContProof + -> IO (Either Text DefPactExec) +verifyCont bdb bh (ContProof base64Proof) = runExceptT $ do + proofBytes <- case decodeB64UrlNoPaddingText (Text.decodeUtf8 base64Proof) of + Left _ -> throwError "verifyCont: Invalid base64-encoded transaction output proof" + Right bs -> return bs + + outputProof <- case Aeson.decodeStrict' @(TransactionOutputProof SHA512t_256) proofBytes of + Nothing -> throwError "verifyCont: Cannot decode transaction output proof" + Just u -> return u + + let cid = CW._chainId bdb + + when (view outputProofChainId outputProof /= cid) $ + throwError "verifyCont: cannot redeem continuation proof on wrong targget chain" + + -- ContProof verification is a 3-step process: + -- 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) + + -- TODO: Do we care about the error type here? + commandResult <- case Aeson.decodeStrict' @(CommandResult Hash Aeson.Value) proof of + Nothing -> throwError "verifyCont: Unable to decode SPV transaction output" + Just cr -> return cr + + case _crContinuation commandResult of + Nothing -> throwError "verifyCont: No pact exec found in command result" + Just defpactExec -> return defpactExec + +verifySPV :: () + => BlockHeaderDb + -> BlockHeader + -> 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 + + case proofType of + "ETH" -> do + throwError "verifySPV: ETH proof type is not yet supported in Pact5." + "TXOUT" -> do + outputProof <- case pactObjectOutputProof proof of + Left err -> throwError err + Right u -> return u + + when (view outputProofChainId outputProof /= cid) $ + throwError "verifySPV: cannot redeem spv proof on wrong target chain" + + -- SPV proof verification is a 3-step process: + -- 1. Verify SPV TransactionOutput proof via Chainweb SPV API + -- 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) + + commandResult <- case Aeson.decodeStrict' @(CommandResult Hash Aeson.Value) rawCommandResult of + Nothing -> throwError "verifySPV: Unable to decode SPV transaction output" + Just cr -> return cr + + case _crResult commandResult of + PactResultErr _ -> do + throwError "verifySPV: Failed command result in tx output proof" + PactResultOk pv -> do + case pv of + PObject o -> do + return (ObjectData o) + _ -> do + throwError "verifySPV: command result not an object" + _ -> do + throwError $ "Unsupported SPV type: " <> proofType + +pactObjectOutputProof :: ObjectData PactValue -> Either Text (TransactionOutputProof SHA512t_256) +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/Pact5/Templates.hs b/src/Chainweb/Pact5/Templates.hs new file mode 100644 index 0000000000..b2540e418f --- /dev/null +++ b/src/Chainweb/Pact5/Templates.hs @@ -0,0 +1,145 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | +-- Module : Chainweb.Pact5.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.Pact5.Templates +( mkFundTxTerm +, mkBuyGasTerm +, mkRedeemGasTerm +, mkCoinbaseTerm +) where + +import Data.Decimal +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 Chainweb.Pact5.Types + +fundTxTemplate :: Text -> Text -> Expr () +fundTxTemplate sender mid = + let senderTerm = strLit sender + midTerm = strLit mid + varApp = qn "fund-tx" "coin" + 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 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 mid sender = + let midTerm = strLit mid + senderTerm = strLit sender + varApp = qn "redeem-gas" "coin" + 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 () + +strLit :: Text -> Expr () +strLit txt = Constant (LString txt) () + +qn :: Text -> Text -> Expr () +qn name modname = Var (QN (QualifiedName name (ModuleName modname Nothing))) () + +bn :: Text -> Expr () +bn name = Var (BN (BareName name)) () + +mkFundTxTerm + :: MinerId -- ^ Id of the miner to fund + -> MinerKeys + -> Text -- ^ Address of the sender from the command + -> GasSupply + -> (Expr (), Map.Map Field PactValue) +mkFundTxTerm (MinerId mid) (MinerKeys ks) sender total = + let + term = fundTxTemplate sender mid + buyGasData = Map.fromList + [ ("miner-keyset", convertKeySet ks) + , ("total", 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) +mkBuyGasTerm sender total = (buyGasTemplate sender, buyGasData) + where + buyGasData = Map.fromList + [ ("total", 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 = + (redeemGasTemplate mid sender, redeemGasData) + where + redeemGasData = PObject $ Map.fromList + [ ("total", PDecimal $ _pact5GasSupply total) + , ("fee", PDecimal $ _pact5GasSupply fee) + , ("miner-keyset", convertKeySet ks) + ] +{-# INLINABLE mkRedeemGasTerm #-} + +coinbaseTemplate :: Text -> Expr () +coinbaseTemplate mid = + let midTerm = strLit mid + varApp = qn "coinbase" "coin" + 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) + where + coinbaseData = PObject $ Map.fromList + [ ("miner-keyset", convertKeySet ks) + , ("reward", PDecimal reward) + ] +{-# INLINABLE mkCoinbaseTerm #-} diff --git a/src/Chainweb/Pact5/Transaction.hs b/src/Chainweb/Pact5/Transaction.hs new file mode 100644 index 0000000000..d9a8aa8714 --- /dev/null +++ b/src/Chainweb/Pact5/Transaction.hs @@ -0,0 +1,249 @@ +{-# 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/TransactionExec.hs b/src/Chainweb/Pact5/TransactionExec.hs new file mode 100644 index 0000000000..651bd6c4ee --- /dev/null +++ b/src/Chainweb/Pact5/TransactionExec.hs @@ -0,0 +1,960 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE GADTs #-} + +-- | +-- Module : Chainweb.Pact.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.Pact5.TransactionExec + ( -- * Public API + TransactionM(..) + , TransactionEnv(..) + , applyCoinbase + , applyLocal + , applyCmd + + -- * Semipublic API, only used by the mempool + , buyGas + + -- * Private API, exposed only for tests + , runVerifiers + , runPayload + , runGenesisPayload + , redeemGas + , applyUpgrades + , managedNamespacePolicy + + -- * Utility + , initialGasOf + +) where + +import Control.Lens +import Control.Monad +import Control.Monad.Catch +import Control.Monad.Reader +import Control.Parallel.Strategies(using, rseq) + +import qualified Data.ByteString as B +import qualified Data.ByteString.Short as SB +import Data.Coerce (coerce) +import Data.Decimal (Decimal, roundTo) +import Data.IORef +import qualified Data.Map.Strict as Map +import Data.Maybe +import qualified Data.Set as S +import Data.Text (Text) +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 +import Pact.Core.DefPacts.Types +import Pact.Core.Environment hiding (_chainId) +import Pact.Core.Evaluate +import Pact.Core.Gas +import Pact.Core.Hash +import Pact.Core.Info +import Pact.Core.Names +import Pact.Core.Namespace +import Pact.Core.PactValue +import Pact.Core.Persistence.Types hiding (GasM(..)) +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 + +-- 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.Pact5.Templates +import Chainweb.Pact5.Types + +import Chainweb.Time +import Chainweb.Pact5.Transaction +import Chainweb.VerifierPlugin hiding (chargeGas) +import Chainweb.Utils +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 + +-- 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. + +-- -------------------------------------------------------------------------- -- + +coinCap :: Text -> [PactValue] -> CapToken QualifiedName PactValue +coinCap n vs = CapToken (QualifiedName n (ModuleName "coin" Nothing)) vs + +data TransactionEnv logger = TransactionEnv + { _txEnvLogger :: logger + , _txEnvGasEnv :: GasEnv CoreBuiltin Info + } + +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 } + deriving newtype + ( Functor + , Applicative + , Monad + , MonadReader (TransactionEnv logger) + , MonadError (Pact5.PactError Info) + , MonadThrow + , MonadIO + ) + +chargeGas :: Info -> GasArgs CoreBuiltin -> TransactionM logger () +chargeGas info gasArgs = do + gasEnv <- view txEnvGasEnv + either throwError return =<< + liftIO (chargeGasArgsM gasEnv info [] gasArgs) + +-- 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 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) (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 + ] + verifierResult <- liftIO $ runVerifierPlugins + (_chainwebVersion txCtx, _chainId txCtx, ctxCurrentBlockHeight txCtx) logger + allVerifiers + (Pact4.Gas $ fromIntegral @SatWord @Int64 $ _gas $ milliGasToGas $ initGasRemaining) + pact4TxVerifiers + 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 + -- NB: this is not nice. + -- TODO: better gas info here + chargeGas noInfo $ GAConstant $ gasToMilliGas $ Gas $ + verifierGasRemaining - min (_gas (milliGasToGas initGasRemaining)) verifierGasRemaining + +applyLocal + :: (Logger logger) + => logger + -- ^ Pact logger + -> Maybe logger + -- ^ Pact gas logger + -> PactDb CoreBuiltin Info + -- ^ Pact db environment + -> TxContext + -- ^ 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)) +applyLocal logger maybeGasLogger coreDb txCtx spvSupport cmd = do + let gasLogsEnabled = maybe GasLogsDisabled (const GasLogsEnabled) maybeGasLogger + let + gasLimitGas :: Gas = cmd ^. cmdPayload . pMeta . pmGasLimit . _GasLimit + gasEnv <- mkTableGasEnv (MilliGasLimit (gasToMilliGas gasLimitGas)) gasLogsEnabled + let runLocal = runVerifiers txCtx cmd *> runPayload Local localFlags coreDb spvSupport [] managedNamespacePolicy gasEnv txCtx cmd + let txEnv = TransactionEnv + { _txEnvGasEnv = gasEnv + , _txEnvLogger = logger + } + runExceptT (runReaderT (runTransactionM runLocal) txEnv) >>= \case + Left err -> do + return CommandResult + { _crReqKey = RequestKey $ _cmdHash cmd + , _crTxId = Nothing + , _crResult = PactResultErr err + -- all gas is used when a command fails + , _crGas = cmd ^. cmdPayload . pMeta . pmGasLimit . _GasLimit + , _crLogs = Nothing + , _crContinuation = Nothing + , _crEvents = [] + , _crMetaData = Nothing + } + Right payloadResult -> do + gasUsed <- milliGasToGas <$> readIORef (_geGasRef gasEnv) + let result = case reverse (_erOutput payloadResult) of + x:_ -> x + _ -> InterpretValue PUnit noInfo + return CommandResult + { _crReqKey = RequestKey $ _cmdHash cmd + , _crTxId = _erTxId payloadResult + , _crResult = + PactResultOk $ compileValueToPactValue $ result + , _crGas = gasUsed + , _crLogs = Just $ _erLogs payloadResult + , _crContinuation = _erExec payloadResult + , _crEvents = _erEvents payloadResult + , _crMetaData = Just (J.toJsonViaEncode $ StableEncoding $ ctxToPublicData (cmd ^. cmdPayload . pMeta) txCtx) + } + + where + localFlags = S.fromList + [ FlagDisableRuntimeRTC + , FlagEnforceKeyFormats + -- Note: this is currently not conditional + -- in pact-5 exec. This may change if it breaks + -- anyone's workflow + , FlagAllowReadInLocal + , FlagRequireKeysetNs + ] + +-- | The main entry point to executing transactions. From here, +-- 'applyCmd' assembles the command environment for a command, +-- purchases gas for the command, executes the command, and +-- redeems leftover gas. +-- 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 + :: forall logger. (Logger logger) + => logger + -- ^ Pact logger + -> Maybe logger + -- ^ Pact gas logger + -> PactDb CoreBuiltin Info + -- ^ Pact db environment + -> TxContext + -- ^ tx metadata + -> SPVSupport + -- ^ SPV support (validates cont proofs) + -> Gas + -- ^ initial gas cost + -> Command (Payload PublicMeta ParsedCode) + -- ^ command with payload to execute + -> IO (Either Pact5GasPurchaseFailure (CommandResult [TxLog ByteString] (Pact5.PactError Info))) +applyCmd logger maybeGasLogger db txCtx spv initialGas cmd = do + logDebug_ logger $ "applyCmd: " <> sshow (_cmdHash cmd) + let flags = Set.fromList + [ FlagDisableRuntimeRTC + , FlagDisableHistoryInTransactionalMode + , FlagEnforceKeyFormats + , FlagRequireKeysetNs + ] + 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 + -- on-chain. + -- Todo: Do we even need this assuming we are passing in the same gas env? + let paidFor :: TransactionM logger EvalResult + paidFor = do + -- TODO: better "info" for this, for gas logs + chargeGas noInfo (GAConstant $ gasToMilliGas initialGas) + + runVerifiers txCtx cmd + + liftIO $ dumpGasLogs "applyCmd.paidFor.beforeRunPayload" (_cmdHash cmd) maybeGasLogger gasEnv + evalResult <- runPayload Transactional flags db spv [] managedNamespacePolicy gasEnv txCtx cmd + liftIO $ dumpGasLogs "applyCmd.paidFor.afterRunPayload" (_cmdHash cmd) maybeGasLogger gasEnv + return evalResult + + eBuyGasResult <- do + if GasLimit initialGas > gasLimit + then do + pure $ Left (PurchaseGasTxTooBigForGasLimit requestKey) + else do + buyGas logger gasEnv db txCtx cmd >>= \case + Left buyGasError -> do + pure $ Left (BuyGasError requestKey buyGasError) + Right buyGasResult -> do + pure $ Right buyGasResult + + case eBuyGasResult of + Left err -> do + pure (Left err) + Right buyGasResult -> do + let txEnv = TransactionEnv + { _txEnvGasEnv = gasEnv + , _txEnvLogger = logger + } + runExceptT (runReaderT (runTransactionM paidFor) txEnv) >>= \case + Left err -> do + -- if any error occurs during the transaction or verifiers, the output of the command is that error + -- 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 + (gasLimit ^. _GasLimit) + (_peDefPactId <$> _erExec buyGasResult) + cmd + case eRedeemGasResult of + Left redeemGasError -> do + pure (Left (RedeemGasError requestKey redeemGasError)) + Right redeemGasResult -> do + return $ Right $ CommandResult + { _crReqKey = RequestKey $ _cmdHash cmd + , _crTxId = Nothing + , _crResult = PactResultErr err + -- all gas is used when a command fails + , _crGas = cmd ^. cmdPayload . pMeta . pmGasLimit . _GasLimit + , _crLogs = Just $ _erLogs buyGasResult <> _erLogs redeemGasResult + , _crContinuation = Nothing + , _crEvents = _erEvents buyGasResult <> _erEvents redeemGasResult + , _crMetaData = Nothing + } + Right payloadResult -> do + gasUsed <- milliGasToGas <$> readIORef (_geGasRef gasEnv) + -- return all unused gas to the user and send all used + -- gas to the miner. + eRedeemGasResult <- redeemGas + logger db txCtx + gasUsed + (_peDefPactId <$> _erExec buyGasResult) + cmd + + case eRedeemGasResult of + Left redeemGasError -> do + pure (Left (RedeemGasError requestKey redeemGasError)) + Right redeemGasResult -> do + -- ensure we include the events and logs from buyGas and redeemGas in the result + return $ Right $ CommandResult + { _crReqKey = RequestKey $ _cmdHash cmd + , _crTxId = _erTxId payloadResult + , _crResult = + -- TODO: don't use `last` here for GHC 9.10 compat + PactResultOk $ compileValueToPactValue $ last $ _erOutput payloadResult + , _crGas = gasUsed + , _crLogs = Just $ _erLogs buyGasResult <> _erLogs payloadResult <> _erLogs redeemGasResult + , _crContinuation = _erExec payloadResult + , _crEvents = _erEvents buyGasResult <> _erEvents payloadResult <> _erEvents redeemGasResult + , _crMetaData = Nothing + } + where + !gasLimit = view (cmdPayload . pMeta . pmGasLimit) cmd + +-- | Convert context to datatype for Pact environment using the +-- current blockheight, referencing the parent header (not grandparent!) +-- hash and blocktime data. +-- +ctxToPublicData :: PublicMeta -> TxContext -> PublicData +ctxToPublicData pm (TxContext ph _) = 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 + +-- | 'applyCoinbase' performs upgrade transactions and constructs and executes +-- a transaction which pays miners their block reward. +applyCoinbase + :: (Logger logger) + => logger + -- ^ Pact logger + -> PactDb CoreBuiltin Info + -- ^ Pact db environment + -> Decimal + -- ^ Miner reward + -> TxContext + -- ^ tx metadata and parent header + -> IO (Either Pact5CoinbaseError (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 + -- 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 + (coinbaseTerm, coinbaseData) = mkCoinbaseTerm mid mks reward + eCoinbaseTxResult <- + evalExecTerm Transactional + db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + (ctxToPublicData noPublicMeta txCtx) + MsgData + { mdHash = coinbaseHash + , mdData = coinbaseData + , mdSigners = [] + , mdVerifiers = [] + } + -- applyCoinbase injects a magic capability to get permission to mint tokens + (emptyCapState & csSlots .~ [CapSlot (coinCap "COINBASE" []) []]) + -- TODO: better info here might be very valuable for debugging + (noSpanInfo <$ coinbaseTerm) + case eCoinbaseTxResult of + Left err -> do + pure $ Left $ CoinbasePactError err + Right coinbaseTxResult -> do + return $! Right $! CommandResult + { _crReqKey = RequestKey coinbaseHash + , _crTxId = _erTxId coinbaseTxResult + , _crResult = + -- TODO: don't use `last` for GHC 9.10 compat + PactResultOk $ compileValueToPactValue $ last $ _erOutput coinbaseTxResult + , _crGas = _erGas coinbaseTxResult + , _crLogs = Just $ _erLogs coinbaseTxResult + , _crContinuation = _erExec coinbaseTxResult + , _crMetaData = Nothing + , _crEvents = _erEvents coinbaseTxResult + } + + where + parentBlockHash = view blockHash $ _parentHeader $ _tcParentHeader txCtx + Miner mid mks = _tcMiner txCtx + +-- | 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 + :: (Logger logger) + => logger + -> PactDb CoreBuiltin Info + -> 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} <- + v ^? versionUpgrades . atChain cid . ix currentHeight = applyUpgrade upgradeTxs + | otherwise = return () + where + v = _chainwebVersion txCtx + currentHeight = ctxCurrentBlockHeight txCtx + cid = _chainId txCtx + applyUpgrade :: [Transaction] -> IO () + applyUpgrade upgradeTxs = do + forM_ upgradeTxs $ \tx -> + tryAllSynchronous (runUpgrade logger db txCtx (view payloadObj <$> tx)) >>= \case + Right _ -> pure () + Left e -> do + logError_ logger $ "Upgrade transaction failed! " <> sshow e + throwM e + +-- | Run a genesis transaction. This differs from an ordinary transaction: +-- * Special capabilities allow making token allocations +-- * Coinbase is allowed, which allows minting as well +-- * Gas is free +-- * Any failures are fatal to PactService +runGenesisPayload + :: Logger logger + => logger + -> PactDb CoreBuiltin Info + -> SPVSupport + -> TxContext + -> Command (Payload PublicMeta ParsedCode) + -> IO (Either (Pact5.PactError Info) (CommandResult [TxLog ByteString] Void)) +runGenesisPayload logger db spv ctx cmd = do + gasRef <- newIORef (MilliGas 0) + let gasEnv = GasEnv gasRef Nothing freeGasModel + let txEnv = TransactionEnv logger gasEnv + -- Todo gas logs + freeGasEnv <- mkFreeGasEnv GasLogsDisabled + runExceptT + (runReaderT + (runTransactionM + (runPayload + Transactional + (Set.singleton FlagDisableRuntimeRTC) + db + spv + [ CapToken (QualifiedName "GENESIS" (ModuleName "coin" Nothing)) [] + , CapToken (QualifiedName "COINBASE" (ModuleName "coin" Nothing)) [] + ] + -- allow installing to root namespace + SimpleNamespacePolicy + freeGasEnv + ctx + cmd <&> \evalResult -> + CommandResult + { _crReqKey = RequestKey (_cmdHash cmd) + , _crTxId = _erTxId evalResult + , _crResult = PactResultOk (compileValueToPactValue $ last $ _erOutput evalResult) + , _crGas = _erGas evalResult + , _crLogs = Just $ _erLogs evalResult + , _crContinuation = _erExec evalResult + , _crMetaData = Nothing + , _crEvents = _erEvents evalResult + } + ) + ) txEnv + ) + +runPayload + :: (Logger logger) + => ExecutionMode + -> Set ExecutionFlag + -> PactDb CoreBuiltin Info + -> SPVSupport + -> [CapToken QualifiedName PactValue] + -> NamespacePolicy + -> GasEnv CoreBuiltin Info + -> TxContext + -> Command (Payload PublicMeta ParsedCode) + -> TransactionM logger EvalResult +runPayload execMode execFlags db spv specialCaps namespacePolicy gasModel txCtx cmd = do + -- Note [Throw out verifier proofs eagerly] + let !verifiersWithNoProof = + (fmap . fmap) (\_ -> ()) verifiers + `using` (traverse . traverse) rseq + + (either throwError return =<<) $ liftIO $ + case payload ^. pPayload of + Exec ExecMsg {..} -> + evalExec (RawCode (_pcCode _pmCode)) execMode + db spv gasModel execFlags namespacePolicy + (ctxToPublicData publicMeta txCtx) + MsgData + { mdHash = _cmdHash cmd + , mdData = _pmData + , mdVerifiers = verifiersWithNoProof + , mdSigners = signers + } + (emptyCapState + & csSlots .~ [CapSlot cap [] | cap <- specialCaps]) + -- Note: we delete the information here from spans for + -- smaller db footprints + (_pcExps _pmCode) + Continuation ContMsg {..} -> + evalContinuation execMode + db spv gasModel execFlags namespacePolicy + (ctxToPublicData publicMeta txCtx) + MsgData + { mdHash = _cmdHash cmd + , mdData = _cmData + , mdSigners = signers + , mdVerifiers = verifiersWithNoProof + } + (emptyCapState + & csSlots .~ [CapSlot cap [] | cap <- specialCaps]) + Cont + { _cPactId = _cmPactId + , _cStep = _cmStep + , _cRollback = _cmRollback + , _cProof = _cmProof + } + where + payload = cmd ^. cmdPayload + verifiers = payload ^. pVerifiers . _Just + signers = payload ^. pSigners + -- chash = toUntypedHash $ _cmdHash cmd + publicMeta = cmd ^. cmdPayload . pMeta + +runUpgrade + :: (Logger logger) + => logger + -> PactDb CoreBuiltin Info + -> TxContext + -> Command (Payload PublicMeta ParsedCode) + -> IO () +runUpgrade _logger db txContext cmd = case payload ^. pPayload of + Exec pm -> do + freeGasEnv <- mkFreeGasEnv GasLogsDisabled + evalExec (RawCode (_pcCode (_pmCode pm))) Transactional + db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) + -- allow installing to root namespace + SimpleNamespacePolicy + (ctxToPublicData publicMeta txContext) + MsgData + { mdHash = chash + , mdData = PObject mempty + , mdSigners = [] + , mdVerifiers = [] + } + (emptyCapState + & 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 + -- TODO: we should probably put these events somewhere! + Right _r -> return () + Continuation _ -> error "runGenesisCore Continuation not supported" + where + payload = cmd ^. cmdPayload + publicMeta = payload ^. pMeta + chash = _cmdHash cmd + +enrichedMsgBodyForGasPayer :: Map.Map Field PactValue -> Command (Payload PublicMeta ParsedCode) -> PactValue +enrichedMsgBodyForGasPayer dat cmd = case (_pPayload $ _cmdPayload cmd) of + Exec exec -> + PObject $ Map.fromList + [ ("tx-type", PString "exec") + -- As part of legacy compat, the "exec-code" field passes chunks of source code as a list + -- in Pact 4, we used the pretty instance (O_O) for this. + -- In Pact 5, what we instead do, is we use the lexer/parser generated `SpanInfo` to + -- then slice the section of code each `TopLevel` uses. + , ("exec-code", PList (Vector.fromList (fmap (PString . sliceFromSourceLines codeLines) expInfos))) + , ("exec-user-data", _pmData exec) + ] `Map.union` dat + where + expInfos = fmap (view Lisp.topLevelInfo) $ _pcExps $ _pmCode exec + codeLines = T.lines (_pcCode (_pmCode exec)) + Continuation cont -> + PObject $ Map.fromList + [ ("tx-type", PString "cont") + , ("cont-pact-id", PString (_defPactId (_cmPactId cont))) + , ("cont-step", PInteger (fromIntegral (_cmStep cont))) + , ("cont-is-rollback", PBool (_cmRollback cont)) + , ("cont-user-data", _cmData cont) + , ("cont-has-proof", PBool (isJust (_cmProof cont))) + ] `Map.union` dat + +-- | Build and execute 'coin.buygas' command from miner info and user command +-- info (see 'TransactionExec.applyCmd' for more information). +-- +-- see: 'pact/coin-contract/coin.pact#fund-tx' and 'pact/coin-contract/coin.pact#buy-gas' +-- +buyGas + :: (Logger logger) + => logger + -> GasEnv CoreBuiltin Info + -> PactDb CoreBuiltin Info + -> TxContext + -> Command (Payload PublicMeta ParsedCode) + -> IO (Either Pact5BuyGasError EvalResult) +buyGas logger origGasEnv db txCtx cmd = do + let gasEnv = origGasEnv & geGasModel . gmGasLimit .~ Just (MilliGasLimit (MilliGas 1_500_000)) + logFunctionText logger L.Debug $ + "buying gas for " <> sshow (_cmdHash cmd) + -- TODO: use quirked gas? + let gasPayerCaps = + [ cap + | signer <- signers + , SigCapability cap <- _siCapList signer + , _qnName (_ctName cap) == "GAS_PAYER" + ] + let gasLimit = publicMeta ^. pmGasLimit + let supply = gasSupplyOf (gasLimit ^. _GasLimit) gasPrice + let (buyGasTerm, buyGasData) = + if isChainweb224Pact + then mkBuyGasTerm sender supply + else mkFundTxTerm mid mks sender supply + + runExceptT $ do + gasPayerCap <- case gasPayerCaps of + [gasPayerCap] -> pure (Just gasPayerCap) + [] -> pure Nothing + _ -> do + throwError BuyGasMultipleGasPayerCaps + + -- Todo: fix once pact-5 fixes the signature of evalGasPayercap. + -- 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 + (ctxToPublicData publicMeta txCtx) + MsgData + -- Note: in the case of gaspayer, buyGas is given extra metadata that comes from + -- the Command + { mdData = maybe (PObject buyGasData) (const $ enrichedMsgBodyForGasPayer buyGasData cmd) gasPayerCap + , mdHash = bgHash + , mdSigners = signersWithDebit + -- no verifiers are allowed in buy gas + , mdVerifiers = [] + } + (emptyCapState & csSlots .~ [CapSlot (coinCap "GAS" []) []]) + (noSpanInfo <$ buyGasTerm) + Nothing -> + evalExecTerm Transactional + db + noSPVSupport + gasEnv + (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + (ctxToPublicData publicMeta txCtx) + MsgData + -- Note: in the case of gaspayer, buyGas is given extra metadata that comes from + -- the Command + { mdData = maybe (PObject buyGasData) (const $ enrichedMsgBodyForGasPayer buyGasData cmd) gasPayerCap + , mdHash = bgHash + , mdSigners = signersWithDebit + -- no verifiers are allowed in buy gas + , mdVerifiers = [] + } + (emptyCapState & csSlots .~ [CapSlot (coinCap "GAS" []) []]) + (noSpanInfo <$ buyGasTerm) + + case e of + Right er' -> do + case _erExec er' of + Nothing + | isChainweb224Pact -> + return er' + | 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" + Just _pe + | isChainweb224Pact -> + internalError "buyGas: Internal error - continuation found after 2.24 fork" + | otherwise -> + return er' + Left err -> do + throwError $ BuyGasPactError err + + where + isChainweb224Pact = guardCtx chainweb224Pact txCtx + publicMeta = cmd ^. cmdPayload . pMeta + sender = publicMeta ^. pmSender + gasPrice = publicMeta ^. pmGasPrice + signers = cmd ^. cmdPayload . pSigners + signedForGas signer = + any (\(SigCapability sc) -> sc == coinCap "GAS" []) (_siCapList signer) + addDebit signer + | signedForGas signer = + signer & siCapList %~ (SigCapability (coinCap "DEBIT" [PString sender]):) + | otherwise = signer + signersWithDebit = + fmap addDebit signers + + Miner mid mks = _tcMiner txCtx + + Hash chash = _cmdHash cmd + bgHash = Hash (chash <> "-buygas") + +-- | Build and execute 'coin.redeem-gas' command from miner info and previous +-- command results (see 'TransactionExec.applyCmd') +-- +redeemGas :: (Logger logger) + => logger + -> PactDb CoreBuiltin Info -> TxContext + -> Gas + -> Maybe DefPactId + -> Command (Payload PublicMeta ParsedCode) + -> IO (Either Pact5RedeemGasError EvalResult) +redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd + | isChainweb224Pact, Nothing <- maybeFundTxPactId = do + logFunctionText logger L.Debug $ + "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 + -- todo: gas logs + freeGasEnv <- mkFreeGasEnv GasLogsDisabled + evalExecTerm + Transactional + -- TODO: more execution flags? + db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + (ctxToPublicData publicMeta txCtx) + MsgData + { mdData = redeemGasData + , mdHash = rgHash + , mdSigners = signers + , mdVerifiers = [] + } + (emptyCapState & csSlots .~ [CapSlot (coinCap "GAS" []) []]) + (noSpanInfo <$ redeemGasTerm) >>= \case + Left unknownPactError -> do + pure $ Left (RedeemGasPactError unknownPactError) + Right evalResult -> do + pure $ Right evalResult + + | not isChainweb224Pact, Just fundTxPactId <- maybeFundTxPactId = do + freeGasEnv <- mkFreeGasEnv GasLogsDisabled + logFunctionText logger L.Debug $ + "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) + + evalContinuation Transactional + db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + (ctxToPublicData publicMeta txCtx) + MsgData + { mdData = redeemGasData + , mdHash = rgHash + , mdSigners = signers + , mdVerifiers = [] + } + (emptyCapState & csSlots .~ [CapSlot (coinCap "GAS" []) []]) + Cont + { _cPactId = fundTxPactId + , _cStep = 1 + , _cRollback = False + , _cProof = Nothing + } >>= \case + Left err -> do + pure $ Left (RedeemGasPactError err) + Right evalResult -> do + return $ Right evalResult + + | otherwise = + internalError "redeemGas: Internal error - defpact ID does not match chainweb224Pact flag" + + 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 + sender = publicMeta ^. pmSender + gasFee = gasSupplyOf + gasUsed + (publicMeta ^. pmGasPrice) + gasTotal = gasSupplyOf + (publicMeta ^. pmGasLimit . _GasLimit) + (publicMeta ^. pmGasPrice) + + +-- -- ---------------------------------------------------------------------------- -- +-- -- Utilities + +-- | Initial gas charged for transaction size +-- ignoring the size of a continuation proof, if present +-- +initialGasOf :: PayloadWithText meta ParsedCode -> Gas +initialGasOf payload = Gas gasFee + where + feePerByte :: Rational = 0.01 + + contProofSize = + case payload ^. payloadObj . pPayload of + Continuation (ContMsg _ _ _ _ (Just (ContProof p))) -> B.length p + _ -> 0 + txSize = SB.length (payload ^. payloadBytes) - 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 #-} + +-- | Chainweb's namespace policy for ordinary transactions. +-- Doesn't allow installing modules in the root namespace. +managedNamespacePolicy :: NamespacePolicy +managedNamespacePolicy = SmartNamespacePolicy + False + (QualifiedName "validate" (ModuleName "ns" Nothing)) + + +-- | Calculate the gas fee (pact-generate gas cost * user-specified gas price), +-- rounding to the nearest stu. +-- +gasSupplyOf :: Gas -> GasPrice -> GasSupply +gasSupplyOf (Gas gas) (GasPrice gp) = GasSupply gs + where + gs = toCoinUnit (fromIntegral gas * gp) +{-# INLINE gasSupplyOf #-} + +-- | Round to the nearest Stu +-- +toCoinUnit :: Decimal -> Decimal +toCoinUnit = roundTo 12 +{-# INLINE toCoinUnit #-} + +dumpGasLogs :: (Logger logger) + => Text -- ^ context + -> Hash -- ^ Command Hash + -> Maybe logger -- ^ Gas logger + -> GasEnv CoreBuiltin Info + -> IO () +dumpGasLogs ctx txHash maybeGasLogger gasEnv = do + forM_ ((,) <$> _geGasLog gasEnv <*> maybeGasLogger) $ \(gasLogRef, gasLogger) -> do + gasLogs <- readIORef gasLogRef + let prettyLogs = Pact5.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 diff --git a/src/Chainweb/Pact5/Types.hs b/src/Chainweb/Pact5/Types.hs new file mode 100644 index 0000000000..6fa8486980 --- /dev/null +++ b/src/Chainweb/Pact5/Types.hs @@ -0,0 +1,197 @@ +{-# 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 + } + +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 + -> PactBlockM logger tbl a -> PactServiceM logger tbl (a, BlockHandle) +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/Pact5/Validations.hs b/src/Chainweb/Pact5/Validations.hs new file mode 100644 index 0000000000..2d5798c4eb --- /dev/null +++ b/src/Chainweb/Pact5/Validations.hs @@ -0,0 +1,242 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} +-- | +-- Module: Chainweb.Pact5.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.Pact5.Validations +( -- * Local metadata _validation + assertLocalMetadata + -- * Validation checks +, assertChainId +, assertGasPrice +, assertNetworkId +, assertSigSize +, assertTxSize +, assertValidateSigs +, assertTxTimeRelativeToParent +, assertTxNotInFuture +, assertCommand + -- * Defaults +, defaultMaxCommandUserSigListSize +, defaultMaxCoinDecimalPlaces +, defaultMaxTTLSeconds +, defaultLenientTimeSlop +) 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.ByteString.Short as SBS +import Data.Word (Word8) + +-- internal modules + +import Chainweb.BlockHeader +import Chainweb.BlockCreationTime (BlockCreationTime(..)) +import Chainweb.Pact.Types +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.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 + + +-- | Check whether a local Api request has valid metadata +-- +assertLocalMetadata + :: P.Command (P.Payload P.PublicMeta c) + -> TxContext + -> Maybe LocalSignatureVerification + -> PactServiceM logger tbl (Either (NonEmpty Text) ()) +assertLocalMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do + v <- view psVersion + cid <- view chainId + Pact4.GasLimit (Pact4.ParsedInteger bgl) <- view psBlockGasLimit + + let P.PublicMeta pcid _ gl gp _ _ = P._pMeta pay + nid = P._pNetworkId pay + signers = P._pSigners pay + + let errs = catMaybes + [ 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 + , 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 signers + , eUnless "Tx time outside of valid range" $ assertTxTimeRelativeToParent pct cmd + ] + + pure $ case nonEmpty errs of + Nothing -> Right () + Just vs -> Left vs + where + sigValidate signers + | Just NoVerify <- sigVerify = True + | otherwise = assertValidateSigs hsh signers sigs + + pct = ParentCreationTime + . view blockCreationTime + . _parentHeader + . _tcParentHeader + $ txCtx + + eUnless t assertion + | assertion = Nothing + | otherwise = Just t + +-- | 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 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 = P.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] -> Bool +assertValidateSigs hsh signers sigs + | length signers /= length sigs = False + | otherwise = and $ zipWith verifyUserSig sigs signers + where + verifyUserSig sig signer = + isRight $ P.verifyUserSig hsh sig signer + +-- 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 + && ttl <= defaultMaxTTLSeconds + where + P.TTLSeconds ttl = view (P.cmdPayload . P.pMeta . P.pmTTL) tx + timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral + P.TxCreationTime txOriginationTime = view (P.cmdPayload . P.pMeta . P.pmCreationTime) 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 (P.cmdPayload . P.pMeta . P.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 -> Bool +assertCommand cmd = + isRight assertHash && + assertValidateSigs hsh signers (P._cmdSigs cmd) + 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 + +-- -------------------------------------------------------------------- -- +-- 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. +-- +defaultMaxTTLSeconds :: Integer +defaultMaxTTLSeconds = 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/Payload.hs b/src/Chainweb/Payload.hs index 8831b74a57..83a375f716 100644 --- a/src/Chainweb/Payload.hs +++ b/src/Chainweb/Payload.hs @@ -375,7 +375,7 @@ type BlockPayload = BlockPayload_ ChainwebMerkleHashAlgorithm -- | The Payload of a block. -- -- The transactions of a block at a given height in the chain are discovered by --- @_blockPayloadTransactionsHash . _blockPayloadHash@. +-- @_blockPayloadTransactionsHash . view blockPayloadHash@. -- -- NOTES: -- diff --git a/src/Chainweb/Payload/PayloadStore.hs b/src/Chainweb/Payload/PayloadStore.hs index 6fb94a0bab..3ff57ca078 100644 --- a/src/Chainweb/Payload/PayloadStore.hs +++ b/src/Chainweb/Payload/PayloadStore.hs @@ -332,7 +332,7 @@ initializePayloadDb initializePayloadDb v db = traverse_ initForChain $ chainIds v where initForChain cid = - addNewPayload db (genesisBlockHeight v cid) $ v ^?! versionGenesis . genesisBlockPayload . onChain cid + addNewPayload db (genesisBlockHeight v cid) $ v ^?! versionGenesis . genesisBlockPayload . atChain cid -- -------------------------------------------------------------------------- -- -- Insert new Payload diff --git a/src/Chainweb/RestAPI/Orphans.hs b/src/Chainweb/RestAPI/Orphans.hs index a12cc0c9bf..80e9843e48 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.Service.Types +import Chainweb.Pact.Types import P2P.Peer diff --git a/src/Chainweb/Rosetta/Internal.hs b/src/Chainweb/Rosetta/Internal.hs index 9d6c628a87..68658a2a6e 100644 --- a/src/Chainweb/Rosetta/Internal.hs +++ b/src/Chainweb/Rosetta/Internal.hs @@ -49,10 +49,10 @@ import qualified Pact.Types.Runtime as P import Pact.Types.Command import Pact.Types.Hash import Pact.Types.Info (noInfo) -import Pact.Types.Runtime (TxId(..), Domain(..), TxLog(..)) +import Pact.Types.Runtime (TxId(..)) import Pact.Types.Persistence (RowKey(..)) -import Pact.Types.RowData (RowData) import Pact.Types.PactValue +import qualified Pact.Core.Persistence as Pact5 import Rosetta import Servant.Server @@ -64,7 +64,7 @@ import Chainweb.BlockHeader import Chainweb.ChainId import Chainweb.Cut import Chainweb.CutDB -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Payload hiding (Transaction(..)) import Chainweb.Payload.PayloadStore import Chainweb.Rosetta.Utils @@ -74,6 +74,7 @@ import Chainweb.Version import Chainweb.WebPactExecutionService (PactExecutionService(..)) import Chainweb.Storage.Table +import qualified Pact.Core.Names as Pact5 --- @@ -128,7 +129,10 @@ matchLogs -> ExceptT RosettaFailure Handler tx matchLogs typ bh logs coinbase txs | bheight == genesisHeight v cid = matchGenesis - | Just upg <- v ^? versionUpgrades . onChain cid . at bheight . _Just = matchRemediation upg + | Just Pact4Upgrade{_pact4UpgradeTransactions = upgradeTxs} + <- v ^? versionUpgrades . atChain cid . at bheight . _Just + = matchRemediation upgradeTxs + -- TODO: integrate pact 5? | otherwise = matchRest where bheight = view blockHeight bh @@ -139,15 +143,15 @@ matchLogs typ bh logs coinbase txs FullLogs -> genesisTransactions logs cid txs SingleLog rk -> genesisTransaction logs cid txs rk - matchRemediation upg = do + matchRemediation upgradeTxs = do hoistEither $ case typ of FullLogs -> overwriteError RosettaMismatchTxLogs $! - remediations logs cid coinbase (_upgradeTransactions upg) txs + remediations logs cid coinbase upgradeTxs txs SingleLog rk -> (noteOptional RosettaTxIdNotFound . overwriteError RosettaMismatchTxLogs) $ - singleRemediation logs cid coinbase (_upgradeTransactions upg) txs rk + singleRemediation logs cid coinbase upgradeTxs txs rk matchRest = hoistEither $ case typ of FullLogs -> @@ -585,10 +589,10 @@ getTxLogs cr bh = do histAcctRow <- hoistEither $ parseHist hist pure $ getBalanceDeltas histAcctRow lastBalSeen where - d = UserTables "coin_coin-table" + d = Pact5.DUserTables (Pact5.TableName "coin-table" (Pact5.ModuleName "coin" Nothing)) parseHist - :: Map TxId [TxLog RowData] + :: Map TxId [Pact5.TxLog Pact5.RowData] -> Either RosettaFailure (Map TxId [AccountRow]) parseHist m | M.size parsed == M.size m = pure $! parsed @@ -597,7 +601,7 @@ getTxLogs cr bh = do parsed = M.mapMaybe (mapM txLogToAccountRow) m parsePrevTxs - :: Map RowKey (TxLog RowData) + :: Map RowKey (Pact5.TxLog Pact5.RowData) -> Either RosettaFailure (Map RowKey AccountRow) parsePrevTxs m | M.size parsed == M.size m = pure $! parsed @@ -667,8 +671,8 @@ getHistoricalLookupBalance' cr bh k = do row <- txLogToAccountRow h ?? RosettaUnparsableTxLog pure $ Just row where - d = UserTables "coin_coin-table" - key = RowKey k -- TODO: How to sanitize this further + d = Pact5.DUserTables (Pact5.TableName "coin-table" (Pact5.ModuleName "coin" Nothing)) + key = Pact5.RowKey k -- TODO: How to sanitize this further getHistoricalLookupBalance :: PactExecutionService diff --git a/src/Chainweb/Rosetta/RestAPI/Server.hs b/src/Chainweb/Rosetta/RestAPI/Server.hs index 35ca4ea1bf..dfbd9220d5 100644 --- a/src/Chainweb/Rosetta/RestAPI/Server.hs +++ b/src/Chainweb/Rosetta/RestAPI/Server.hs @@ -36,7 +36,7 @@ import qualified Data.HashMap.Strict as HM import qualified Data.Text as T import qualified Data.Vector as V -import Pact.Types.Command +import qualified Pact.Types.Command as Pact4 import Pact.Types.Util (fromText') import Rosetta @@ -59,7 +59,7 @@ import Chainweb.RestAPI.Utils import Chainweb.Rosetta.Internal import Chainweb.Rosetta.RestAPI import Chainweb.Rosetta.Utils -import Chainweb.Transaction (ChainwebTransaction) +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils import Chainweb.Utils.Paging import Chainweb.Version @@ -77,7 +77,7 @@ rosettaServer . CanReadablePayloadCas tbl => ChainwebVersion -> [(ChainId, PayloadDb tbl)] - -> [(ChainId, MempoolBackend ChainwebTransaction)] + -> [(ChainId, MempoolBackend Pact4.UnparsedTransaction)] -> PeerDb -> CutDb tbl -> [(ChainId, PactExecutionService)] @@ -100,7 +100,7 @@ someRosettaServer :: CanReadablePayloadCas tbl => ChainwebVersion -> [(ChainId, PayloadDb tbl)] - -> [(ChainId, MempoolBackend ChainwebTransaction)] + -> [(ChainId, MempoolBackend Pact4.UnparsedTransaction)] -> PeerDb -> [(ChainId, PactExecutionService)] -> CutDb tbl @@ -115,7 +115,7 @@ rosettaConstructionServer :: forall tbl (v :: ChainwebVersionT) . CanReadablePayloadCas tbl => ChainwebVersion - -> [(ChainId, MempoolBackend ChainwebTransaction)] + -> [(ChainId, MempoolBackend Pact4.UnparsedTransaction)] -> CutDb tbl -> [(ChainId, PactExecutionService)] -> Server (RosettaConstructionApi v) @@ -133,7 +133,7 @@ rosettaConstructionServer v ms cutDb pacts = someRosettaConstructionServer :: CanReadablePayloadCas tbl => ChainwebVersion - -> [(ChainId, MempoolBackend ChainwebTransaction)] + -> [(ChainId, MempoolBackend Pact4.UnparsedTransaction)] -> [(ChainId, PactExecutionService)] -> CutDb tbl -> SomeServer @@ -429,9 +429,9 @@ constructionCombineH (ConstructionCombineReq _ unsignedTx sigs) = (rosettaError' RosettaUnparsableTx) $ textToEnrichedCommand unsignedTx payload <- getCmdPayload unsignedCmd - userSigs <- matchSigs sigs (_pSigners $! payload) + userSigs <- matchSigs sigs (Pact4._pSigners $! payload) - let signedCmd = unsignedCmd { _cmdSigs = userSigs } + let signedCmd = unsignedCmd { Pact4._cmdSigs = userSigs } signedTx = enrichedCommandToText (EnrichedCommand signedCmd meta signAccts) pure $ ConstructionCombineResp signedTx @@ -452,7 +452,7 @@ constructionHashH (ConstructionHashReq _ signedTx) = -- Note (linda): This code simulates the logic of `sendHandler` closely. constructionSubmitH :: ChainwebVersion - -> [(ChainId, MempoolBackend ChainwebTransaction)] + -> [(ChainId, MempoolBackend Pact4.UnparsedTransaction)] -> ConstructionSubmitReq -> Handler TransactionIdResp constructionSubmitH v ms (ConstructionSubmitReq net tx) = @@ -478,9 +478,11 @@ constructionSubmitH v ms (ConstructionSubmitReq net tx) = note (rosettaError' RosettaUnparsableTx) $ textToEnrichedCommand tx + -- TODO: pact5... what do we do here? case validateCommand v cid cmd of Right validated -> do - let txs = V.fromList [validated] + let txs = (fmap . fmap . fmap) Pact4._pcCode $ + V.fromList [validated] -- If any of the txs in the batch fail validation, we reject them all. liftIO (mempoolInsertCheck mempool txs) >>= checkResult liftIO (mempoolInsert mempool UncheckedInsert txs) diff --git a/src/Chainweb/Rosetta/Utils.hs b/src/Chainweb/Rosetta/Utils.hs index ebf777c20f..eae6551f2e 100644 --- a/src/Chainweb/Rosetta/Utils.hs +++ b/src/Chainweb/Rosetta/Utils.hs @@ -41,17 +41,22 @@ import qualified Pact.Types.Runtime as P import qualified Pact.Types.RPC as P import qualified Pact.Types.Command as P import qualified Pact.Parse as P +import qualified Pact.JSON.Decode as J import qualified Data.Set as S -import Data.Maybe ( fromMaybe ) -import qualified Pact.Types.RowData as P +import Data.Maybe (fromMaybe) import Numeric.Natural ( Natural ) import Pact.Types.Command import Pact.Types.PactValue (PactValue(..)) -import Pact.Types.Exp (Literal(..)) import Pact.JSON.Legacy.Value +import qualified Pact.Core.Persistence as PCore +import qualified Pact.Core.PactValue as PCore +import qualified Pact.Core.Literal as PCore +import qualified Pact.Core.Names as PCore +import qualified Pact.Core.StableEncoding as PCore + import Rosetta -- internal modules @@ -1237,11 +1242,11 @@ rowDataToAccountLog (currKey, currBal, currGuard) prev = do } -- | Parse TxLog Value into fungible asset account columns -txLogToAccountRow :: P.TxLog P.RowData -> Maybe AccountRow -txLogToAccountRow (P.TxLog _ key (P.RowData _ (P.ObjectMap row))) = do - LegacyValue guard <- toLegacyJsonViaEncode . P.rowDataToPactValue <$> M.lookup "guard" row - case M.lookup "balance" row of - Just (P.RDLiteral (LDecimal bal)) -> pure (key, bal, guard) +txLogToAccountRow :: PCore.TxLog PCore.RowData -> Maybe AccountRow +txLogToAccountRow (PCore.TxLog _ key (PCore.RowData row)) = do + LegacyValue guard <- (maybe (error "txLogToAccountRow: can't decode PactValue") id . J.decodeStrict . PCore.encodeStable) <$> M.lookup (PCore.Field "guard") row + case M.lookup (PCore.Field "balance") row of + Just (PCore.PLiteral (PCore.LDecimal bal)) -> pure (key, bal, guard) _ -> Nothing hushResult :: Result a -> Maybe a diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index f5f1e4a3a9..ae69591b0f 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -134,6 +134,7 @@ module Chainweb.Utils , fromJuste , (???) , fromEitherM +, fromEithere , InternalInvariantViolation(..) -- ** Synchronous Exceptions @@ -191,6 +192,7 @@ module Chainweb.Utils -- * Strict Tuples , T2(..) , T3(..) +, T4(..) , sfst , ssnd , scurry @@ -209,7 +211,6 @@ module Chainweb.Utils , unsafeManager , unsafeManagerWithSettings , setManagerRequestTimeout -, defaultSupportedTlsSettings -- * SockAddr from network package , showIpv4 @@ -257,7 +258,6 @@ import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Lazy as BL import qualified Data.Csv as CSV import Data.Decimal -import Data.Default.Class (def) import Data.Functor.Of import Data.Hashable import qualified Data.HashMap.Strict as HM @@ -274,7 +274,7 @@ import qualified Data.Vector.Mutable as MV import Data.Word import GHC.Generics -import GHC.Stack (HasCallStack) +import GHC.Stack (HasCallStack, callStack, prettyCallStack) import GHC.TypeLits (KnownSymbol, symbolVal) import qualified Network.Connection as HTTP @@ -870,13 +870,24 @@ fromEitherM :: MonadThrow m => Exception e => Either e a -> m a fromEitherM = either throwM return {-# INLINE fromEitherM #-} +-- | Throw an exception if a value is a 'Left' result. +-- +fromEithere :: HasCallStack => Either e a -> a +fromEithere = either (error "Chainweb.Utils.fromJuste: Nothing") id +{-# INLINE fromEithere #-} + -- | An exeption to indicate an violation of an internal code invariants. -- Throwing this type of exception means that there is a bug in the code. -- -newtype InternalInvariantViolation = InternalInvariantViolation T.Text - deriving (Show) +data InternalInvariantViolation = HasCallStack => InternalInvariantViolation T.Text + +instance Show InternalInvariantViolation where + show (InternalInvariantViolation t) = "Invariant violation: " <> T.unpack t -instance Exception InternalInvariantViolation +instance Exception InternalInvariantViolation where + displayException (InternalInvariantViolation v) = + "Invariant violation: " <> T.unpack v + <> "\n" <> GHC.Stack.prettyCallStack callStack -- | Catch and handle exception that are not contained in 'SomeAsyncException'. -- @@ -1294,6 +1305,9 @@ instance Field2 (T3 a b c) (T3 a x c) b x where instance Field3 (T3 a b c) (T3 a b x) c x where _3 = lens (\(T3 _a _b c) -> c) (\(T3 a b _c) x -> T3 a b x) +data T4 a b c d = T4 !a !b !c !d + deriving (Show, Eq, Ord, Generic, NFData, Functor) + sfst :: T2 a b -> a sfst (T2 a _) = a {-# INLINE sfst #-} @@ -1351,9 +1365,6 @@ approximateThreadDelay d = withMVar threadDelayRng (approximately d) -- -- TODO unify with other HTTP managers -defaultSupportedTlsSettings :: HTTP.Supported -defaultSupportedTlsSettings = def - manager :: Int -> IO HTTP.Manager manager micros = HTTP.newManager $ setManagerRequestTimeout micros @@ -1363,7 +1374,7 @@ unsafeManager :: Int -> IO HTTP.Manager unsafeManager micros = HTTP.newTlsManagerWith $ setManagerRequestTimeout micros $ HTTP.mkManagerSettings - (HTTP.TLSSettingsSimple True True True defaultSupportedTlsSettings) + (HTTP.TLSSettingsSimple True True True HTTP.defaultSupported) Nothing unsafeManagerWithSettings @@ -1372,7 +1383,7 @@ unsafeManagerWithSettings unsafeManagerWithSettings settings = HTTP.newTlsManagerWith $ settings $ HTTP.mkManagerSettings - (HTTP.TLSSettingsSimple True True True defaultSupportedTlsSettings) + (HTTP.TLSSettingsSimple True True True HTTP.defaultSupported) Nothing setManagerRequestTimeout :: Int -> HTTP.ManagerSettings -> HTTP.ManagerSettings @@ -1441,7 +1452,7 @@ estimateBlockHeight rate dateStr curHeight = do -- | Parse UTC Time in the format "%y-%m-%dT%H:%M:%SZ" -- -parseUtcTime :: MonadThrow m => String -> m UTCTime +parseUtcTime :: (HasCallStack, MonadThrow m) => String -> m UTCTime parseUtcTime d = case parseTimeM False defaultTimeLocale fmt d of Nothing -> throwM $ InternalInvariantViolation $ "parseUtcTime: failed to parse utc date " <> sshow d @@ -1494,4 +1505,4 @@ unsafeHead msg = \case unsafeTail :: HasCallStack => String -> [a] -> [a] unsafeTail msg = \case _ : xs -> xs - [] -> error $ "unsafeTail: empty list: " <> msg \ No newline at end of file + [] -> error $ "unsafeTail: empty list: " <> msg diff --git a/src/Chainweb/VerifierPlugin.hs b/src/Chainweb/VerifierPlugin.hs index abf392297e..4399a5e066 100644 --- a/src/Chainweb/VerifierPlugin.hs +++ b/src/Chainweb/VerifierPlugin.hs @@ -8,16 +8,17 @@ {-# language TupleSections #-} {-# language TypeApplications #-} {-# language RankNTypes #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveAnyClass #-} module Chainweb.VerifierPlugin ( VerifierPlugin(..) - , VerifierError(..) , runVerifierPlugins , chargeGas ) where import Control.DeepSeq -import Control.Exception.Safe(Exception, throw, try, tryAny) +import Control.Exception.Safe(tryAny, handleAny) import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class @@ -30,15 +31,11 @@ import Data.Foldable import Data.Map.Strict(Map) import qualified Data.Map.Strict as Map import qualified Data.Map.Merge.Strict as Merge -import Data.Maybe import Data.Set(Set) import qualified Data.Set as Set import Data.STRef -import Data.Text(Text) import Pact.Types.Capability -import Pact.Types.ChainMeta -import Pact.Types.Command import Pact.Types.Gas import Pact.Types.PactValue import Pact.Types.Verifier @@ -47,11 +44,7 @@ import Chainweb.Version import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Utils - -newtype VerifierError = VerifierError - { getVerifierError :: Text } - deriving stock Show -instance Exception VerifierError +import Pact.Core.Errors (VerifierError(..)) newtype VerifierPlugin = VerifierPlugin @@ -82,32 +75,36 @@ runVerifierPlugins -> logger -> Map VerifierName VerifierPlugin -> Gas - -> Command (Payload PublicMeta ParsedCode) + -> [Verifier ParsedVerifierProof] -> IO (Either VerifierError Gas) -runVerifierPlugins chainContext logger allVerifiers gasRemaining tx = try $ do - gasRef <- stToIO $ newSTRef gasRemaining - either throw (\_ -> stToIO $ readSTRef gasRef) <=< runExceptT $ Merge.mergeA - -- verifier in command does not exist in list of all valid verifiers - (Merge.traverseMissing $ \(VerifierName vn) _ -> - throwError $ VerifierError ("verifier does not exist: " <> vn) - ) - -- valid verifier not used in command - Merge.dropMissing - (Merge.zipWithAMatched $ \(VerifierName vn) proofsAndCaps verifierPlugin -> - for_ proofsAndCaps $ \(ParsedVerifierProof proof, caps) -> do - verifierGasRemaining <- lift $ stToIO $ readSTRef gasRef - tryAny (hoist stToIO (runVerifierPlugin verifierPlugin chainContext proof caps gasRef)) >>= \case - Left ex -> do - liftIO $ logFunctionText logger Warn ("Uncaught exception in verifier: " <> sshow ex) - throwError $ VerifierError "Uncaught exception in verifier" - Right () -> return () - verifierDoneGasRemaining <- lift $ stToIO $ readSTRef gasRef - if verifierDoneGasRemaining > verifierGasRemaining - then throwError $ VerifierError ("Verifier attempted to charge negative gas: " <> vn) - else return () +runVerifierPlugins chainContext logger allVerifiers gasRemaining txVerifiers = + handleAny (\_ -> return $ Left $ VerifierError "unknown exception") $ do + gasRef <- stToIO $ newSTRef gasRemaining + maybeErr <- runExceptT $ Merge.mergeA + -- verifier in command does not exist in list of all valid verifiers + (Merge.traverseMissing $ \(VerifierName vn) _ -> + throwError $ VerifierError ("verifier does not exist: " <> vn) ) - verifiersInCommand - allVerifiers + -- valid verifier not used in command + Merge.dropMissing + (Merge.zipWithAMatched $ \(VerifierName vn) proofsAndCaps verifierPlugin -> + for_ proofsAndCaps $ \(ParsedVerifierProof proof, caps) -> do + verifierGasRemaining <- lift $ stToIO $ readSTRef gasRef + tryAny (hoist stToIO (runVerifierPlugin verifierPlugin chainContext proof caps gasRef)) >>= \case + Left ex -> do + liftIO $ logFunctionText logger Warn ("Uncaught exception in verifier: " <> sshow ex) + -- TODO: we should add the verifier name to the error, but to do so we have to fork + throwError $ VerifierError $ "Uncaught exception in verifier" + Right () -> return () + verifierDoneGasRemaining <- lift $ stToIO $ readSTRef gasRef + if verifierDoneGasRemaining > verifierGasRemaining + then throwError $ VerifierError ("Verifier attempted to charge negative gas: " <> vn) + else return () + ) + verifiersInCommand + allVerifiers + gasRemaining' <- stToIO $ readSTRef gasRef + return (gasRemaining' <$ maybeErr) where verifiersInCommand :: Map VerifierName [(ParsedVerifierProof, Set SigCapability)] verifiersInCommand = @@ -115,4 +112,4 @@ runVerifierPlugins chainContext logger allVerifiers gasRemaining tx = try $ do fmap (\Verifier {_verifierName = name, _verifierProof = proof, _verifierCaps = caps} -> (name, [(proof, Set.fromList caps)])) $ - fromMaybe [] $ _pVerifiers (_cmdPayload tx) + txVerifiers diff --git a/src/Chainweb/VerifierPlugin/Allow.hs b/src/Chainweb/VerifierPlugin/Allow.hs index bf92b20af5..24676750ca 100644 --- a/src/Chainweb/VerifierPlugin/Allow.hs +++ b/src/Chainweb/VerifierPlugin/Allow.hs @@ -13,6 +13,7 @@ import qualified Data.Text.Encoding as Text import Pact.Types.Capability import Pact.Types.Exp import Pact.Types.PactValue +import Pact.Core.Errors (VerifierError(..)) import Chainweb.VerifierPlugin diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Announcement.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Announcement.hs index 7aa200458e..fa60c09723 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Announcement.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Announcement.hs @@ -32,6 +32,7 @@ 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 diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs index e3ee78372a..4397c89cf4 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs @@ -44,6 +44,7 @@ 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) diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message/Before225.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message/Before225.hs index 23b1e4b76d..78f858859c 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message/Before225.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Message/Before225.hs @@ -38,6 +38,7 @@ 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 diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index dbf6d196c2..5bff5af741 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -16,6 +16,9 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE QuantifiedConstraints #-} -- | -- Module: Chainweb.Version @@ -48,8 +51,7 @@ module Chainweb.Version , decodeChainwebVersionCode , ChainwebVersionName(..) , ChainwebVersion(..) - , Upgrade(..) - , upgrade + , pact4Upgrade , VersionQuirks(..) , noQuirks , quirkGasFees @@ -76,6 +78,15 @@ module Chainweb.Version , genesisBlockHeight , genesisHeightAndGraph + , PactUpgrade(..) + , PactVersion(..) + , PactVersionT(..) + , ForBothPactVersions(..) + , ForSomePactVersion(..) + , pattern ForPact4 + , pattern ForPact5 + , forAnyPactVersion + -- * Typelevel ChainwebVersionName , ChainwebVersionT(..) , ChainwebVersionSymbol @@ -123,6 +134,7 @@ module Chainweb.Version -- ** Utilities for constructing Chainweb Version , indexByForkHeights , latestBehaviorAt + , onAllChains , domainAddr2PeerInfo -- * Internal. Don't use. Exported only for testing @@ -164,7 +176,8 @@ import Chainweb.Graph import Chainweb.HostAddress import Chainweb.MerkleUniverse import Chainweb.Payload -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 +import qualified Chainweb.Pact5.Transaction as Pact5 import Chainweb.Utils import Chainweb.Utils.Rule import Chainweb.Utils.Serialization @@ -195,6 +208,7 @@ data Fork | Pact4Coin3 | EnforceKeysetFormats | Pact42 + -- always add new forks at the end, not in the middle of the constructors. | CheckTxHash | Chainweb213Pact | Chainweb214Pact @@ -212,6 +226,7 @@ data Fork | Chainweb225Pact | Chainweb226Pact | Chainweb227Pact + | Pact5Fork -- 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) @@ -249,6 +264,7 @@ instance HasTextRepresentation Fork where toText Chainweb225Pact = "chainweb225Pact" toText Chainweb226Pact = "chainweb226Pact" toText Chainweb227Pact = "chainweb227Pact" + toText Pact5Fork = "pact5" fromText "slowEpoch" = return SlowEpoch fromText "vuln797Fix" = return Vuln797Fix @@ -282,6 +298,7 @@ instance HasTextRepresentation Fork where fromText "chainweb225Pact" = return Chainweb225Pact fromText "chainweb226Pact" = return Chainweb226Pact fromText "chainweb227Pact" = return Chainweb227Pact + fromText "pact5" = return Pact5Fork fromText t = throwM . TextFormatException $ "Unknown Chainweb fork: " <> t instance ToJSON Fork where @@ -324,35 +341,80 @@ instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ChainwebVer toMerkleNode = encodeMerkleInputNode encodeChainwebVersionCode fromMerkleNode = decodeMerkleInputNode decodeChainwebVersionCode +data PactVersion = Pact4 | Pact5 +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 +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) + -- The type of upgrades, which are sets of transactions to run at certain block -- heights during coinbase. --- -data Upgrade = Upgrade - { _upgradeTransactions :: [ChainwebTransaction] - , _legacyUpgradeIsPrecocious :: Bool + +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. - } - deriving stock (Generic, Eq) - deriving anyclass (NFData) + } -> PactUpgrade + Pact5Upgrade :: + { _pact5UpgradeTransactions :: [Pact5.Transaction] + } -> PactUpgrade -instance Show Upgrade where - show _ = "" +instance Eq PactUpgrade where + Pact4Upgrade txs precocious == Pact4Upgrade txs' precocious' = + txs == txs' && precocious == precocious' + Pact5Upgrade txs == Pact5Upgrade txs' = + txs == txs' + _ == _ = False -upgrade :: [ChainwebTransaction] -> Upgrade -upgrade txs = Upgrade txs False +instance Show PactUpgrade where + show Pact4Upgrade {} = "" + show Pact5Upgrade {} = "" + +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 -- 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 :: !(HashMap RequestKey Gas) - -- ^ Gas fee to charge at particular 'RequestKey's. - -- This should be 'MilliGas' once 'applyCmd' is refactored - -- to use 'MilliGas' instead of 'Gas'. - -- Note: only works for user txs in blocks right now. + -- ^ Note: only works for user txs in blocks right now. } deriving stock (Show, Eq, Ord, Generic) deriving anyclass (NFData) @@ -388,8 +450,8 @@ data ChainwebVersion -- ^ The block heights on each chain to apply behavioral changes. -- Interpretation of these is up to the functions in -- `Chainweb.Version.Guards`. - , _versionUpgrades :: ChainMap (HashMap BlockHeight Upgrade) - -- ^ The upgrade transactions to execute on each chain at certain block + , _versionUpgrades :: ChainMap (HashMap BlockHeight PactUpgrade) + -- ^ The Pact upgrade transactions to execute on each chain at certain block -- heights. , _versionBlockDelay :: BlockDelay -- ^ The Proof-of-Work `BlockDelay` for each `ChainwebVersion`. This is @@ -490,7 +552,7 @@ makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionDefaults makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionQuirks genesisBlockPayloadHash :: ChainwebVersion -> ChainId -> BlockPayloadHash -genesisBlockPayloadHash v cid = v ^?! versionGenesis . genesisBlockPayload . onChain cid . to _payloadWithOutputsPayloadHash +genesisBlockPayloadHash v cid = v ^?! versionGenesis . genesisBlockPayload . atChain cid . to _payloadWithOutputsPayloadHash instance HasTextRepresentation ChainwebVersionName where toText = getChainwebVersionName @@ -662,8 +724,8 @@ indexByForkHeights v = OnChains . foldl' go (HM.empty <$ HS.toMap (chainIds v)) newTxs = HM.fromList $ [ (cid, HM.singleton forkHeight upg) | cid <- HM.keys acc - , Just upg <- [txsPerChain ^? onChain cid] - , ForkAtBlockHeight forkHeight <- [v ^?! versionForks . at fork . _Just . onChain cid] + , Just upg <- [txsPerChain ^? atChain cid] + , ForkAtBlockHeight forkHeight <- [v ^?! versionForks . at fork . _Just . atChain cid] , forkHeight /= maxBound ] @@ -677,3 +739,11 @@ latestBehaviorAt v = foldlOf' behaviorChanges max 0 v + 1 , versionUpgrades . folded . ifolded . asIndex , versionGraphs . to ruleHead . _1 ] + +-- | 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 <$> + HM.traverseWithKey + (\cid () -> f cid) + (HS.toMap (chainIds v)) diff --git a/src/Chainweb/Version/Development.hs b/src/Chainweb/Version/Development.hs index 6867a0c650..363804b36b 100644 --- a/src/Chainweb/Version/Development.hs +++ b/src/Chainweb/Version/Development.hs @@ -31,7 +31,11 @@ devnet :: ChainwebVersion devnet = ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000002 , _versionName = ChainwebVersionName "development" - , _versionForks = tabulateHashMap $ \_ -> AllChains ForkAtGenesis + , _versionForks = tabulateHashMap $ \case + -- TODO: for now, Pact 5 is never enabled on devnet. + -- this will change as it stabilizes. + Pact5Fork -> AllChains ForkNever + _ -> AllChains ForkAtGenesis , _versionUpgrades = AllChains mempty , _versionGraphs = Bottom (minBound, twentyChainGraph) , _versionBlockDelay = BlockDelay 30_000_000 diff --git a/src/Chainweb/Version/Guards.hs b/src/Chainweb/Version/Guards.hs index acc8f05c47..610f86b333 100644 --- a/src/Chainweb/Version/Guards.hs +++ b/src/Chainweb/Version/Guards.hs @@ -47,8 +47,9 @@ module Chainweb.Version.Guards , chainweb224Pact , chainweb225Pact , chainweb226Pact + , pact5 , pact44NewTrans - , pactParserVersion + , pact4ParserVersion , maxBlockGasLimit , validPPKSchemes , isWebAuthnPrefixLegal @@ -69,12 +70,12 @@ import Pact.Types.Scheme (PPKScheme(ED25519, WebAuthn)) import Chainweb.BlockHeight import Chainweb.ChainId -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Version import Chainweb.Utils.Rule getForkHeight :: Fork -> ChainwebVersion -> ChainId -> ForkHeight -getForkHeight fork v cid = v ^?! versionForks . at fork . _Just . onChain cid +getForkHeight fork v cid = v ^?! versionForks . at fork . _Just . atChain cid checkFork :: (BlockHeight -> ForkHeight -> Bool) @@ -219,6 +220,9 @@ pact4Coin3 = checkFork after Pact4Coin3 pact42 :: ChainwebVersion -> ChainId -> BlockHeight -> Bool pact42 = checkFork atOrAfter Pact42 +pact5 :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +pact5 = checkFork atOrAfter Pact5Fork + chainweb213Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool chainweb213Pact = checkFork atOrAfter Chainweb213Pact @@ -261,10 +265,10 @@ chainweb225Pact = checkFork atOrAfter Chainweb225Pact chainweb226Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool chainweb226Pact = checkFork before Chainweb226Pact -pactParserVersion :: ChainwebVersion -> ChainId -> BlockHeight -> PactParserVersion -pactParserVersion v cid bh - | chainweb213Pact v cid bh = PactParserChainweb213 - | otherwise = PactParserGenesis +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 @@ -279,11 +283,11 @@ validPPKSchemes v cid bh = then [ED25519, WebAuthn] else [ED25519] -isWebAuthnPrefixLegal :: ChainwebVersion -> ChainId -> BlockHeight -> IsWebAuthnPrefixLegal +isWebAuthnPrefixLegal :: ChainwebVersion -> ChainId -> BlockHeight -> Pact4.IsWebAuthnPrefixLegal isWebAuthnPrefixLegal v cid bh = if chainweb222Pact v cid bh - then WebAuthnPrefixLegal - else WebAuthnPrefixIllegal + then Pact4.WebAuthnPrefixLegal + else Pact4.WebAuthnPrefixIllegal validKeyFormats :: ChainwebVersion -> ChainId -> BlockHeight -> [PublicKeyText -> Bool] validKeyFormats v cid bh = diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index 6aae0d9556..ebee58537d 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -54,7 +54,7 @@ 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 testnet +-- from 2020-07-09. This value should be double checked after the testnet04 -- transition and before the release of chainweb node version 2.1. -- -- @ @@ -135,6 +135,7 @@ mainnet = ChainwebVersion 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 + Pact5Fork -> AllChains ForkNever 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 @@ -160,7 +161,7 @@ mainnet = ChainwebVersion , _versionWindow = WindowWidth 120 , _versionHeaderBaseSizeBytes = 318 - 110 , _versionMaxBlockGasLimit = - (succ $ mainnet ^?! versionForks . at Chainweb216Pact . _Just . onChain (unsafeChainId 0) . _ForkAtBlockHeight, Just 180_000) `Above` + (succ $ mainnet ^?! versionForks . at Chainweb216Pact . _Just . atChain (unsafeChainId 0) . _ForkAtBlockHeight, Just 180_000) `Above` Bottom (minBound, Nothing) , _versionBootstraps = domainAddr2PeerInfo mainnetBootstrapHosts , _versionGenesis = VersionGenesis @@ -170,40 +171,41 @@ mainnet = ChainwebVersion ] , _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 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]] ] } , _versionUpgrades = chainZip HM.union (indexByForkHeights mainnet [ (CoinV2, onChains - [ (unsafeChainId 0, upgrade MN0.transactions) - , (unsafeChainId 1, upgrade MN1.transactions) - , (unsafeChainId 2, upgrade MN2.transactions) - , (unsafeChainId 3, upgrade MN3.transactions) - , (unsafeChainId 4, upgrade MN4.transactions) - , (unsafeChainId 5, upgrade MN5.transactions) - , (unsafeChainId 6, upgrade MN6.transactions) - , (unsafeChainId 7, upgrade MN7.transactions) - , (unsafeChainId 8, upgrade MN8.transactions) - , (unsafeChainId 9, upgrade MN9.transactions) + [ (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 $ Upgrade CoinV3.transactions True) - , (Chainweb214Pact, AllChains $ Upgrade CoinV4.transactions True) - , (Chainweb215Pact, AllChains $ Upgrade CoinV5.transactions True) - , (Chainweb223Pact, AllChains $ upgrade CoinV6.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 (upgrade MNKAD.transactions))]) + (onChains [(unsafeChainId 0, HM.singleton to20ChainsMainnet (pact4Upgrade MNKAD.transactions))]) , _versionCheats = VersionCheats { _disablePow = False , _fakeFirstEpochStart = False diff --git a/src/Chainweb/Version/Pact5Development.hs b/src/Chainweb/Version/Pact5Development.hs new file mode 100644 index 0000000000..131ff247d8 --- /dev/null +++ b/src/Chainweb/Version/Pact5Development.hs @@ -0,0 +1,67 @@ +{-# language LambdaCase #-} +{-# language ImportQualifiedPost #-} +{-# language NumericUnderscores #-} +{-# language OverloadedStrings #-} +{-# language PatternSynonyms #-} +{-# language QuasiQuotes #-} +{-# language ViewPatterns #-} + +module Chainweb.Version.Pact5Development(pact5Devnet, pattern Pact5Development) where + +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 Data.Set qualified as Set +import Pact.Types.Verifier +import qualified Chainweb.BlockHeader.Genesis.Pact5Development0Payload as DN0 +import qualified Chainweb.BlockHeader.Genesis.Pact5Development1to19Payload as DNN + +pattern Pact5Development :: ChainwebVersion +pattern Pact5Development <- ((== pact5Devnet) -> True) where + Pact5Development = pact5Devnet + +pact5Devnet :: ChainwebVersion +pact5Devnet = ChainwebVersion + { _versionCode = ChainwebVersionCode 0x00000008 + , _versionName = ChainwebVersionName "pact5-development" + , _versionForks = tabulateHashMap $ \case + _ -> 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` 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]] + ] + } + + -- 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 + } diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index c671106df3..19e0576a36 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -40,7 +40,7 @@ pattern RecapDevelopment <- ((== recapDevnet) -> True) where recapDevnet :: ChainwebVersion recapDevnet = ChainwebVersion - { _versionCode = ChainwebVersionCode 0x00000001 + { _versionCode = ChainwebVersionCode 0x0000_0001 , _versionName = ChainwebVersionName "recap-development" , _versionForks = tabulateHashMap $ \case @@ -76,15 +76,16 @@ recapDevnet = ChainwebVersion Chainweb225Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 620 Chainweb226Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 630 Chainweb227Pact -> AllChains ForkNever + Pact5Fork -> AllChains ForkNever , _versionUpgrades = foldr (chainZip HM.union) (AllChains mempty) [ indexByForkHeights recapDevnet - [ (CoinV2, onChains [(unsafeChainId i, upgrade RecapDevnet.transactions) | i <- [0..9]]) - , (Pact4Coin3, AllChains (Upgrade CoinV3.transactions True)) - , (Chainweb214Pact, AllChains (Upgrade CoinV4.transactions True)) - , (Chainweb215Pact, AllChains (Upgrade CoinV5.transactions True)) + [ (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 (upgrade MNKAD.transactions))] + , onChains [(unsafeChainId 0, HM.singleton to20ChainsHeight (pact4Upgrade MNKAD.transactions))] ] , _versionGraphs = diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index bd9e07b8a5..2f11d5ea66 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -1,4 +1,6 @@ {-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE MonoLocalBinds #-} +{-# LANGUAGE NamedFieldPuns #-} -- | -- Module: Chainweb.Version.Registry -- Copyright: Copyright © 2023 Kadena LLC. @@ -44,16 +46,19 @@ import GHC.Stack import Chainweb.Version import Chainweb.Version.Development +import Chainweb.Version.Pact5Development import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet -import Chainweb.Version.Testnet +import Chainweb.Version.Testnet04 +import Chainweb.Version.Testnet05 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, testnet]] + newIORef $ HM.fromList [(_versionCode v, v) | v <- [mainnet, testnet04, testnet05]] -- | Register a version into our registry by code, ensuring it contains no -- errors and there are no others registered with that code. @@ -71,14 +76,15 @@ registerVersion v = do -- | 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, testnet]) - then error "You cannot unregister mainnet or testnet versions" + if elem (_versionCode v) (_versionCode <$> [mainnet, testnet04, testnet05]) + then error "You cannot unregister mainnet, testnet04, or testnet05 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 (AllChains _) = True hasAllChains (OnChains m) = HS.fromMap (void m) == chainIds v errors = concat @@ -96,11 +102,18 @@ validateVersion v = do , hasAllChains (_genesisBlockTarget $ _versionGenesis v) , hasAllChains (_genesisTime $ _versionGenesis v) ])] - , [ "validateVersion: some upgrade has no transactions" - | any (any (\upg -> null (_upgradeTransactions upg))) (_versionUpgrades v) ] + , [ "validateVersion: some pact upgrade has no transactions" + | any (any isUpgradeEmpty) (_versionUpgrades v) ] + -- TODO: check that pact 4/5 upgrades are only enabled when pact 4/5 is enabled ] 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 -- | Look up a version in the registry by code. lookupVersionByCode :: HasCallStack => ChainwebVersionCode -> ChainwebVersion @@ -109,27 +122,35 @@ lookupVersionByCode code -- 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 testnet = testnet + | code == _versionCode testnet04 = testnet04 + | code == _versionCode testnet05 = testnet05 | 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 + 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" - | code == _versionCode devnet = "devnet version used but not registered, remember to do so after it's configured" - | otherwise = "version not registered with code " <> show code <> ", have you seen Chainweb.Test.TestVersions.testVersions?" + | 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 pact5Devnet = "Pact 5 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 testnet = testnet + | name == _versionName testnet04 = testnet04 + | name == _versionName testnet05 = testnet05 | otherwise = lookupVersion & versionName .~ name where lookupVersion = unsafeDupablePerformIO $ do @@ -138,6 +159,8 @@ lookupVersionByName name 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 pact5Devnet = "Pact 5 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 @@ -146,15 +169,12 @@ fabricateVersionWithName name = -- | Versions known to us by name. knownVersions :: [ChainwebVersion] -knownVersions = [mainnet, testnet, recapDevnet, devnet] +knownVersions = [mainnet, testnet04, testnet05, recapDevnet, devnet, pact5Devnet] -- | Look up a known version by name, usually with `m` instantiated to some -- configuration parser monad. 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: try development, mainnet01, testnet04, or testnet05" Just v -> return v - -instance HasChainwebVersion ChainwebVersionCode where - _chainwebVersion = lookupVersionByCode diff --git a/src/Chainweb/Version/Testnet.hs b/src/Chainweb/Version/Testnet04.hs similarity index 83% rename from src/Chainweb/Version/Testnet.hs rename to src/Chainweb/Version/Testnet04.hs index 7cf11ab8bf..5166448935 100644 --- a/src/Chainweb/Version/Testnet.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -5,7 +5,7 @@ {-# language QuasiQuotes #-} {-# language ViewPatterns #-} -module Chainweb.Version.Testnet(testnet, pattern Testnet04) where +module Chainweb.Version.Testnet04(testnet04, pattern Testnet04) where import Control.Lens import qualified Data.HashMap.Strict as HM @@ -40,10 +40,10 @@ 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.Testnet0Payload as PN0 -import qualified Chainweb.BlockHeader.Genesis.Testnet1to19Payload as PNN +import qualified Chainweb.BlockHeader.Genesis.Testnet040Payload as PN0 +import qualified Chainweb.BlockHeader.Genesis.Testnet041to19Payload as PNN --- | Initial hash target for testnet 20-chain transition. Based on the following +-- | 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 -- 5 - 50 GPUs in the system for a smooth transition. @@ -89,11 +89,11 @@ to20ChainsTestnet :: BlockHeight to20ChainsTestnet = 332_604 -- 2020-07-28 16:00:00 pattern Testnet04 :: ChainwebVersion -pattern Testnet04 <- ((== testnet) -> True) where - Testnet04 = testnet +pattern Testnet04 <- ((== testnet04) -> True) where + Testnet04 = testnet04 -testnet :: ChainwebVersion -testnet = ChainwebVersion +testnet04 :: ChainwebVersion +testnet04 = ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000007 , _versionName = ChainwebVersionName "testnet04" , _versionForks = tabulateHashMap $ \case @@ -132,6 +132,7 @@ testnet = ChainwebVersion 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 Chainweb227Pact -> AllChains ForkNever + Pact5Fork -> AllChains ForkNever , _versionGraphs = (to20ChainsTestnet, twentyChainGraph) `Above` @@ -140,9 +141,9 @@ testnet = ChainwebVersion , _versionWindow = WindowWidth 120 , _versionHeaderBaseSizeBytes = 318 - 110 , _versionMaxBlockGasLimit = - (succ $ testnet ^?! versionForks . at Chainweb216Pact . _Just . onChain (unsafeChainId 0) . _ForkAtBlockHeight, Just 180_000) `Above` + (succ $ testnet04 ^?! versionForks . at Chainweb216Pact . _Just . atChain (unsafeChainId 0) . _ForkAtBlockHeight, Just 180_000) `Above` Bottom (minBound, Nothing) - , _versionBootstraps = domainAddr2PeerInfo testnetBootstrapHosts + , _versionBootstraps = domainAddr2PeerInfo testnet04BootstrapHosts , _versionGenesis = VersionGenesis { _genesisBlockTarget = OnChains $ HM.fromList $ concat [ [(unsafeChainId i, maxTarget) | i <- [0..9]] @@ -150,31 +151,30 @@ testnet = ChainwebVersion ] , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] , _genesisBlockPayload = OnChains $ HM.fromList $ concat - [ [ (unsafeChainId 0, PN0.payloadBlock) - ] + [ [(unsafeChainId 0, PN0.payloadBlock)] , [(unsafeChainId i, PNN.payloadBlock) | i <- [1..19]] ] } , _versionUpgrades = chainZip HM.union - (indexByForkHeights testnet + (indexByForkHeights testnet04 [ (CoinV2, onChains $ - [ (unsafeChainId 0, upgrade MN0.transactions) - , (unsafeChainId 1, upgrade MN1.transactions) - , (unsafeChainId 2, upgrade MN2.transactions) - , (unsafeChainId 3, upgrade MN3.transactions) - , (unsafeChainId 4, upgrade MN4.transactions) - , (unsafeChainId 5, upgrade MN5.transactions) - , (unsafeChainId 6, upgrade MN6.transactions) - , (unsafeChainId 7, upgrade MN7.transactions) - , (unsafeChainId 8, upgrade MN8.transactions) - , (unsafeChainId 9, upgrade MN9.transactions) + [ (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 (Upgrade CoinV3.transactions True)) - , (Chainweb214Pact, AllChains (Upgrade CoinV4.transactions True)) - , (Chainweb215Pact, AllChains (Upgrade CoinV5.transactions True)) - , (Chainweb223Pact, AllChains $ upgrade CoinV6.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 (upgrade MNKAD.transactions))]) + (onChains [(unsafeChainId 0, HM.singleton to20ChainsTestnet (pact4Upgrade MNKAD.transactions))]) , _versionCheats = VersionCheats { _disablePow = False , _fakeFirstEpochStart = False diff --git a/src/Chainweb/Version/Testnet05.hs b/src/Chainweb/Version/Testnet05.hs new file mode 100644 index 0000000000..364bcadc56 --- /dev/null +++ b/src/Chainweb/Version/Testnet05.hs @@ -0,0 +1,106 @@ +{-# language LambdaCase #-} +{-# language NumericUnderscores #-} +{-# language OverloadedStrings #-} +{-# language PatternSynonyms #-} +{-# language QuasiQuotes #-} +{-# language ViewPatterns #-} + +module Chainweb.Version.Testnet05(testnet05, pattern Testnet05) where + +import qualified Data.HashMap.Strict as HM +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 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 + +testnet05 :: ChainwebVersion +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 + Chainweb227Pact -> AllChains ForkNever + Pact5Fork -> AllChains ForkAtGenesis + + , _versionGraphs = + Bottom (minBound, twentyChainGraph) + , _versionBlockDelay = BlockDelay 30_000_000 + , _versionWindow = WindowWidth 120 + , _versionHeaderBaseSizeBytes = 318 - 110 + , _versionMaxBlockGasLimit = + Bottom (minBound, Just 180_000) + , _versionBootstraps = domainAddr2PeerInfo testnet05BootstrapHosts + , _versionGenesis = VersionGenesis + { _genesisBlockTarget = OnChains $ HM.fromList $ concat + [ [(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]] + ] + } + , _versionUpgrades = AllChains mempty + , _versionCheats = VersionCheats + { _disablePow = False + , _fakeFirstEpochStart = False + , _disablePact = False + } + , _versionDefaults = VersionDefaults + { _disablePeerValidation = False + , _disableMempoolSync = False + } + , _versionVerifierPluginNames = AllChains $ + Bottom (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) + , _versionQuirks = VersionQuirks + { _quirkGasFees = mempty + } + , _versionServiceDate = Nothing + } diff --git a/src/Chainweb/Version/Utils.hs b/src/Chainweb/Version/Utils.hs index 32674460b9..c890d44467 100644 --- a/src/Chainweb/Version/Utils.hs +++ b/src/Chainweb/Version/Utils.hs @@ -461,8 +461,12 @@ verifiersAt :: ChainwebVersion -> ChainId -> BlockHeight -> Map VerifierName Ver verifiersAt v cid bh = M.restrictKeys allVerifierPlugins activeVerifierNames where - activeVerifierNames = - snd $ ruleZipperHere $ snd $ ruleSeek (\h _ -> bh >= h) $ _versionVerifierPluginNames v ^?! onChain cid + activeVerifierNames + = snd + $ ruleZipperHere + $ snd + $ ruleSeek (\h _ -> bh >= h) + $ _versionVerifierPluginNames v ^?! 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 0ad9bc06b8..121a224338 100644 --- a/src/Chainweb/WebBlockHeaderDB.hs +++ b/src/Chainweb/WebBlockHeaderDB.hs @@ -258,7 +258,7 @@ checkBlockHeaderGraph b = void where graph | isGenesisBlockHeader b = _chainGraph b - | otherwise = chainGraphAt (view blockChainwebVersion b) (view blockHeight b - 1) + | otherwise = chainGraphAt (_chainwebVersion b) (view blockHeight b - 1) {-# INLINE checkBlockHeaderGraph #-} -- | Given a 'WebBlockHeaderDb' @db@, @checkBlockAdjacentParents h@ checks that @@ -270,4 +270,3 @@ checkBlockAdjacentParents -> IO () checkBlockAdjacentParents db = void . blockAdjacentParentHeaders db {-# INLINE checkBlockAdjacentParents #-} - diff --git a/src/Chainweb/WebPactExecutionService.hs b/src/Chainweb/WebPactExecutionService.hs index 93f5abb4f4..7fc3cc14f4 100644 --- a/src/Chainweb/WebPactExecutionService.hs +++ b/src/Chainweb/WebPactExecutionService.hs @@ -2,6 +2,7 @@ {-# LANGUAGE ImplicitParams #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} module Chainweb.WebPactExecutionService ( WebPactExecutionService(..) @@ -14,14 +15,14 @@ module Chainweb.WebPactExecutionService , emptyPactExecutionService , NewBlock(..) , newBlockToPayloadWithOutputs - , newBlockParentHeader + , newBlockParent ) where +import Control.Lens import Control.Monad.Catch import qualified Data.HashMap.Strict as HM import Data.Vector (Vector) -import qualified Data.Vector as V import GHC.Stack @@ -35,33 +36,41 @@ import Chainweb.Mempool.Mempool (InsertError) import Chainweb.Miner.Pact import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Pact.Utils import Chainweb.Payload -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -import Pact.Types.Hash -import Pact.Types.Persistence (RowKey, TxLog, Domain) -import Pact.Types.RowData (RowData) +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) -- -------------------------------------------------------------------------- -- -- PactExecutionService data NewBlock - = NewBlockInProgress !BlockInProgress + = NewBlockInProgress !(ForSomePactVersion BlockInProgress) | NewBlockPayload !ParentHeader !PayloadWithOutputs deriving Show newBlockToPayloadWithOutputs :: NewBlock -> PayloadWithOutputs newBlockToPayloadWithOutputs (NewBlockInProgress bip) - = blockInProgressToPayloadWithOutputs bip + = forAnyPactVersion finalizeBlock bip newBlockToPayloadWithOutputs (NewBlockPayload _ pwo) = pwo -newBlockParentHeader :: NewBlock -> ParentHeader -newBlockParentHeader (NewBlockInProgress bip) = _blockInProgressParentHeader bip -newBlockParentHeader (NewBlockPayload ph _) = ph +newBlockParent :: NewBlock -> (BlockHash, BlockHeight, BlockCreationTime) +newBlockParent (NewBlockInProgress (ForSomePactVersion _ bip)) = blockInProgressParent bip +newBlockParent (NewBlockPayload (ParentHeader ph) _) = + (view blockHash ph, view blockHeight ph, view blockCreationTime ph) -- | 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 @@ -72,7 +81,7 @@ data PactExecutionService = PactExecutionService CheckablePayload -> IO PayloadWithOutputs ) - -- ^ Validate block payload data by running through pact service. + -- ^ Validate block payload data by running through pact service. , _pactNewBlock :: !( ChainId -> Miner -> @@ -81,52 +90,53 @@ data PactExecutionService = PactExecutionService IO (Historical NewBlock) ) , _pactContinueBlock :: !( + forall pv. ChainId -> - BlockInProgress -> - IO (Historical BlockInProgress) + BlockInProgress pv -> + IO (Historical (BlockInProgress pv)) ) - -- ^ Request a new block to be formed using mempool + -- ^ Request a new block to be formed using mempool , _pactLocal :: !( Maybe LocalPreflightSimulation -> Maybe LocalSignatureVerification -> Maybe RewindDepth -> - ChainwebTransaction -> + Pact4.UnparsedTransaction -> IO LocalResult) - -- ^ Directly execute a single transaction in "local" mode (all DB interactions rolled back). - -- Corresponds to `local` HTTP endpoint. + -- ^ 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 PactHash + -> Vector ShortByteString -- txs to lookup - -> IO (HM.HashMap PactHash (T2 BlockHeight BlockHash)) + -> IO (HM.HashMap ShortByteString (T2 BlockHeight BlockHash)) ) , _pactReadOnlyReplay :: !( BlockHeader -> Maybe BlockHeader -> IO () - ) - -- ^ Lookup pact hashes as of a block header to detect duplicates + ) + -- ^ Lookup pact hashes as of a block header to detect duplicates , _pactPreInsertCheck :: !( ChainId - -> Vector ChainwebTransaction - -> IO (Vector (Either InsertError ()))) - -- ^ Run speculative checks to find bad transactions (ie gas buy failures, etc) + -> 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 -> - Domain RowKey RowData -> + Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info -> IO (Historical BlockTxHistory) ) - -- ^ Obtain all transaction history in block for specified table/domain. + -- ^ Obtain all transaction history in block for specified table/domain. , _pactHistoricalLookup :: !( BlockHeader -> - Domain RowKey RowData -> - RowKey -> - IO (Historical (Maybe (TxLog RowData))) + 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. + -- ^ Obtain latest entry at or before the given block for specified table/domain and row key. , _pactSyncToBlock :: !( BlockHeader -> IO () @@ -153,9 +163,9 @@ _webPactNewBlock = _pactNewBlock . _webPactExecutionService _webPactContinueBlock :: WebPactExecutionService -> ChainId - -> BlockInProgress - -> IO (Historical BlockInProgress) -_webPactContinueBlock = _pactContinueBlock . _webPactExecutionService + -> BlockInProgress pv + -> IO (Historical (BlockInProgress pv)) +_webPactContinueBlock w cid bip = _pactContinueBlock (_webPactExecutionService w) cid bip {-# INLINE _webPactContinueBlock #-} _webPactValidateBlock @@ -229,7 +239,7 @@ emptyPactExecutionService = PactExecutionService , _pactContinueBlock = \_ _ -> throwM (userError "emptyPactExecutionService: attempted `continueBlock` call") , _pactLocal = \_ _ _ _ -> throwM (userError "emptyPactExecutionService: attempted `local` call") , _pactLookup = \_ _ _ -> return $! HM.empty - , _pactPreInsertCheck = \_ txs -> return $ V.map (const (Right ())) txs + , _pactPreInsertCheck = \_ txs -> return $ Nothing <$ txs , _pactBlockTxHistory = \_ _ -> error "Chainweb.WebPactExecutionService.emptyPactExecutionService: pactBlockTxHistory unsupported" , _pactHistoricalLookup = \_ _ _ -> error "Chainweb.WebPactExecutionService.emptyPactExecutionService: pactHistoryLookup unsupported" , _pactSyncToBlock = \_ -> return () diff --git a/src/P2P/BootstrapNodes.hs b/src/P2P/BootstrapNodes.hs index 91103f1760..c6e015969f 100644 --- a/src/P2P/BootstrapNodes.hs +++ b/src/P2P/BootstrapNodes.hs @@ -12,7 +12,8 @@ -- module P2P.BootstrapNodes ( mainnetBootstrapHosts -, testnetBootstrapHosts +, testnet04BootstrapHosts +, testnet05BootstrapHosts ) where -- internal modules @@ -45,7 +46,7 @@ mainnetBootstrapHosts = map unsafeHostAddressFromText ] -- -------------------------------------------------------------------------- -- --- | Testnet bootstrap nodes. +-- | Testnet04 bootstrap nodes. -- -- Nodes in this list need a public DNS name and a corresponding TLS -- certificate. Operators of the nodes are expected to guarantee long term @@ -53,8 +54,8 @@ mainnetBootstrapHosts = map unsafeHostAddressFromText -- -- Please make a pull request, if you like to see your node being included here. -- -testnetBootstrapHosts :: [HostAddress] -testnetBootstrapHosts = map unsafeHostAddressFromText +testnet04BootstrapHosts :: [HostAddress] +testnet04BootstrapHosts = map unsafeHostAddressFromText [ "us1.testnet.chainweb.com:443" , "us2.testnet.chainweb.com:443" , "eu1.testnet.chainweb.com:443" @@ -63,3 +64,21 @@ testnetBootstrapHosts = map unsafeHostAddressFromText , "ap2.testnet.chainweb.com:443" ] +-- -------------------------------------------------------------------------- -- +-- | Testnet05 bootstrap nodes. +-- +-- Nodes in this list need a public DNS name and a corresponding TLS +-- certificate. Operators of the nodes are expected to guarantee long term +-- availability of the nodes. +-- +-- Please make a pull request, if you like to see your node being included here. +-- +testnet05BootstrapHosts :: [HostAddress] +testnet05BootstrapHosts = map unsafeHostAddressFromText + [ "us1.testnet05.chainweb.com:443" + , "us2.testnet05.chainweb.com:443" + -- , "eu1.testnet05.chainweb.com:443" + -- , "eu2.testnet05.chainweb.com:443" + -- , "ap1.testnet05.chainweb.com:443" + -- , "ap2.testnet05.chainweb.com:443" + ] diff --git a/src/P2P/Node.hs b/src/P2P/Node.hs index a2f39af46d..078c5d4200 100644 --- a/src/P2P/Node.hs +++ b/src/P2P/Node.hs @@ -513,18 +513,20 @@ findNextPeer conf node = do candidates <- awaitCandidates -- random circular shift of a set - let shift i = uncurry (++) + let shift :: Int -> [a] -> [a] + shift i = uncurry (++) . swap . splitAt i + let shiftR :: [a] -> IO [a] shiftR s = do i <- nodeRandomR node (0, max 1 (length s) - 1) return $ shift i s let (p0, p1) = L.partition (\p -> _peerEntryActiveSessionCount p > 0 && _peerEntrySuccessiveFailures p <= 1) candidates - -- this ix expensive but lazy and only forced if p0 is empty - p2 = L.groupBy ((==) `on` _peerEntrySuccessiveFailures) p1 + -- 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 diff --git a/src/Utils/Logging/Trace.hs b/src/Utils/Logging/Trace.hs index 51b7d24fac..d49018eec6 100644 --- a/src/Utils/Logging/Trace.hs +++ b/src/Utils/Logging/Trace.hs @@ -81,21 +81,21 @@ trace -> m a -> m a trace logg label param weight a = - trace' logg label param (const weight) a + trace' logg label (const param) (const weight) a trace' :: MonadIO m => ToJSON param => (LogLevel -> JsonLog Trace -> IO ()) -> T.Text - -> param + -> (a -> param) -> (a -> Int) -> m a -> m a -trace' logg label param calcWeight a = do +trace' logg label calcParam calcWeight a = do (!r, t) <- stopWatch a liftIO $ logg Info $ JsonLog $ Trace label - (toJSON param) + (toJSON (calcParam r)) (calcWeight r) (fromIntegral $ toNanoSecs t `div` 1000) return r diff --git a/test/PactTests.hs b/test/PactTests.hs index 5d94111b4d..1c2ee3b918 100644 --- a/test/PactTests.hs +++ b/test/PactTests.hs @@ -17,12 +17,12 @@ module Main import Test.Tasty import qualified Chainweb.Test.Pact -import qualified Chainweb.Test.Pact.PactService +import qualified Chainweb.Test.Pact4.PactService main :: IO () main = do - pactTests <- Chainweb.Test.Pact.tests - pactServiceTests <- Chainweb.Test.Pact.PactService.tests + pactTests <- Chainweb.Test.Pact4.tests + pactServiceTests <- Chainweb.Test.Pact4.PactService.tests defaultMain $ testGroup "Chainweb-Pact Unit Tests" [ pactTests , pactServiceTests ] diff --git a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs index ccdaa39ce7..e9602283b5 100644 --- a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs +++ b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs @@ -14,15 +14,20 @@ module Chainweb.Test.Cut.TestBlockDb , mkTestBlockDb , addTestBlockDb , getParentTestBlockDb + , getParentBlockTestBlockDb + , getCutTestBlockDb + , setCutTestBlockDb , getBlockHeaderDb -- convenience export , RocksDbTable ) where import Control.Concurrent.MVar +import Control.Lens import Control.Monad.Catch import qualified Data.HashMap.Strict as HM +import Chainweb.Block import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.ChainId @@ -38,6 +43,7 @@ import Chainweb.WebBlockHeaderDB import Chainweb.Storage.Table.RocksDB import Chainweb.BlockHeight +import Control.Monad data TestBlockDb = TestBlockDb { _bdbWebBlockHeaderDb :: WebBlockHeaderDb @@ -103,6 +109,22 @@ getParentTestBlockDb (TestBlockDb _ _ cmv) cid = do fromMaybeM (userError $ "Internal error, parent not found for cid " ++ show cid) $ HM.lookup cid $ _cutMap c +-- | Get header for chain on current cut. +getParentBlockTestBlockDb :: TestBlockDb -> ChainId -> IO Block +getParentBlockTestBlockDb tdb cid = do + bh <- getParentTestBlockDb tdb cid + pwo <- fromJuste <$> lookupPayloadWithHeight (_bdbPayloadDb tdb) (Just $ view blockHeight bh) (view blockPayloadHash bh) + return Block + { _blockHeader = bh + , _blockPayloadWithOutputs = pwo + } + +getCutTestBlockDb :: TestBlockDb -> IO Cut +getCutTestBlockDb (TestBlockDb _ _ cmv) = readMVar cmv + +setCutTestBlockDb :: TestBlockDb -> Cut -> IO () +setCutTestBlockDb (TestBlockDb _ _ cmv) c = void $ swapMVar cmv c + -- | Convenience accessor getBlockHeaderDb :: MonadThrow m => ChainId -> TestBlockDb -> m BlockHeaderDb getBlockHeaderDb cid (TestBlockDb wdb _ _) = diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index a0439ea50e..d825d3f974 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -105,7 +105,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.Test.P2P.Peer.BootstrapConfig -import Chainweb.Test.Pact.Utils (sigmaCompact) +import Chainweb.Test.Pact4.Utils (sigmaCompact) import Chainweb.Test.Utils import Chainweb.Time (Seconds(..)) import Chainweb.Utils diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index 41b6afd215..e223ee6f03 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -129,7 +129,7 @@ import Chainweb.Miner.Config import Chainweb.Miner.Pact import Chainweb.NodeVersion import Chainweb.Pact.RestAPI.SPV -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.PowHash import Chainweb.RestAPI.NetworkID @@ -151,7 +151,7 @@ import Chainweb.Version import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.Registry -import Chainweb.Version.Testnet +import Chainweb.Version.Testnet04 import Chainweb.Version.Utils import Data.Singletons diff --git a/test/lib/Chainweb/Test/Pact/Utils.hs b/test/lib/Chainweb/Test/Pact4/Utils.hs similarity index 89% rename from test/lib/Chainweb/Test/Pact/Utils.hs rename to test/lib/Chainweb/Test/Pact4/Utils.hs index 55a66312f8..4ce86ef50b 100644 --- a/test/lib/Chainweb/Test/Pact/Utils.hs +++ b/test/lib/Chainweb/Test/Pact4/Utils.hs @@ -16,17 +16,18 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE DataKinds #-} {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} -- | --- Module: Chainweb.Test.Pact.Utils +-- 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.Pact.Utils +module Chainweb.Test.Pact4.Utils ( -- * Test key data SimpleKeyPair , sender00 @@ -118,12 +119,14 @@ module Chainweb.Test.Pact.Utils -- * miscellaneous , toTxCreationTime , dummyLogger +, stdoutDummyLogger , hunitDummyLogger , pactTestLogger , someTestVersionHeader , someBlockHeader , testPactFilesDir , getPWOByHeader +, throwIfNotPact4 ) where @@ -162,6 +165,7 @@ import System.IO.Temp (createTempDirectory) import System.LogLevel import Test.Tasty +import Test.Tasty.HUnit(assertFailure) -- internal pact modules @@ -199,15 +203,15 @@ 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.RelationalCheckpointer (initRelationalCheckpointer) +import Chainweb.Pact.PactService.Checkpointer.Internal (initCheckpointerResources) import Chainweb.Pact.Backend.SQLite.DirectV2 -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Backend.Utils hiding (withSqliteDb) + +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.Service.Types import Chainweb.Pact.Types +import Chainweb.Pact4.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Test.Cut @@ -216,7 +220,7 @@ import Chainweb.Test.Utils import Chainweb.Test.Utils.BlockHeader import Chainweb.Test.TestVersions import Chainweb.Time -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils import Chainweb.Version (ChainwebVersion(..), chainIds) import qualified Chainweb.Version as Version @@ -225,15 +229,16 @@ import Chainweb.WebBlockHeaderDB import Chainweb.WebPactExecutionService import Chainweb.Storage.Table.RocksDB +import Chainweb.Pact.Backend.Types -- ----------------------------------------------------------------------- -- -- Keys -type SimpleKeyPair = (Text,Text) - testPactFilesDir :: FilePath testPactFilesDir = "test/pact/" +type SimpleKeyPair = (Text,Text) + sender00 :: SimpleKeyPair sender00 = ("368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca" ,"251a920c403ae8c8f65f59142316af3c82b631fba46ddea92ee8c95035bd2898") @@ -566,8 +571,8 @@ defaultCmd = CmdBuilder -- | Build parsed + verified Pact command -- --- TODO: Use the new `assertCommand` function. -buildCwCmd :: (MonadThrow m, MonadIO m) => Text -> ChainwebVersion -> CmdBuilder -> m ChainwebTransaction +-- 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: " ++ err @@ -667,15 +672,14 @@ testPactCtxSQLite -> PayloadDb tbl -> SQLiteEnv -> PactServiceConfig - -> (TxContext -> GasModel) -> IO (TestPactCtx logger tbl) -testPactCtxSQLite logger v cid bhdb pdb sqlenv conf gasmodel = do - cp <- initRelationalCheckpointer defaultModuleCacheLimit sqlenv DoNotPersistIntraBlockWrites cpLogger v cid +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 mempty v cid) + evalPactServiceM_ ctx (initialPayloadState v cid) return ctx where cpLogger = addLabel ("chain-id", chainIdToText cid) $ addLabel ("sub-component", "checkpointer") $ logger @@ -685,41 +689,42 @@ testPactCtxSQLite logger v cid bhdb pdb sqlenv conf gasmodel = do , _psCheckpointer = cp , _psPdb = pdb , _psBlockHeaderDb = bhdb - , _psGasModel = gasmodel , _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") $ _cpLogger $ _cpReadCp cp + , _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") - $ _cpLogger $ _cpReadCp cp + $ logger - , _psBlockGasLimit = _pactBlockGasLimit conf + , _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 - -> MemPoolAccess - -> (TxContext -> GasModel) + -> 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 mempoolAccess gasmodel act = +withWebPactExecutionServiceCompaction logger v pactConfig bdb mempools act = withDbs $ \srcSqlEnvs -> withDbs $ \targetSqlEnvs -> do srcPacts <- mkPacts srcSqlEnvs @@ -749,20 +754,20 @@ withWebPactExecutionServiceCompaction logger v pactConfig bdb mempoolAccess gasm -> IO PactExecutionService mkTestPactExecutionService sqlenv c = do bhdb <- getBlockHeaderDb c bdb - ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig gasmodel + ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig return $ PactExecutionService { _pactNewBlock = \_ m fill ph -> - evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock mempoolAccess m fill ph + evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph , _pactContinueBlock = \_ bip -> - evalPactServiceM_ ctx $ execContinueBlock mempoolAccess bip + evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip , _pactValidateBlock = \h d -> - evalPactServiceM_ ctx $ fst <$> execValidateBlock mempoolAccess 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 (() <$) <$> execPreInsertCheckReq txs + evalPactServiceM_ ctx $ V.map (\_ -> Nothing) <$> execPreInsertCheckReq txs , _pactBlockTxHistory = \h d -> evalPactServiceM_ ctx $ execBlockTxHistory h d , _pactHistoricalLookup = \h d k -> @@ -774,20 +779,54 @@ withWebPactExecutionServiceCompaction logger v pactConfig bdb mempoolAccess gasm } -- | A queue-less WebPactExecutionService (for all chains) --- with direct chain access map for local. +-- with direct chain access map for local. withWebPactExecutionService :: (Logger logger) => logger -> ChainwebVersion -> PactServiceConfig -> TestBlockDb - -> MemPoolAccess - -> (TxContext -> GasModel) - -> ((WebPactExecutionService, HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) -> IO a) + -> ChainMap MemPoolAccess + -> ((WebPactExecutionService,HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) -> IO a) -> IO a -withWebPactExecutionService logger v pactConfig bdb mempoolAccess gasmodel act = - withWebPactExecutionServiceCompaction logger v pactConfig bdb mempoolAccess gasmodel - $ \(pact, pacts, _, _) -> act (pact, pacts) +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 @@ -854,7 +893,7 @@ withPactCtxSQLite logger v bhdbIO pdbIO conf f = bhdb <- bhdbIO pdb <- pdbIO s <- ios - testPactCtxSQLite logger v cid bhdb pdb s conf freeGasModel + testPactCtxSQLite logger v cid bhdb pdb s conf toTxCreationTime :: Integral a => Time a -> TxCreationTime toTxCreationTime (Time timespan) = TxCreationTime $ fromIntegral $ timeSpanToSeconds timespan @@ -940,7 +979,7 @@ withPactTestBlockDb' version cid rdb sqlEnvIO mempoolIO pactConfig f = mempool <- mempoolIO bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) cid let pdb = _bdbPayloadDb bdb - a <- async $ runForever (\_ _ -> return ()) "Chainweb.Test.Pact.Utils.withPactTestBlockDb" $ + 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)) @@ -996,7 +1035,7 @@ withPactTestBlockDb version cid rdb mempoolIO pactConfig f = bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) cid let pdb = _bdbPayloadDb bdb sqlEnv <- startSqliteDb cid logger dir False - a <- async $ runForever (\_ _ -> return ()) "Chainweb.Test.Pact.Utils.withPactTestBlockDb" $ + 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)) @@ -1012,6 +1051,9 @@ withPactTestBlockDb version cid rdb mempoolIO pactConfig f = 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) @@ -1075,3 +1117,10 @@ 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/Pact/VerifierPluginTest/Transaction.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction.hs similarity index 87% rename from test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction.hs rename to test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction.hs index 6d04eb5edf..54b5fafeb1 100644 --- a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction.hs +++ b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction.hs @@ -6,7 +6,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -module Chainweb.Test.Pact.VerifierPluginTest.Transaction +module Chainweb.Test.Pact4.VerifierPluginTest.Transaction ( tests ) where @@ -27,24 +27,25 @@ import Pact.Types.Term import Pact.Types.Verifier hiding (verifierName) import Chainweb.Miner.Pact -import Chainweb.Pact.PactService -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils -import qualified Chainweb.Test.Pact.VerifierPluginTest.Transaction.Message.After225 as After225 -import qualified Chainweb.Test.Pact.VerifierPluginTest.Transaction.Message.Before225 as Before225 -import Chainweb.Test.Pact.VerifierPluginTest.Transaction.Utils +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 +import Data.IORef +import Chainweb.Version tests :: TestTree tests = testGroup testName - [ test generousConfig getGasModel "verifierTest" verifierTest + [ test generousConfig "verifierTest" verifierTest - , test generousConfig getGasModel "recoverValidatorAnnouncementSuccess" hyperlaneRecoverValidatorAnnouncementSuccess - , test generousConfig getGasModel "recoverValidatorAnnouncementIncorrectSignatureFailure" + , test generousConfig "recoverValidatorAnnouncementSuccess" hyperlaneRecoverValidatorAnnouncementSuccess + , test generousConfig "recoverValidatorAnnouncementIncorrectSignatureFailure" hyperlaneRecoverValidatorAnnouncementIncorrectSignatureFailure - , test generousConfig getGasModel "recoverValidatorAnnouncementDifferentSignerFailure" + , test generousConfig "recoverValidatorAnnouncementDifferentSignerFailure" hyperlaneRecoverValidatorAnnouncementDifferentSignerFailure , testGroup "Message" @@ -53,19 +54,21 @@ tests = testGroup testName ] ] where - testName = "Chainweb.Test.Pact.VerifierPluginTest.Transaction" + 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 { _pactBlockGasLimit = 300_000 } + generousConfig = testPactServiceConfig { _pactNewBlockGasLimit = 300_000 } - test pactConfig gasmodel tname f = - withDelegateMempool $ \dmpio -> testCaseSteps tname $ \step -> + test pactConfig tname f = + testCaseSteps tname $ \step -> withTestBlockDb testVersion $ \bdb -> do - (iompa,mpa) <- dmpio let logger = hunitDummyLogger step - withWebPactExecutionService logger testVersion pactConfig bdb mpa gasmodel $ \(pact,_) -> + mempools <- onAllChains testVersion $ \_ -> do + mempoolRef <- newIORef mempty + return (mempoolRef, delegateMemPoolAccess mempoolRef) + withWebPactExecutionService logger testVersion pactConfig bdb (snd <$> mempools) $ \(pact,_) -> runReaderT f $ - SingleEnv bdb pact (return iompa) noMiner cid + SingleEnv bdb pact (mempools ^?! atChain cid . _1) noMiner cid verifierTest :: PactTestM () verifierTest = do @@ -126,7 +129,7 @@ verifierTest = do (\cr -> liftIO $ do assertTxSuccess "should have succeeded" (pDecimal 1) cr -- The **Allow** verifier costs 100 gas flat - assertEqual "gas should have been charged" 344 (_crGas cr) + assertEqual "gas should have been charged" 335 (_crGas cr) ) , PactTxTest (buildBasic' @@ -184,7 +187,7 @@ hyperlaneRecoverValidatorAnnouncementSuccess = do (mkExec' "(free.m.x)")) (\cr -> liftIO $ do assertTxSuccess "should have succeeded" (pDecimal 1) cr - assertEqual "gas should have been charged" 16501 (_crGas cr)) + assertEqual "gas should have been charged" 16492 (_crGas cr)) ] hyperlaneRecoverValidatorAnnouncementIncorrectSignatureFailure :: PactTestM () @@ -271,4 +274,3 @@ hyperlaneRecoverValidatorAnnouncementDifferentSignerFailure = do assertTxFailure "should have failed with incorrect signer" errMsg cr assertTxGas "verifier errors charge all gas" 20000 cr) ] - diff --git a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Message/After225.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/After225.hs similarity index 91% rename from test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Message/After225.hs rename to test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/After225.hs index 00fac34a34..4683d9edac 100644 --- a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Message/After225.hs +++ b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/After225.hs @@ -6,7 +6,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -module Chainweb.Test.Pact.VerifierPluginTest.Transaction.Message.After225 (tests) where +module Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Message.After225 (tests) where import Control.Lens hiding ((.=)) import Control.Monad.Reader @@ -27,41 +27,44 @@ import Pact.Types.Runtime import Pact.Types.Verifier hiding (verifierName) import Chainweb.Miner.Pact -import Chainweb.Pact.PactService -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.VerifierPlugin.Hyperlane.Binary import Chainweb.VerifierPlugin.Hyperlane.Utils -import Chainweb.Test.Pact.VerifierPluginTest.Transaction.Utils +import Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils +import Chainweb.Version +import Data.IORef tests :: TestTree tests = testGroup "After225" - [ test generousConfig getGasModel "verifySuccess" hyperlaneVerifySuccess - , test generousConfig getGasModel "verifyMoreValidatorsSuccess" hyperlaneVerifyMoreValidatorsSuccess - , test generousConfig getGasModel "verifyThresholdZeroError" hyperlaneVerifyThresholdZeroError - , test generousConfig getGasModel "verifyWrongSignersFailure" hyperlaneVerifyWrongSignersFailure - , test generousConfig getGasModel "verifyNotEnoughRecoveredSignaturesFailure" hyperlaneVerifyNotEnoughRecoveredSignaturesFailure - , test generousConfig getGasModel "verifyNotEnoughCapabilitySignaturesFailure" hyperlaneVerifyNotEnoughCapabilitySignaturesFailure - , test generousConfig getGasModel "verifyIncorretProofFailure" hyperlaneVerifyMerkleIncorrectProofFailure - , test generousConfig getGasModel "verifyFailureNotEnoughSignaturesToPassThreshold" hyperlaneVerifyFailureNotEnoughSignaturesToPassThreshold + [ 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 { _pactBlockGasLimit = 300_000 } + generousConfig = testPactServiceConfig { _pactNewBlockGasLimit = 300_000 } - test pactConfig gasmodel tname f = - withDelegateMempool $ \dmpio -> testCaseSteps tname $ \step -> + test pactConfig tname f = + testCaseSteps tname $ \step -> withTestBlockDb testVersion $ \bdb -> do - (iompa,mpa) <- dmpio let logger = hunitDummyLogger step - withWebPactExecutionService logger testVersion pactConfig bdb mpa gasmodel $ \(pact,_) -> + mempools <- onAllChains testVersion $ \_ -> do + mempoolRef <- newIORef mempty + return (mempoolRef, delegateMemPoolAccess mempoolRef) + withWebPactExecutionService logger testVersion pactConfig bdb (snd <$> mempools) $ \(pact,_) -> runReaderT f $ - SingleEnv bdb pact (return iompa) noMiner cid + SingleEnv bdb pact (mempools ^?! atChain cid . _1) noMiner cid -- hyperlane message tests @@ -220,7 +223,7 @@ hyperlaneVerifySuccess = do , PactTxTest (mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [validSignature] [validSigner] threshold) (\cr -> liftIO $ do assertTxSuccess "should have succeeded" (pString "succeeded") cr - assertEqual "gas should have been charged" 16592 (_crGas cr)) + assertEqual "gas should have been charged" 16582 (_crGas cr)) ] hyperlaneVerifyMoreValidatorsSuccess :: PactTestM () @@ -235,7 +238,7 @@ hyperlaneVerifyMoreValidatorsSuccess = do , PactTxTest (mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [validSignature] signers threshold) (\cr -> liftIO $ do assertTxSuccess "should have succeeded" (pString "succeeded") cr - assertEqual "gas should have been charged" 16592 (_crGas cr)) + assertEqual "gas should have been charged" 16583 (_crGas cr)) ] hyperlaneVerifyThresholdZeroError :: PactTestM () diff --git a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Message/Before225.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/Before225.hs similarity index 93% rename from test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Message/Before225.hs rename to test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/Before225.hs index 049769bcd6..498f93b541 100644 --- a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Message/Before225.hs +++ b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/Before225.hs @@ -6,7 +6,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -module Chainweb.Test.Pact.VerifierPluginTest.Transaction.Message.Before225 (tests) where +module Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Message.Before225 (tests) where import Control.Lens hiding ((.=)) import Control.Monad.Reader @@ -26,44 +26,47 @@ import Pact.Types.Term import Pact.Types.Verifier hiding (verifierName) import Chainweb.Miner.Pact -import Chainweb.Pact.PactService -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.VerifierPlugin.Hyperlane.Binary import Chainweb.VerifierPlugin.Hyperlane.Utils -import Chainweb.Test.Pact.VerifierPluginTest.Transaction.Utils +import Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils +import Data.IORef +import Chainweb.Version tests :: TestTree tests = testGroup "Before225" [ testGroup "MessageId metadata tests" - [ test generousConfig getGasModel "verifySuccess" hyperlaneVerifyMessageIdSuccess - , test generousConfig getGasModel "verifyEmptyRecoveredSignaturesSuccess" hyperlaneVerifyMessageIdEmptyRecoveredSignaturesSuccess - , test generousConfig getGasModel "verifyWrongSignersFailure" hyperlaneVerifyMessageIdWrongSignersFailure - , test generousConfig getGasModel "verifyNotEnoughRecoveredSignaturesFailure" hyperlaneVerifyMessageIdNotEnoughRecoveredSignaturesFailure - , test generousConfig getGasModel "verifyNotEnoughCapabilitySignaturesFailure" hyperlaneVerifyMessageIdNotEnoughCapabilitySignaturesFailure + [ 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 getGasModel "verifyNotEnabledFailure" hyperlaneVerifyMerkleNotEnabledFailure + [ test generousConfig "verifyNotEnabledFailure" hyperlaneVerifyMerkleNotEnabledFailure ] ] where -- This is way more than what is used in production, but during testing -- we can be generous. - generousConfig = testPactServiceConfig { _pactBlockGasLimit = 300_000 } + generousConfig = testPactServiceConfig { _pactNewBlockGasLimit = 300_000 } - test pactConfig gasmodel tname f = - withDelegateMempool $ \dmpio -> testCaseSteps tname $ \step -> + test pactConfig tname f = + testCaseSteps tname $ \step -> withTestBlockDb testVersion $ \bdb -> do - (iompa,mpa) <- dmpio let logger = hunitDummyLogger step - withWebPactExecutionService logger testVersion pactConfig bdb mpa gasmodel $ \(pact,_) -> + mempools <- onAllChains testVersion $ \_ -> do + mempoolRef <- newIORef mempty + return (mempoolRef, delegateMemPoolAccess mempoolRef) + withWebPactExecutionService logger testVersion pactConfig bdb (snd <$> mempools) $ \(pact,_) -> runReaderT f $ - SingleEnv bdb pact (return iompa) noMiner cid + SingleEnv bdb pact (mempools ^?! atChain cid . _1) noMiner cid -- hyperlane message tests @@ -166,7 +169,7 @@ hyperlaneVerifyMessageIdSuccess = do (mkExec' "(free.m.x)")) (\cr -> liftIO $ do assertTxSuccess "should have succeeded" (pString "succeeded") cr - assertEqual "gas should have been charged" 16533 (_crGas cr)) + assertEqual "gas should have been charged" 16524 (_crGas cr)) ] @@ -214,7 +217,7 @@ hyperlaneVerifyMessageIdEmptyRecoveredSignaturesSuccess = do (mkExec' "(free.m.x)")) (\cr -> liftIO $ do assertTxSuccess "should have succeeded" (pDecimal 1) cr - assertEqual "gas should have been charged" 258 (_crGas cr)) + assertEqual "gas should have been charged" 249 (_crGas cr)) ] diff --git a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Utils.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs similarity index 82% rename from test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Utils.hs rename to test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs index 030371c2b4..0b01b8e653 100644 --- a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Transaction/Utils.hs +++ b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs @@ -6,7 +6,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -module Chainweb.Test.Pact.VerifierPluginTest.Transaction.Utils where +module Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils where import Control.Concurrent.MVar import Control.Lens hiding ((.=)) @@ -35,11 +35,11 @@ import Chainweb.ChainId import Chainweb.Cut import Chainweb.Mempool.Mempool import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types + import Chainweb.Payload import Chainweb.Test.Cut import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Test.Utils import Chainweb.Test.TestVersions import Chainweb.Time @@ -47,6 +47,7 @@ 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 @@ -58,7 +59,7 @@ cid = unsafeChainId 9 data SingleEnv = SingleEnv { _menvBdb :: !TestBlockDb , _menvPact :: !WebPactExecutionService - , _menvMpa :: !(IO (IORef MemPoolAccess)) + , _menvMpa :: !(IORef MemPoolAccess) , _menvMiner :: !Miner , _menvChainId :: !ChainId } @@ -68,12 +69,12 @@ makeLenses ''SingleEnv type PactTestM = ReaderT SingleEnv IO newtype MempoolCmdBuilder = MempoolCmdBuilder - { _mempoolCmdBuilder :: BlockHeader -> CmdBuilder + { _mempoolCmdBuilder :: ChainId -> BlockCreationTime -> CmdBuilder } -- | Block filler. A 'Nothing' result means "skip this filler". newtype MempoolBlock = MempoolBlock - { _mempoolBlock :: BlockHeader -> Maybe [MempoolCmdBuilder] + { _mempoolBlock :: ChainId -> BlockCreationTime -> Maybe [MempoolCmdBuilder] } -- | Mempool with an ordered list of fillers. @@ -106,31 +107,35 @@ setPactMempool :: PactMempool -> PactTestM () setPactMempool (PactMempool fs) = do mpa <- view menvMpa mpsRef <- liftIO $ newIORef fs - setMempool mpa $ mempty { + liftIO $ writeIORef mpa $ mempty { mpaGetBlock = \_ -> go mpsRef } where - go ref mempoolPreBlockCheck bHeight bHash blockHeader = do + go ref mempoolPreBlockCheck bHeight bHash bct = do mps <- readIORef ref let runMps i = \case [] -> return mempty - (mp:r) -> case _mempoolBlock mp blockHeader of + (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 blockHeader) testVersion $ _mempoolCmdBuilder b blockHeader - validationResults <- mempoolPreBlockCheck bHeight bHash cmds - return $ fmap fst $ V.filter snd (V.zip cmds validationResults) + 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 :: (BlockHeader -> Bool) -> MempoolBlock -> MempoolBlock -filterBlock f (MempoolBlock b) = MempoolBlock $ \mi -> - if f mi then b mi else Nothing +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 $ \bh -> - view blockChainId bh == chid +blockForChain chid = filterBlock $ \chain _ -> + chain == chid runCut' :: PactTestM () runCut' = do @@ -171,7 +176,7 @@ runBlockTest pts = do -- | Convert tests to block for specified chain. testsToBlock :: ChainId -> [PactTxTest] -> MempoolBlock -testsToBlock chid pts = blockForChain chid $ MempoolBlock $ \_ -> +testsToBlock chid pts = blockForChain chid $ MempoolBlock $ \_ _ -> pure $ map _pttBuilder pts -- | Run tests on current cut and chain. @@ -198,7 +203,7 @@ signSender00 = set cbSigners [mkEd25519Signer' sender00 []] setFromHeader :: BlockHeader -> CmdBuilder -> CmdBuilder setFromHeader bh = - set cbChainId (view blockChainId bh) + set cbChainId (_chainId bh) . set cbCreationTime (toTxCreationTime $ _bct $ view blockCreationTime bh) buildBasic @@ -214,9 +219,10 @@ buildBasic' :: (CmdBuilder -> CmdBuilder) -> PactRPC T.Text -> MempoolCmdBuilder -buildBasic' f r = MempoolCmdBuilder $ \bh -> +buildBasic' f r = MempoolCmdBuilder $ \chain bct -> f $ signSender00 - $ setFromHeader bh + $ set cbChainId chain + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC r $ defaultCmd diff --git a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Unit.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs similarity index 93% rename from test/lib/Chainweb/Test/Pact/VerifierPluginTest/Unit.hs rename to test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs index 14ec27d1f8..29ed9eeea2 100644 --- a/test/lib/Chainweb/Test/Pact/VerifierPluginTest/Unit.hs +++ b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs @@ -12,7 +12,7 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} -module Chainweb.Test.Pact.VerifierPluginTest.Unit +module Chainweb.Test.Pact4.VerifierPluginTest.Unit ( -- * test suite tests ) where @@ -35,7 +35,7 @@ instance Arbitrary Word256 where arbitrary = fromInteger . getNonNegative <$> arbitrary tests :: TestTree -tests = testGroup "Chainweb.Test.Pact.VerifierPluginTest.Unit" +tests = testGroup "Chainweb.Test.Pact4.VerifierPluginTest.Unit" [ testCase "decimalToWord" hyperlaneDecimalToWord , testCase "decimalToWord2" hyperlaneDecimalToWord2 , testCase "wordToDecimal" hyperlaneWordToDecimal diff --git a/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs b/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs new file mode 100644 index 0000000000..b8323be4ed --- /dev/null +++ b/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs @@ -0,0 +1,275 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ViewPatterns #-} + +module Chainweb.Test.Pact5.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 Control.Lens hiding ((.=)) +import Pact.Core.Command.Types +import Data.Text (Text) +import GHC.Generics +import Pact.Core.Capabilities +import Pact.Core.Guards +import Pact.Core.Verifiers (Verifier, ParsedVerifierProof) +import Pact.Core.Command.RPC +import Pact.Core.ChainData +import Pact.Core.Gas.Types +import Control.Exception.Safe +import Control.Monad.IO.Class +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 Data.Text.Encoding as T +import Chainweb.Utils +import Data.Maybe +import Pact.Core.Command.Crypto +import Pact.Core.Command.Util +import qualified Data.Text as T +import Pact.Core.Names (Field(..), QualifiedName, DefPactId) +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 Pact.Core.Command.Client (ApiKeyPair (..), mkCommandWithDynKeys) +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 + +type TextKeyPair = (Text,Text) + +sender00 :: TextKeyPair +sender00 = ("368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca" + ,"251a920c403ae8c8f65f59142316af3c82b631fba46ddea92ee8c95035bd2898") + +sender01 :: TextKeyPair +sender01 = ("6be2f485a7af75fedb4b7f153a903f7e6000ca4aa501179c91a2450b777bd2a7" + ,"2beae45b29e850e6b1882ae245b0bab7d0689ebdd0cd777d4314d24d7024b4f7") + +sender02WebAuthnPrefixed :: TextKeyPair +sender02WebAuthnPrefixed = + ("WEBAUTHN-a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf" + ,"fecd4feb1243d715d095e24713875ca76c476f8672ec487be8e3bc110dd329ab") + +sender02WebAuthn :: TextKeyPair +sender02WebAuthn = + ("a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf" + ,"fecd4feb1243d715d095e24713875ca76c476f8672ec487be8e3bc110dd329ab") + +sender03WebAuthn :: TextKeyPair +sender03WebAuthn = + ("a4010103272006215820ad72392508272b4c45536976474cdd434e772bfd630738ee9aac7343e7222eb6" + ,"ebe7d1119a53863fa64be7347d82d9fcc9ebeb8cbbe480f5e8642c5c36831434") + +allocation00KeyPair :: TextKeyPair +allocation00KeyPair = + ( "d82d0dcde9825505d86afb6dcc10411d6b67a429a79e21bda4bb119bf28ab871" + , "c63cd081b64ae9a7f8296f11c34ae08ba8e1f8c84df6209e5dee44fa04bcb9f5" + ) + +-- | Make trivial keyset data +mkKeySetData :: Text -> [TextKeyPair] -> PactValue +mkKeySetData name keys = PObject $ Map.singleton (Field name) $ PList (Vector.fromList $ map (PString . fst) keys) + +sender00Ks :: KeySet +sender00Ks = KeySet + (Set.fromList [PublicKeyText "368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca"]) + KeysAll + + +-- | 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 -> [CapToken QualifiedName PactValue] -> CmdSigner +mkEd25519Signer pubKey privKey caps = CmdSigner + { _csSigner = signer + , _csPrivKey = privKey + } + where + signer = Signer + { _siScheme = Nothing + , _siPubKey = pubKey + , _siAddress = Nothing + , _siCapList = SigCapability <$> caps } + +mkEd25519Signer' :: TextKeyPair -> [CapToken QualifiedName PactValue] -> CmdSigner +mkEd25519Signer' (pub,priv) = mkEd25519Signer pub priv + +mkWebAuthnSigner :: Text -> Text -> [CapToken QualifiedName PactValue] -> CmdSigner +mkWebAuthnSigner pubKey privKey caps = CmdSigner + { _csSigner = signer + , _csPrivKey = privKey + } + where + signer = Signer + { _siScheme = Just WebAuthn + , _siPubKey = pubKey + , _siAddress = Nothing + , _siCapList = SigCapability <$> caps } + +mkWebAuthnSigner' :: TextKeyPair -> [CapToken QualifiedName PactValue] -> 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 :: !(Maybe Text) + , _cbChainId :: !Chainweb.ChainId + , _cbSender :: !Text + , _cbGasLimit :: !GasLimit + , _cbGasPrice :: !GasPrice + , _cbTTL :: !TTLSeconds + , _cbCreationTime :: !(Maybe TxCreationTime) + } deriving (Eq,Show,Generic) +makeLenses ''CmdBuilder + +-- | Make code-only Exec PactRPC +mkExec' :: Text -> PactRPC Text +mkExec' ecode = mkExec ecode (PObject mempty) + +-- | Make Exec PactRPC +mkExec :: Text -> PactValue -> PactRPC Text +mkExec ecode edata = Exec $ ExecMsg ecode edata + +mkCont :: ContMsg -> PactRPC Text +mkCont = Continuation + +mkContMsg :: DefPactId -> Int -> ContMsg +mkContMsg pid step = ContMsg + { _cmPactId = pid + , _cmStep = step + , _cmRollback = False + , _cmData = PObject mempty + , _cmProof = Nothing } + +-- | Default builder. +defaultCmd :: CmdBuilder +defaultCmd = CmdBuilder + { _cbSigners = [] + , _cbVerifiers = [] + , _cbRPC = mkExec' "1" + , _cbNonce = Nothing + , _cbChainId = unsafeChainId 0 + , _cbSender = "sender00" + , _cbGasLimit = GasLimit (Gas 10_000) + , _cbGasPrice = GasPrice 0.000_1 + , _cbTTL = TTLSeconds 300 -- 5 minutes + , _cbCreationTime = Nothing + } + +-- | Build parsed + verified Pact command +-- TODO: Use the new `assertPact4Command` function. +buildCwCmd :: (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact5.Transaction +buildCwCmd v cmd = buildTextCmd v cmd >>= \(c :: Command Text) -> + case validatePact5Command v (_cbChainId cmd) 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 + + 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 unparsed, unverified command +-- +buildTextCmd :: (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m (Command Text) +buildTextCmd v = fmap (fmap T.decodeUtf8) . buildRawCmd v + +-- | Build a raw bytestring command +-- +buildRawCmd :: (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m (Command ByteString) +buildRawCmd v CmdBuilder{..} = do + kps <- liftIO $ traverse mkDynKeyPairs _cbSigners + nonce <- liftIO $ maybe (fmap T.pack $ replicateM 10 $ randomRIO ('a', 'z')) return _cbNonce + creationTime <- liftIO $ do + case _cbCreationTime of + Nothing -> do + Time timespan <- getCurrentTimeIntegral @Integer + pure (TxCreationTime $ fromIntegral $ timeSpanToSeconds timespan) + Just t -> do + return t + let pm = PublicMeta + { _pmChainId = cid + , _pmSender = _cbSender + , _pmGasLimit = _cbGasLimit + , _pmGasPrice = _cbGasPrice + , _pmTTL = _cbTTL + , _pmCreationTime = creationTime + } + cmd <- liftIO $ mkCommandWithDynKeys kps _cbVerifiers (StableEncoding pm) nonce (Just nid) _cbRPC + pure cmd + where + nid = NetworkId (sshow v) + cid = ChainId $ sshow (chainIdInt _cbChainId :: Int) + +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 diff --git a/test/lib/Chainweb/Test/Pact5/Utils.hs b/test/lib/Chainweb/Test/Pact5/Utils.hs new file mode 100644 index 0000000000..42c8623b39 --- /dev/null +++ b/test/lib/Chainweb/Test/Pact5/Utils.hs @@ -0,0 +1,253 @@ +{-# language + FlexibleContexts + , ImportQualifiedPost + , LambdaCase + , NumericUnderscores + , OverloadedStrings + , PackageImports + , TypeApplications +#-} + +module Chainweb.Test.Pact5.Utils + ( initCheckpointer + , pactTxFrom4To5 + + -- * Logging + , getTestLogLevel + , testLogFn + , getTestLogger + + -- * Mempool + , insertMempool + , lookupMempool + + -- * Resources + , withTempSQLiteResource + , withInMemSQLiteResource + , withTestRocksDb + , withPactQueue + , withMempool + , withRunPactService + , withBlockDbs + , withTestBlockHeaderDb + , testRocksDb + ) + where + +import Chainweb.Chainweb (validatingMempoolConfig) +import "pact" Pact.Types.Command qualified as Pact4 +import "pact" Pact.Types.Hash qualified as Pact4 +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB (BlockHeaderDb, initBlockHeaderDb, closeBlockHeaderDb) +import Chainweb.BlockHeaderDB qualified as BlockHeaderDB +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 Pact.Core.Pretty 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 (..), finally, 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 (ByteString) +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 Data.Word (Word64) +import Database.RocksDB.Internal qualified as R +import Pact.Core.Command.Types qualified as Pact5 +import Pact.Core.Hash qualified as Pact5 +import Pact.JSON.Encode qualified as J +import Pact.Types.Gas qualified as Pact4 +import System.Environment (lookupEnv) +import System.IO.Temp (createTempDirectory, getCanonicalTemporaryDirectory) +import System.LogLevel +import System.Random (randomIO) + +withBlockDbs :: ChainwebVersion -> RocksDb -> ResourceT IO (PayloadDb RocksDbTable, WebBlockHeaderDb) +withBlockDbs v rdb = do + testRdb <- liftIO $ testRocksDb "withBlockDbs" rdb + webBHDb <- liftIO $ initWebBlockHeaderDb testRdb v + let payloadDb = newPayloadDb testRdb + liftIO $ initializePayloadDb v payloadDb + return (payloadDb, webBHDb) + +testBlockHeaderDb + :: RocksDb + -> BlockHeader + -> IO BlockHeaderDb +testBlockHeaderDb rdb h = do + rdb' <- testRocksDb "withTestBlockHeaderDb" rdb + initBlockHeaderDb (BlockHeaderDB.Configuration h rdb') + +withTestBlockHeaderDb + :: RocksDb + -> BlockHeader + -> ResourceT IO BlockHeaderDb +withTestBlockHeaderDb rdb h = + snd <$> allocate (testBlockHeaderDb rdb h) closeBlockHeaderDb + +testRocksDb + :: ByteString -- ^ Prefix + -> RocksDb + -> IO RocksDb +testRocksDb l r = do + prefix <- (<>) l . sshow <$> (randomIO @Word64) + return r { _rocksDbNamespace = prefix } + +withTestRocksDb :: ResourceT IO RocksDb +withTestRocksDb = view _2 . snd <$> allocate create destroy + where + create = do + sysdir <- getCanonicalTemporaryDirectory + dir <- createTempDirectory sysdir "chainweb-rocksdb-tmp" + opts@(R.Options' opts_ptr _ _) <- R.mkOpts modernDefaultOptions + rocks <- openRocksDb dir opts_ptr + return (dir, rocks, opts) + + destroy (dir, rocks, opts) = + closeRocksDb rocks `finally` + R.freeOpts opts `finally` + destroyRocksDb dir + +-- | 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. +insertMempool :: MempoolBackend Pact4.UnparsedTransaction -> InsertType -> [Pact5.Transaction] -> IO () +insertMempool 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. +lookupMempool :: MempoolBackend Pact4.UnparsedTransaction -> Vector Pact5.Hash -> IO (HashSet Pact5.Hash) +lookupMempool 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) + diff --git a/test/lib/Chainweb/Test/RestAPI/Client_.hs b/test/lib/Chainweb/Test/RestAPI/Client_.hs index 77e88ba85a..9260da9d2c 100644 --- a/test/lib/Chainweb/Test/RestAPI/Client_.hs +++ b/test/lib/Chainweb/Test/RestAPI/Client_.hs @@ -45,6 +45,7 @@ import Servant.API.ContentTypes -- internal modules +import Chainweb.Block import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeaderDB @@ -195,4 +196,3 @@ 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) - \ No newline at end of file diff --git a/test/lib/Chainweb/Test/RestAPI/Utils.hs b/test/lib/Chainweb/Test/RestAPI/Utils.hs index ab3df755bf..1d6add486e 100644 --- a/test/lib/Chainweb/Test/RestAPI/Utils.hs +++ b/test/lib/Chainweb/Test/RestAPI/Utils.hs @@ -50,7 +50,6 @@ import Control.Lens import Control.Monad.Catch import Control.Retry -import Data.Either import Data.Foldable (toList) import Data.Text (Text) import Data.Maybe (fromJust) @@ -70,7 +69,7 @@ import Chainweb.Cut.CutHashes (_cutHashes, _bhwhHeight) import Chainweb.CutDB.RestAPI.Client import Chainweb.Pact.RestAPI.Client import Chainweb.Pact.RestAPI.EthSpv -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Rosetta.RestAPI.Client import Chainweb.Version import Chainweb.Test.Utils @@ -81,6 +80,9 @@ 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 -- ------------------------------------------------------------------ -- -- Defaults @@ -242,6 +244,7 @@ sending v sid cenv batch = -- | Poll with retry using an exponential backoff -- data PollingExpectation = ExpectPactError | ExpectPactResult + deriving Eq polling :: ChainwebVersion @@ -249,7 +252,7 @@ polling -> ClientEnv -> RequestKeys -> PollingExpectation - -> IO PollResponses + -> IO Pact4.PollResponses polling v sid cenv rks pollingExpectation = pollingWithDepth v sid cenv rks Nothing pollingExpectation @@ -260,7 +263,7 @@ pollingWithDepth -> RequestKeys -> Maybe ConfirmationDepth -> PollingExpectation - -> IO PollResponses + -> IO Pact4.PollResponses pollingWithDepth v sid cenv rks confirmationDepth pollingExpectation = recovering testRetryPolicy [h] $ \s -> do debug @@ -271,25 +274,27 @@ pollingWithDepth v sid cenv rks confirmationDepth pollingExpectation = -- by making sure results are successful and request keys -- are sane - runClientM (pactPollWithQueryApiClient v sid confirmationDepth $ Poll rs) cenv >>= \case + runClientM (pactPollWithQueryApiClient v sid confirmationDepth $ Pact5.PollRequest rs) cenv >>= \case Left e -> throwM $ PollingFailure (show e) - Right r@(PollResponses mp) -> + Right r@(Pact5.PollResponse mp) -> if all (go mp) (toList rs) - then return r + then do + let pact4Resps = HM.fromList $ + [ (toPact4RequestKey rk, toPact4CommandResult cr) | (rk, cr) <- HM.toList mp ] + return $ Pact4.PollResponses pact4Resps else throwM $ PollingFailure $ T.unpack $ "polling check failed: " <> J.encodeText r where h _ = Handler $ \case PollingFailure _ -> return True _ -> return False - rs = _rkRequestKeys rks + rs = toPact5RequestKey <$> _rkRequestKeys rks - validate (PactResult a) = case pollingExpectation of - ExpectPactResult -> isRight a - ExpectPactError -> isLeft a + validate Pact5.PactResultOk{} = pollingExpectation == ExpectPactResult + validate Pact5.PactResultErr{} = pollingExpectation == ExpectPactError go m rk = case m ^. at rk of - Just cr -> _crReqKey cr == rk && validate (_crResult cr) + Just cr -> Pact5._crReqKey cr == rk && validate (Pact5._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 06ee7e2fe2..6147754c96 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -1,9 +1,11 @@ {-# LANGUAGE BlockArguments #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} module Chainweb.Test.TestVersions ( barebonesTestVersion @@ -13,6 +15,9 @@ module Chainweb.Test.TestVersions , quirkedGasSlowForkingCpmTestVersion , timedConsensusVersion , instantCpmTestVersion + , pact5InstantCpmTestVersion + , pact5CheckpointerTestVersion + , pact5SlowCpmTestVersion ) where import Control.Lens hiding (elements) @@ -25,6 +30,8 @@ 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 System.IO.Unsafe @@ -46,6 +53,8 @@ import P2P.Peer import qualified Pact.Types.Command as P import qualified Pact.Types.Gas as P import qualified Pact.Types.Hash as P +import Chainweb.Test.Pact5.Utils (pactTxFrom4To5) + import Pact.Types.Verifier import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 @@ -81,9 +90,10 @@ type VersionBuilder = ChainwebVersion -> ChainwebVersion -- fixed point. Additionally registers it in the global version registry. buildTestVersion :: VersionBuilder -> ChainwebVersion buildTestVersion f = - unsafeDupablePerformIO (v <$ registerVersion v) & versionName .~ v ^. versionName + unsafePerformIO (v <$ registerVersion v) & versionName .~ v ^. versionName where v = f v +{-# 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 @@ -113,6 +123,15 @@ 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] + ] ] -- | Details common to all test versions thus far. @@ -130,42 +149,6 @@ testVersionTemplate v = v & versionQuirks .~ noQuirks & versionServiceDate .~ Nothing --- | 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 - Chainweb227Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 46 - -- | A test version without Pact or PoW, with only one chain graph. barebonesTestVersion :: ChainGraph -> ChainwebVersion barebonesTestVersion g = buildTestVersion $ \v -> @@ -222,6 +205,35 @@ timedConsensusVersion g1 g2 = buildTestVersion $ \v -> v , _genesisTime = AllChains $ BlockCreationTime epoch } +-- | A test version without Pact or PoW. +pact5CheckpointerTestVersion :: ChainGraph -> ChainwebVersion +pact5CheckpointerTestVersion g1 = buildTestVersion $ \v -> v + & testVersionTemplate + & versionName .~ ChainwebVersionName ("pact5-checkpointertest-" <> toText g1) + & versionBlockDelay .~ BlockDelay 1_000_000 + & versionWindow .~ WindowWidth 120 + & versionForks .~ tabulateHashMap (\case + SkipTxTimingValidation -> AllChains $ ForkAtBlockHeight (BlockHeight 2) + -- pact is disabled, we don't care about pact forks + _ -> AllChains ForkAtGenesis + ) + & versionUpgrades .~ AllChains 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, emptyPayload) | n <- HS.toList (chainIds v) ] + , _genesisBlockTarget = AllChains maxTarget + , _genesisTime = AllChains $ BlockCreationTime epoch + } + -- | A family of versions each with Pact enabled and PoW disabled. cpmTestVersion :: ChainGraph -> VersionBuilder cpmTestVersion g v = v @@ -247,13 +259,13 @@ cpmTestVersion g v = v } & versionUpgrades .~ chainZip HM.union (indexByForkHeights v - [ (CoinV2, AllChains (upgrade Other.transactions)) - , (Pact4Coin3, AllChains (Upgrade CoinV3.transactions True)) - , (Chainweb214Pact, AllChains (Upgrade CoinV4.transactions True)) - , (Chainweb215Pact, AllChains (Upgrade CoinV5.transactions True)) - , (Chainweb223Pact, AllChains (upgrade CoinV6.transactions)) + [ (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) (Upgrade MNKAD.transactions False))]) + (onChains [(unsafeChainId 3, HM.singleton (BlockHeight 2) (Pact4Upgrade MNKAD.transactions False))]) slowForks :: HashMap Fork (ChainMap ForkHeight) slowForks = tabulateHashMap \case @@ -289,6 +301,44 @@ slowForks = tabulateHashMap \case Chainweb225Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 130) Chainweb226Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 135) Chainweb227Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 140) + Pact5Fork -> AllChains ForkNever + +-- | 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 + Chainweb227Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 46 + Pact5Fork -> AllChains ForkNever -- | CPM version (see `cpmTestVersion`) with forks and upgrades slowly enabled. slowForkingCpmTestVersion :: ChainGraph -> ChainwebVersion @@ -327,17 +377,82 @@ noBridgeCpmTestVersion g = buildTestVersion $ \v -> v & versionForks .~ (fastForks & at SPVBridge ?~ AllChains ForkNever) -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled --- at genesis. +-- at genesis EXCEPT Pact 5. instantCpmTestVersion :: ChainGraph -> ChainwebVersion instantCpmTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("instant-CPM-" <> toText g) - & versionForks .~ tabulateHashMap (\_ -> AllChains ForkAtGenesis) + & versionForks .~ tabulateHashMap (\case + -- pact 5 is off + Pact5Fork -> AllChains ForkNever + _ -> AllChains ForkAtGenesis + ) & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] + [(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 + ) + & 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. +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 + ) + & 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"] + ) + ) diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index 00308e94be..36a958cff1 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -27,6 +27,15 @@ module Chainweb.Test.Utils , withResource' , withResourceT , independentSequentialTestGroup +, unsafeHeadOf + +, TestPact5CommandResult +, toPact4RequestKey +, toPact5RequestKey +, toPact4Command +, toPact4CommandResult +, toPact5CommandResult +, pact4Poll -- * Test RocksDb , testRocksDb @@ -124,6 +133,7 @@ module Chainweb.Test.Utils , NodeDbDirs(..) ) where +import Chainweb.Test.Pact5.Utils (getTestLogLevel) import Control.Concurrent import Control.Concurrent.STM import Control.Lens @@ -143,6 +153,7 @@ 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 @@ -153,6 +164,7 @@ import qualified Network.HTTP.Client as HTTP import qualified Network.HTTP.Client.TLS as HTTP import qualified Network.HTTP.Types 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.Wai.Handler.WarpTLS as W (runTLSSocket) @@ -206,7 +218,7 @@ import Chainweb.Mempool.Mempool (MempoolBackend(..), TransactionHash(..), BlockF import Chainweb.MerkleUniverse import Chainweb.Miner.Config import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types (SQLiteEnv) +import Chainweb.Pact.Backend.Types(SQLiteEnv) import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) import Chainweb.Payload.PayloadStore import Chainweb.RestAPI @@ -234,6 +246,17 @@ import qualified P2P.Node.PeerDB as P2P 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.Core.Info 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 -- -------------------------------------------------------------------------- -- -- Intialize Test BlockHeader DB @@ -940,10 +963,9 @@ withNodes -> (ChainwebConfiguration -> ChainwebConfiguration) -> [NodeDbDirs] -> ResourceT IO ChainwebNetwork -withNodes = withNodes_ (genericLogger Error (error . T.unpack)) - -- Test resources are part of test infrastructure and should never print - -- anything. A message at log level error means that the test harness itself - -- failed and with thus abort the test. +withNodes v confF nodeDbDirs = do + logLevel <- liftIO getTestLogLevel + withNodes_ (genericLogger logLevel T.putStrLn) v confF nodeDbDirs withNodesAtLatestBehavior :: ChainwebVersion @@ -1142,7 +1164,7 @@ getClientEnv :: BaseUrl -> IO ClientEnv getClientEnv url = flip mkClientEnv url <$> HTTP.newTlsManagerWith mgrSettings where mgrSettings = HTTP.mkManagerSettings - (HTTP.TLSSettingsSimple True False False defaultSupportedTlsSettings) + (HTTP.TLSSettingsSimple True False False HTTP.defaultSupported) Nothing -- | Backoff up to a constant 250ms, limiting to ~40s @@ -1166,3 +1188,39 @@ independentSequentialTestGroup tn tts = (mvarIO >>= takeMVar) (\_ -> mvarIO >>= flip putMVar ()) $ \_ -> tt + +unsafeHeadOf :: HasCallStack => Getting (Endo a) s a -> s -> a +unsafeHeadOf l s = s ^?! l + +type TestPact5CommandResult = Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.LineInfo)) + +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/pact/contTXOUTNew.pact b/test/pact/contTXOUTNew.pact index 3527e074b0..791cbdbf7e 100644 --- a/test/pact/contTXOUTNew.pact +++ b/test/pact/contTXOUTNew.pact @@ -24,8 +24,8 @@ } } , "events": [] - , "gas": 6 - , "logs": "c_Xbqb7BZetrHBtU7nGdZ4yoc1_Y1iE3CgsO03gOrbc" + , "gas": 379 + , "logs": "haM83WBlgzMmCtcQHLv0qR_0mdoDY_L6Y98PsM2H-_4" , "meta": {} , "req-key": "pHsX8RJkJNRlrGflD_vRiKOsjlLSVGdf1hYU_eI6fgU" , "result": diff --git a/test/pact/tfrTXOUTNew.pact b/test/pact/tfrTXOUTNew.pact index e7e72b1e2d..6afd57d52b 100644 --- a/test/pact/tfrTXOUTNew.pact +++ b/test/pact/tfrTXOUTNew.pact @@ -9,8 +9,8 @@ , "params": ["sender00" "sender01" 1.0] } ] - , "gas": 5 - , "logs": "zF8hy3XGDeZR0sHrd5e-KehTlPUHguzwSbAQRMU8mcM" + , "gas": 547 + , "logs": "KlSdh0LqHlhmSd0iHzukADGkI-IzkYdNdD_FDrF6uIA" , "meta": {} , "req-key": "LCLlbFalZdnCr7ax2Cqntuj2PCUq0pVGhmgAE-oabKs" , "result": "Write succeeded" diff --git a/test/unit/Chainweb/Test/BlockHeader/Genesis.hs b/test/unit/Chainweb/Test/BlockHeader/Genesis.hs index 3212094eec..affc622b61 100644 --- a/test/unit/Chainweb/Test/BlockHeader/Genesis.hs +++ b/test/unit/Chainweb/Test/BlockHeader/Genesis.hs @@ -36,7 +36,7 @@ import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet -import Chainweb.Version.Testnet +import Chainweb.Version.Testnet04 --- @@ -70,36 +70,36 @@ graphTransitionTargetTests :: TestTree graphTransitionTargetTests = testGroup "graph transition genesis targets" -- mainnet20InitialHashTarget properties [ testProperty "mainnet20InitialHashTarget deserialization" $ - Just (Mainnet01 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) === (HashTarget . (4 *) <$> decodePowHashNat64 "DOordl9cgfs4ZTBdFnbjRW5th-hW-pL33DIAAAAAAAA") + Just (Mainnet01 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) === (HashTarget . (4 *) <$> decodePowHashNat64 "DOordl9cgfs4ZTBdFnbjRW5th-hW-pL33DIAAAAAAAA") , testProperty "mainnet20InitialHashTarget json deserialization" $ - Just (Mainnet01 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) === (HashTarget . (4 *) <$> decodePowHashNatJson "DOordl9cgfs4ZTBdFnbjRW5th-hW-pL33DIAAAAAAAA") + Just (Mainnet01 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) === (HashTarget . (4 *) <$> decodePowHashNatJson "DOordl9cgfs4ZTBdFnbjRW5th-hW-pL33DIAAAAAAAA") , testProperties "mainnet old chains" $ forChain Mainnet01 maxTarget . unsafeChainId <$> [0..9] , testProperties "mainnet new chains" $ - forChain Mainnet01 (Mainnet01 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) . unsafeChainId <$> [10..19] + forChain Mainnet01 (Mainnet01 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) . unsafeChainId <$> [10..19] -- testnet20InitialHashTarget properties , testProperty "testnet20InitialHashTarget deserialization" $ - Just (Testnet04 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) === (HashTarget <$> decodePowHashNat64 "NZIklpW6xujSPrX3gyhXInfxxOS6JDjkW_GbGwAAAAA") + Just (Testnet04 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) === (HashTarget <$> decodePowHashNat64 "NZIklpW6xujSPrX3gyhXInfxxOS6JDjkW_GbGwAAAAA") , testProperty "testnet20InitialHashTarget json deserialization" $ - Just (Testnet04 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) === (HashTarget <$> decodePowHashNatJson "NZIklpW6xujSPrX3gyhXInfxxOS6JDjkW_GbGwAAAAA") - , testProperties "testnet old chains" $ + Just (Testnet04 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) === (HashTarget <$> decodePowHashNatJson "NZIklpW6xujSPrX3gyhXInfxxOS6JDjkW_GbGwAAAAA") + , testProperties "testnet04 old chains" $ forChain Testnet04 maxTarget . unsafeChainId <$> [0..9] - , testProperties "testnet new chains" $ - forChain Testnet04 (Testnet04 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) . unsafeChainId <$> [10..19] + , testProperties "testnet04 new chains" $ + forChain Testnet04 (Testnet04 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) . unsafeChainId <$> [10..19] -- Cross check targets to ensure that the values are as expected , testProperty "cross check testnet20InitialHashTarget and mainnet20InitialHashTarget" $ - _hashTarget (Testnet04 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) `div` - _hashTarget (Mainnet01 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) + _hashTarget (Testnet04 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) `div` + _hashTarget (Mainnet01 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) === PowHashNat 8893 , testProperty "cross check development and testnet20InitialHashTarget" $ - _hashTarget (RecapDevelopment ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) `div` - _hashTarget (Testnet04 ^?! versionGenesis . genesisBlockTarget . onChain (unsafeChainId 10)) + _hashTarget (RecapDevelopment ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) `div` + _hashTarget (Testnet04 ^?! versionGenesis . genesisBlockTarget . atChain (unsafeChainId 10)) === PowHashNat 20321 ] where - forChain v target cid = (show cid, v ^?! versionGenesis . genesisBlockTarget . onChain cid === target) + forChain v target cid = (show cid, v ^?! versionGenesis . genesisBlockTarget . atChain cid === target) decodePowHashNat64 t = runGetS decodePowHashNat =<< decodeB64UrlNoPaddingText t decodePowHashNatJson t = decodeStrictOrThrow' @_ @PowHashNat $ "\"" <> t <> "\"" diff --git a/test/unit/Chainweb/Test/BlockHeader/Validation.hs b/test/unit/Chainweb/Test/BlockHeader/Validation.hs index 273db49a70..031da3da83 100644 --- a/test/unit/Chainweb/Test/BlockHeader/Validation.hs +++ b/test/unit/Chainweb/Test/BlockHeader/Validation.hs @@ -56,7 +56,7 @@ import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet -import Chainweb.Version.Testnet +import Chainweb.Version.Testnet04 import Numeric.AffineSpace @@ -73,7 +73,7 @@ tests = testGroup "Chainweb.Test.Blockheader.Validation" , prop_featureFlag (barebonesTestVersion petersonChainGraph) 10 , testProperty "validate arbitrary test header" prop_validateArbitrary , testProperty "validate arbitrary test header for mainnet" $ prop_validateArbitrary Mainnet01 - , testProperty "validate arbitrary test header for testnet" $ prop_validateArbitrary Testnet04 + , testProperty "validate arbitrary test header for testnet04" $ prop_validateArbitrary Testnet04 , testProperty "validate arbitrary test header for devnet" $ prop_validateArbitrary RecapDevelopment ] diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 9b3d0d957f..d1fa8aa936 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -67,7 +67,7 @@ import Chainweb.Test.Cut import Chainweb.CutDB import Chainweb.CutDB.RestAPI.Server import Chainweb.Miner.Pact -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.RocksDB diff --git a/test/unit/Chainweb/Test/Mempool.hs b/test/unit/Chainweb/Test/Mempool.hs index af231ce362..bc2b06929d 100644 --- a/test/unit/Chainweb/Test/Mempool.hs +++ b/test/unit/Chainweb/Test/Mempool.hs @@ -211,9 +211,10 @@ propBadlistPreblock (txs, badTxs) _ mempool = runExceptT $ do where badHashes = HashSet.fromList $ map hash badTxs - preblockCheck _ _ ts = - let hashes = V.map hash ts - in return $! V.map (not . flip HashSet.member badHashes) hashes + preblockCheck _ _ ts = return $ + V.map + (\tx -> if hash tx `HashSet.member` badHashes then Left InsertErrorBadlisted else Right tx) + ts txcfg = mempoolTxConfig mempool hash = txHasher txcfg diff --git a/test/unit/Chainweb/Test/Mempool/InMem.hs b/test/unit/Chainweb/Test/Mempool/InMem.hs index afb6d112f1..f1ebc2ac57 100644 --- a/test/unit/Chainweb/Test/Mempool/InMem.hs +++ b/test/unit/Chainweb/Test/Mempool/InMem.hs @@ -7,13 +7,11 @@ import Control.Concurrent.MVar import qualified Data.Vector as V import Test.Tasty ------------------------------------------------------------------------------ -import Chainweb.Graph (singletonChainGraph) import qualified Chainweb.Mempool.InMem as InMem import Chainweb.Mempool.InMemTypes (InMemConfig(..)) import Chainweb.Mempool.Mempool import Chainweb.Test.Mempool (InsertCheck, MempoolWithFunc(..)) import qualified Chainweb.Test.Mempool -import Chainweb.Test.TestVersions import Chainweb.Utils (Codec(..)) ------------------------------------------------------------------------------ @@ -26,7 +24,8 @@ tests = testGroup "Chainweb.Test.Mempool" wf f = do mv <- newMVar (pure . V.map Right) let cfg = InMemConfig txcfg mockBlockGasLimit 0 2048 Right (checkMv mv) (1024 * 10) - InMem.withInMemoryMempool cfg (barebonesTestVersion singletonChainGraph) $ f mv + mp <- InMem.startInMemoryMempoolTest cfg + f mv mp checkMv :: MVar (t -> IO b) -> t -> IO b checkMv mv xs = do diff --git a/test/unit/Chainweb/Test/Mempool/RestAPI.hs b/test/unit/Chainweb/Test/Mempool/RestAPI.hs index 34151a8a06..51ad9e6df2 100644 --- a/test/unit/Chainweb/Test/Mempool/RestAPI.hs +++ b/test/unit/Chainweb/Test/Mempool/RestAPI.hs @@ -68,12 +68,12 @@ newTestServer = mask_ $ do f <- readMVar mv f xs - server inMemCfg inmemMv envMv restore = - InMem.withInMemoryMempool inMemCfg version $ \inmem -> do - putMVar inmemMv inmem - restore $ withTestAppServer True (return $! mkApp inmem) mkEnv $ \env -> do - putMVar envMv env - atomically retry + server inMemCfg inmemMv envMv restore = do + inmem <- InMem.startInMemoryMempoolTest inMemCfg + putMVar inmemMv inmem + restore $ withTestAppServer True (return $! mkApp inmem) mkEnv $ \env -> do + putMVar envMv env + atomically retry version :: ChainwebVersion version = barebonesTestVersion singletonChainGraph diff --git a/test/unit/Chainweb/Test/Mempool/Sync.hs b/test/unit/Chainweb/Test/Mempool/Sync.hs index 5a123429bd..e6ccc27761 100644 --- a/test/unit/Chainweb/Test/Mempool/Sync.hs +++ b/test/unit/Chainweb/Test/Mempool/Sync.hs @@ -20,13 +20,11 @@ import Test.QuickCheck hiding ((.&.)) import Test.QuickCheck.Monadic import Test.Tasty ------------------------------------------------------------------------------ -import Chainweb.Graph (singletonChainGraph) import Chainweb.Mempool.InMem import Chainweb.Mempool.InMemTypes import Chainweb.Mempool.Mempool import Chainweb.Test.Mempool (InsertCheck, MempoolWithFunc(..), lookupIsPending, mempoolProperty) -import Chainweb.Test.TestVersions (barebonesTestVersion) import Chainweb.Utils (Codec(..)) ------------------------------------------------------------------------------ @@ -40,7 +38,7 @@ tests = testGroup "Chainweb.Mempool.sync" wf f = do mv <- newMVar (pure . V.map Right) let cfg = InMemConfig txcfg mockBlockGasLimit 0 2048 Right (checkMv mv) (1024 * 10) - withInMemoryMempool cfg (barebonesTestVersion singletonChainGraph) $ f mv + f mv =<< startInMemoryMempoolTest cfg checkMv :: MVar (t -> IO b) -> t -> IO b checkMv mv xs = do @@ -74,48 +72,48 @@ propSync -> InsertCheck -> MempoolBackend MockTx -> IO (Either String ()) -propSync (txs, missing, later) _ localMempool' = - withInMemoryMempool testInMemCfg (barebonesTestVersion singletonChainGraph) $ \remoteMempool -> do - mempoolInsert localMempool' CheckedInsert txsV - mempoolInsert remoteMempool CheckedInsert txsV - mempoolInsert remoteMempool CheckedInsert missingV - - doneVar <- newEmptyMVar - syncFinished <- newEmptyMVar - - let nmissing = V.length missingV - let nlater = V.length laterV - let onInitialSyncFinished = tryPutMVar syncFinished () - let onFinalSyncFinished = putMVar doneVar () - localMempool <- - timebomb nmissing onInitialSyncFinished =<< - timebomb (nmissing + nlater) onFinalSyncFinished localMempool' - let syncThread = syncMempools noLog 10 localMempool remoteMempool - - - -- expect remote to deliver transactions during sync. - -- Timeout to guard against waiting forever - m <- timeout 20_000_000 $ do - Async.withAsync syncThread $ \_ -> do - -- Wait until time bomb 1 goes off - takeMVar syncFinished - - -- We should now be subscribed and waiting for V.length laterV - -- more transactions before getting killed. Transactions - -- inserted into remote should get synced to us. - mempoolInsert remoteMempool CheckedInsert laterV - - -- wait until time bomb 2 goes off - takeMVar doneVar - - maybe (fail "timeout") return m - - -- we synced the right number of transactions. verify they're all there. - runExceptT $ do - liftIO (mempoolLookup localMempool missingHashes) >>= - V.mapM_ lookupIsPending - liftIO (mempoolLookup localMempool laterHashes) >>= - V.mapM_ lookupIsPending +propSync (txs, missing, later) _ localMempool' = do + remoteMempool <- startInMemoryMempoolTest testInMemCfg + mempoolInsert localMempool' CheckedInsert txsV + mempoolInsert remoteMempool CheckedInsert txsV + mempoolInsert remoteMempool CheckedInsert missingV + + doneVar <- newEmptyMVar + syncFinished <- newEmptyMVar + + let nmissing = V.length missingV + let nlater = V.length laterV + let onInitialSyncFinished = tryPutMVar syncFinished () + let onFinalSyncFinished = putMVar doneVar () + localMempool <- + timebomb nmissing onInitialSyncFinished =<< + timebomb (nmissing + nlater) onFinalSyncFinished localMempool' + let syncThread = syncMempools noLog 10 localMempool remoteMempool + + + -- expect remote to deliver transactions during sync. + -- Timeout to guard against waiting forever + m <- timeout 20_000_000 $ do + Async.withAsync syncThread $ \_ -> do + -- Wait until time bomb 1 goes off + takeMVar syncFinished + + -- We should now be subscribed and waiting for V.length laterV + -- more transactions before getting killed. Transactions + -- inserted into remote should get synced to us. + mempoolInsert remoteMempool CheckedInsert laterV + + -- wait until time bomb 2 goes off + takeMVar doneVar + + maybe (fail "timeout") return m + + -- we synced the right number of transactions. verify they're all there. + runExceptT $ do + liftIO (mempoolLookup localMempool missingHashes) >>= + V.mapM_ lookupIsPending + liftIO (mempoolLookup localMempool laterHashes) >>= + V.mapM_ lookupIsPending where noLog = const $ const $ return () diff --git a/test/unit/Chainweb/Test/Pact/VerifierPluginTest.hs b/test/unit/Chainweb/Test/Pact/VerifierPluginTest.hs deleted file mode 100644 index 10ca538c28..0000000000 --- a/test/unit/Chainweb/Test/Pact/VerifierPluginTest.hs +++ /dev/null @@ -1,14 +0,0 @@ -module Chainweb.Test.Pact.VerifierPluginTest -( tests -) where - -import Test.Tasty - -import qualified Chainweb.Test.Pact.VerifierPluginTest.Transaction -import qualified Chainweb.Test.Pact.VerifierPluginTest.Unit - -tests :: TestTree -tests = testGroup "Chainweb.Test.Pact.VerifierPluginTest" - [ Chainweb.Test.Pact.VerifierPluginTest.Unit.tests - , Chainweb.Test.Pact.VerifierPluginTest.Transaction.tests - ] \ No newline at end of file diff --git a/test/unit/Chainweb/Test/Pact/Checkpointer.hs b/test/unit/Chainweb/Test/Pact4/Checkpointer.hs similarity index 94% rename from test/unit/Chainweb/Test/Pact/Checkpointer.hs rename to test/unit/Chainweb/Test/Pact4/Checkpointer.hs index b8bb098fd4..2a0c6c39c3 100644 --- a/test/unit/Chainweb/Test/Pact/Checkpointer.hs +++ b/test/unit/Chainweb/Test/Pact4/Checkpointer.hs @@ -7,8 +7,9 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE DataKinds #-} -module Chainweb.Test.Pact.Checkpointer (tests) where +module Chainweb.Test.Pact4.Checkpointer (tests) where import Control.Concurrent.MVar import Control.DeepSeq @@ -51,20 +52,20 @@ import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.MerkleLogHash (merkleLogHash) import Chainweb.MerkleUniverse -import Chainweb.Pact.Backend.ChainwebPactDb -import Chainweb.Pact.Backend.RelationalCheckpointer -import Chainweb.Pact.Backend.Types +import Chainweb.Pact4.Backend.ChainwebPactDb + import Chainweb.Pact.Backend.Utils -import Chainweb.Pact.TransactionExec - (applyContinuation', applyExec', buildExecParsedCode) +import Chainweb.Pact4.TransactionExec import Chainweb.Pact.Types -import Chainweb.Test.Pact.Utils +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 -- -------------------------------------------------------------------------- -- -- Tests @@ -82,8 +83,10 @@ tests = testGroup "Checkpointer" -- Module Name Test testModuleName :: TestTree -testModuleName = withResourceT withTempSQLiteResource $ - runSQLite' $ \resIO -> testCase "testModuleName" $ do +testModuleName = withResourceT withTempSQLiteResource $ \s -> + runSQLite' f s + where + f = \resIO -> testCase "testModuleName" $ do (cp, sql) <- resIO @@ -123,9 +126,9 @@ testModuleName = withResourceT withTempSQLiteResource $ -- Key Set Test testKeyset :: TestTree -testKeyset = withResource initializeSQLite freeSQLiteResource (runSQLite keysetTest) +testKeyset = withResource initializeSQLite freeSQLiteResource $ \s -> runSQLite keysetTest s -keysetTest :: IO (Checkpointer logger) -> TestTree +keysetTest :: Logger logger => IO (Checkpointer logger) -> TestTree keysetTest c = testCaseSteps "Keyset test" $ \next -> do cp <- c let @@ -622,10 +625,10 @@ withRelationalCheckpointerResource :: (Logger logger, logger ~ GenericLogger) => (IO (Checkpointer logger) -> TestTree) -> TestTree -withRelationalCheckpointerResource = - withResource initializeSQLite freeSQLiteResource . runSQLite +withRelationalCheckpointerResource f = + withResource initializeSQLite freeSQLiteResource $ \s -> runSQLite f s -addKeyset :: ChainwebPactDbEnv logger -> KeySetName -> KeySet -> IO () +addKeyset :: PactDbEnv (BlockEnv logger) -> KeySetName -> KeySet -> IO () addKeyset (PactDbEnv pactdb mvar) keysetname keyset = _writeRow pactdb Insert KeySets keysetname keyset mvar @@ -650,12 +653,12 @@ runSQLite' -> TestTree runSQLite' runTest sqlEnvIO = runTest $ do sqlenv <- sqlEnvIO - cp <- initRelationalCheckpointer defaultModuleCacheLimit sqlenv DoNotPersistIntraBlockWrites logger testVer testChainId + 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 -> ChainwebPactDbEnv logger -> Maybe Value -> Text -> IO EvalResult +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 $ @@ -666,7 +669,7 @@ runExec cp pactdbenv eData eCode = do cmdenv = TransactionEnv { _txMode = Transactional , _txDbEnv = pactdbenv - , _txLogger = _cpLogger (_cpReadCp cp) + , _txLogger = cpLogger cp , _txGasLogger = Nothing , _txPublicData = noPublicData , _txSpvSupport = noSPVSupport @@ -680,7 +683,7 @@ runExec cp pactdbenv eData eCode = do } cmdst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty -runCont :: Logger logger => Checkpointer logger -> ChainwebPactDbEnv logger -> PactId -> Int -> IO EvalResult +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 @@ -691,7 +694,7 @@ runCont cp pactdbenv pactId step = do cmdenv = TransactionEnv { _txMode = Transactional , _txDbEnv = pactdbenv - , _txLogger = _cpLogger (_cpReadCp cp) + , _txLogger = cpLogger cp , _txGasLogger = Nothing , _txPublicData = noPublicData , _txSpvSupport = noSPVSupport @@ -711,17 +714,19 @@ runCont cp pactdbenv pactId step = do -- witnessing that we only use the PactDbEnv portion -- of the CurrentBlockDbEnv cpReadFrom - :: Checkpointer logger + :: Logger logger + => Checkpointer logger -> Maybe BlockHeader - -> (ChainwebPactDbEnv logger -> IO q) + -> (PactDbEnv (BlockEnv logger) -> IO q) -> IO q cpReadFrom cp pc f = do - _cpReadFrom - (_cpReadCp cp) + Checkpointer.readFrom + cp (ParentHeader <$> pc) - (f . _cpPactDbEnv) >>= \case + Pact4T + (\env _blockHandle -> f $ (_cpPactDbEnv env)) >>= \case NoHistory -> error $ unwords - [ "Chainweb.Test.Pact.Checkpointer.cpReadFrom:" + [ "Chainweb.Test.Pact4.Checkpointer.cpReadFrom:" , "parent header missing from the database" ] Historical r -> return r @@ -729,13 +734,14 @@ cpReadFrom cp pc f = do -- 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, Monoid q) => Checkpointer logger -> Maybe BlockHeader - -> [(BlockHeader, ChainwebPactDbEnv logger -> IO q)] + -> [(BlockHeader, PactDbEnv (BlockEnv logger) -> IO q)] -> IO q -cpRestoreAndSave cp pc blks = snd <$> _cpRestoreAndSave cp (ParentHeader <$> pc) - (traverse Stream.yield [RunnableBlock $ \dbEnv _ -> (,bh) <$> fun (_cpPactDbEnv dbEnv) | (bh, fun) <- blks]) +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 diff --git a/test/unit/Chainweb/Test/Pact/DbCacheTest.hs b/test/unit/Chainweb/Test/Pact4/DbCacheTest.hs similarity index 92% rename from test/unit/Chainweb/Test/Pact/DbCacheTest.hs rename to test/unit/Chainweb/Test/Pact4/DbCacheTest.hs index 0150773817..52cc6f07af 100644 --- a/test/unit/Chainweb/Test/Pact/DbCacheTest.hs +++ b/test/unit/Chainweb/Test/Pact4/DbCacheTest.hs @@ -1,7 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} -module Chainweb.Test.Pact.DbCacheTest (tests) where +module Chainweb.Test.Pact4.DbCacheTest (tests) where import Chainweb.Pact.Backend.DbCache @@ -19,7 +19,7 @@ import Test.Tasty import Test.Tasty.HUnit tests :: TestTree -tests = testGroup "Chainweb.Test.Pact.DbCacheTest" +tests = testGroup "Chainweb.Test.Pact4.DbCacheTest" [ testCache ] entry :: MonadIO m => String -> m ([String], Int) @@ -80,7 +80,7 @@ testCache = testCase "testCache" $ do where doCheck msg k v txid = do - mc <- StateT (checkDbCache (Utf8 k) (toStrict (encode v)) txid) + 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/Pact/GrandHash.hs b/test/unit/Chainweb/Test/Pact4/GrandHash.hs similarity index 98% rename from test/unit/Chainweb/Test/Pact/GrandHash.hs rename to test/unit/Chainweb/Test/Pact4/GrandHash.hs index a0d8dd683a..f2eb40553c 100644 --- a/test/unit/Chainweb/Test/Pact/GrandHash.hs +++ b/test/unit/Chainweb/Test/Pact4/GrandHash.hs @@ -6,7 +6,7 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} -module Chainweb.Test.Pact.GrandHash +module Chainweb.Test.Pact4.GrandHash ( tests ) where @@ -45,7 +45,7 @@ import Chainweb.Test.Utils tests :: TestTree tests = - independentSequentialTestGroup "Chainweb.Test.Pact.GrandHash" + 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 diff --git a/test/unit/Chainweb/Test/Pact/ModuleCacheOnRestart.hs b/test/unit/Chainweb/Test/Pact4/ModuleCacheOnRestart.hs similarity index 93% rename from test/unit/Chainweb/Test/Pact/ModuleCacheOnRestart.hs rename to test/unit/Chainweb/Test/Pact4/ModuleCacheOnRestart.hs index 613baa9d9d..2c0b25b6c3 100644 --- a/test/unit/Chainweb/Test/Pact/ModuleCacheOnRestart.hs +++ b/test/unit/Chainweb/Test/Pact4/ModuleCacheOnRestart.hs @@ -8,7 +8,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} -module Chainweb.Test.Pact.ModuleCacheOnRestart (tests) where +module Chainweb.Test.Pact4.ModuleCacheOnRestart (tests) where import Control.Concurrent.MVar.Strict import Control.DeepSeq (NFData) @@ -40,9 +40,8 @@ import Chainweb.ChainId import Chainweb.Graph import Chainweb.Logger import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types + import Chainweb.Pact.PactService -import Chainweb.Pact.Service.Types import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore @@ -50,13 +49,15 @@ import Chainweb.Time import Chainweb.Test.Cut import Chainweb.Test.Cut.TestBlockDb import Chainweb.Test.Utils -import Chainweb.Test.Pact.Utils(getPWOByHeader) +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 @@ -80,7 +81,7 @@ tests rdb = withResource' newEmptyMVar $ \rewindDataM -> withResource' (mkTestBlockDb testVer rdb) $ \bdbio -> withResourceT withTempSQLiteResource $ \ioSqlEnv -> - independentSequentialTestGroup "Chainweb.Test.Pact.ModuleCacheOnRestart" + 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) @@ -129,7 +130,7 @@ testCoinbase iobdb = (initPayloadState >> doCoinbase,snapshotCache) bdb <- liftIO iobdb bip <- throwIfNoHistory =<< execNewBlock mempty noMiner NewBlockFill (ParentHeader (genesisBlockHeader testVer testChainId)) - let pwo = blockInProgressToPayloadWithOutputs bip + 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) @@ -222,7 +223,7 @@ testCw217CoinOnly iobdb _rewindM = (go, go') go' ioa initCache = do snapshotCache ioa initCache case M.lookup 20 initCache of - Just a -> assertEqual "module init cache contains only coin" ["coin"] (moduleCacheKeys a) + Just a -> assertEqual "module init cache contains only coin" ["coin"] (Pact4.moduleCacheKeys a) Nothing -> assertFailure "failed to lookup block at 20" assertNoCacheMismatch @@ -252,8 +253,8 @@ doNextCoinbase iobdb = do _ <- execValidateBlock mempty prevH (CheckablePayloadWithOutputs pwo') bip <- throwIfNoHistory =<< execNewBlock mempty noMiner NewBlockFill (ParentHeader prevH) - let prevH' = _blockInProgressParentHeader bip - let pwo = blockInProgressToPayloadWithOutputs bip + 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 @@ -271,14 +272,14 @@ doNextCoinbaseN_ n iobdb = fmap last $ replicateM n $ doNextCoinbase iobdb justModuleHashes :: ModuleInitCache -> SHM.StableHashMap ModuleName (Maybe ModuleHash) justModuleHashes = justModuleHashes' . snd . last . M.toList -justModuleHashes' :: ModuleCache -> SHM.StableHashMap ModuleName (Maybe ModuleHash) +justModuleHashes' :: Pact4.ModuleCache -> SHM.StableHashMap ModuleName (Maybe ModuleHash) justModuleHashes' = - fmap (preview (_1 . mdModule . _MDModule . mHash)) . moduleCacheToHashMap + fmap (preview (_1 . mdModule . _MDModule . mHash)) . Pact4.moduleCacheToHashMap initPayloadState :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) => PactServiceM logger tbl () -initPayloadState = initialPayloadState mempty testVer testChainId +initPayloadState = initialPayloadState testVer testChainId snapshotCache :: IO (MVar ModuleInitCache) -> ModuleInitCache -> IO () snapshotCache iomcache initCache = do diff --git a/test/unit/Chainweb/Test/Pact/NoCoinbase.hs b/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs similarity index 79% rename from test/unit/Chainweb/Test/Pact/NoCoinbase.hs rename to test/unit/Chainweb/Test/Pact4/NoCoinbase.hs index 9bed0f4ce1..f1d13dc076 100644 --- a/test/unit/Chainweb/Test/Pact/NoCoinbase.hs +++ b/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs @@ -2,7 +2,7 @@ {-# LANGUAGE ScopedTypeVariables #-} -- | --- Module: Chainweb.Test.Pact.NoCoinbase +-- Module: Chainweb.Test.Pact4.NoCoinbase -- Copyright: Copyright © 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -10,7 +10,7 @@ -- -- TODO -- -module Chainweb.Test.Pact.NoCoinbase +module Chainweb.Test.Pact4.NoCoinbase ( tests ) where @@ -23,11 +23,11 @@ import Test.Tasty.HUnit -- internal modules -import Chainweb.Pact.NoCoinbase +import Chainweb.Pact4.NoCoinbase import Chainweb.Payload tests :: TestTree -tests = testGroup "Chainweb.Test.Pact.NoCoinbase" +tests = testGroup "Chainweb.Test.Pact4.NoCoinbase" [testCase "noCoinbaseOutput is consistent" test_noCoinbase] test_noCoinbase :: Assertion diff --git a/test/unit/Chainweb/Test/Pact/PactExec.hs b/test/unit/Chainweb/Test/Pact4/PactExec.hs similarity index 91% rename from test/unit/Chainweb/Test/Pact/PactExec.hs rename to test/unit/Chainweb/Test/Pact4/PactExec.hs index 3518b599a7..4d515828da 100644 --- a/test/unit/Chainweb/Test/Pact/PactExec.hs +++ b/test/unit/Chainweb/Test/Pact4/PactExec.hs @@ -17,11 +17,12 @@ -- -- Unit test for Pact execution in Chainweb -module Chainweb.Test.Pact.PactExec +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 @@ -41,23 +42,23 @@ import Test.Tasty.HUnit import Chainweb.BlockHeader import Chainweb.BlockHeaderDB (BlockHeaderDb) import Chainweb.ChainId -import Chainweb.Graph +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.ExecBlock +import Chainweb.Pact.PactService.Pact4.ExecBlock import Chainweb.Pact.Types -import Chainweb.Pact.Service.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.InMemory (newPayloadDb) import Chainweb.Storage.Table.RocksDB (RocksDb) -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Test.Utils import Chainweb.Test.TestVersions -import Chainweb.Transaction -import Chainweb.Version (ChainwebVersion(..)) +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) @@ -68,6 +69,7 @@ import Pact.Types.Persistence import Pact.Types.Pretty import qualified Pact.JSON.Encode as J +import Data.Functor.Product testVersion :: ChainwebVersion testVersion = slowForkingCpmTestVersion petersonChainGraph @@ -124,7 +126,7 @@ tests = let genesisHeader = genesisBlockHeader v cid testBlockHeaderDb rdb genesisHeader - label = "Chainweb.Test.Pact.PactExec" + label = "Chainweb.Test.Pact4.PactExec" allowReads = testPactServiceConfig { _pactAllowReadsInLocal = True } logger = dummyLogger @@ -154,7 +156,7 @@ data TestResponse a = TestResponse } deriving (Generic, Show) -type TxsTest = (IO (V.Vector ChainwebTransaction), Either String (TestResponse String) -> Assertion) +type TxsTest = (IO (V.Vector Pact4.Transaction), Either String (TestResponse String) -> Assertion) -- -------------------------------------------------------------------------- -- -- sample data @@ -374,10 +376,13 @@ testContinuationGasPayer = (txs,checkResultSuccess test) (pString "Write succeeded") checkPactResultSuccess "contFirstStep" contFirstStep $ assertEqual "contFirstStep" (pString "Step One") - checkPactResultSuccess "balCheck1" balCheck1 $ assertEqual "balCheck1" (pDecimal 100) - checkPactResultSuccess "paidSecondStep" paidSecondStep $ assertEqual "paidSecondStep" + checkPactResultSuccess "balCheck1" balCheck1 $ + assertEqual "balCheck1" (pDecimal 100) + checkPactResultSuccess "paidSecondStep" paidSecondStep $ + assertEqual "paidSecondStep" (pString "Step Two") - checkPactResultSuccess "balCheck2" balCheck2 $ assertEqual "balCheck2" (pDecimal 99.999_5) + checkPactResultSuccess "balCheck2" balCheck2 $ + assertEqual "balCheck2" (pDecimal 99.999_4) test r = assertFailure $ "Expected 6 results, got: " ++ show r testExecGasPayer :: TxsTest @@ -426,7 +431,7 @@ testExecGasPayer = (txs,checkResultSuccess test) (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_5) + checkPactResultSuccess "balCheck2" balCheck2 $ assertEqual "balCheck2" (pDecimal 99.999_4) test r = assertFailure $ "Expected 6 results, got: " ++ show r testFailureRedeem :: TxsTest @@ -449,18 +454,18 @@ testFailureRedeem = (txs,checkResultSuccess test) -- 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 + epsilon tx size gas for [0] + -- miner first is reward + tx gas for [0] checkPactResultSuccess "miner bal 0" mbal0 $ - assertEqual "miner bal 0" (pDecimal 2.344523) + 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.92) + 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.424523) + assertEqual "miner bal 1" (pDecimal 12.904523) test r = assertFailure $ "Expected 5 results, got: " ++ show r @@ -502,14 +507,17 @@ execTest v runPact request = _trEval request $ do trans <- mkCmds cmdStrs results <- runPact $ (throwIfNoHistory =<<) $ readFrom (Just $ ParentHeader $ genesisBlockHeader v cid) $ - execTransactions False defaultMiner - trans (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled True) Nothing Nothing - >>= throwCommandInvalidError + 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) (toHashCommandResult <$> outputs)) - (toHashCommandResult $ _transactionCoinbase results) + (zip (_trCmds request) (hashPact4TxLogs <$> outputs)) + (hashPact4TxLogs $ _transactionCoinbase results) where mkCmds cmdStrs = fmap V.fromList $ forM (zip cmdStrs [0..]) $ \(code,n :: Int) -> @@ -533,20 +541,23 @@ execTxsTest v runPact name (trans',check) = testCase name (go >>= check) trans <- trans' results' <- tryAllSynchronous $ runPact $ (throwIfNoHistory =<<) $ readFrom (Just $ ParentHeader $ genesisBlockHeader v cid) $ - execTransactions False defaultMiner trans - (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled True) Nothing Nothing - >>= throwCommandInvalidError + 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 . payloadObj . _cmdPayload + tcode = _pNonce . Pact4.payloadObj . _cmdPayload inputs = map (showPretty . tcode) $ V.toList trans return $ TestResponse - (zip inputs (toHashCommandResult <$> outputs)) - (toHashCommandResult $ _transactionCoinbase results) + (zip inputs (hashPact4TxLogs <$> outputs)) + (hashPact4TxLogs $ _transactionCoinbase results) Left e -> return $ Left $ show e -type LocalTest = (IO ChainwebTransaction,Either String (CommandResult Hash) -> Assertion) +type LocalTest = (IO Pact4.Transaction,Either String (CommandResult Hash) -> Assertion) execLocalTest :: (Logger logger, CanReadablePayloadCas tbl) @@ -558,14 +569,16 @@ execLocalTest runPact name (trans',check) = testCase name (go >>= check) where go = do trans <- trans' - results' <- tryAllSynchronous $ runPact $ - execLocal trans Nothing Nothing Nothing + results' <- tryAny $ runPact $ + execLocal (Pact4.unparseTransaction trans) Nothing Nothing Nothing case results' of Right (MetadataValidationFailure e) -> return $ Left $ show e Right LocalTimeout -> return $ Left "LocalTimeout" Right (LocalResultLegacy cr) -> return $ Right cr Right (LocalResultWithWarns cr _) -> return $ Right cr + Right (LocalPact5PreflightResult _ _) -> error "Pact 5" + Right (LocalPact5ResultLegacy _) -> error "Pact 5" Left e -> return $ Left $ show e getPactCode :: TestSource -> IO Text @@ -624,7 +637,7 @@ _showValidationFailure = do } miner = defaultMiner header = genesisBlockHeader testVersion $ someChainId testVersion - pwo = toPayloadWithOutputs miner outs1 + pwo = toPayloadWithOutputs Pact4T miner outs1 cr2 = set crGas 1 cr1 outs2 = Transactions { _transactionPairs = V.zip txs (V.singleton cr2) @@ -633,5 +646,5 @@ _showValidationFailure = do r = validateHashes header (CheckablePayloadWithOutputs pwo) miner outs2 BL.putStrLn $ case r of - Left e -> J.encode e + Left e -> J.encode $ J.text (sshow e) Right x -> encode x diff --git a/test/unit/Chainweb/Test/Pact/PactMultiChainTest.hs b/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs similarity index 82% rename from test/unit/Chainweb/Test/Pact/PactMultiChainTest.hs rename to test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs index 0fdf0ff235..6ed782b989 100644 --- a/test/unit/Chainweb/Test/Pact/PactMultiChainTest.hs +++ b/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs @@ -8,7 +8,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -module Chainweb.Test.Pact.PactMultiChainTest +module Chainweb.Test.Pact4.PactMultiChainTest ( tests ) where @@ -23,15 +23,14 @@ 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.Map qualified as M 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 -import System.IO.Unsafe -- internal modules @@ -56,30 +55,30 @@ import Chainweb.ChainId import Chainweb.Cut import Chainweb.Mempool.Mempool import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.PactService -import Chainweb.Pact.Service.Types -import Chainweb.Pact.TransactionExec (listErrMsg) + +import Chainweb.Pact.Types +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Pact4.TransactionExec (listErrMsg) import Chainweb.Payload import Chainweb.SPV.CreateProof import Chainweb.Test.Cut import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact.Utils +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.Registry import Chainweb.WebPactExecutionService import Chainweb.Payload.PayloadStore (lookupPayloadWithHeight) +import Chainweb.Pact.Backend.Types testVersion :: ChainwebVersion testVersion = slowForkingCpmTestVersion peterson -quirkedGasTestVersion :: RequestKey -> ChainwebVersion -quirkedGasTestVersion = quirkedGasSlowForkingCpmTestVersion peterson +-- quirkedGasTestVersion :: RequestKey -> ChainwebVersion +-- quirkedGasTestVersion = quirkedGasSlowForkingCpmTestVersion peterson cid :: ChainId cid = unsafeChainId 9 @@ -90,7 +89,7 @@ data MultiEnv = MultiEnv , _menvPact :: !WebPactExecutionService , _menvPacts :: !(HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) , _menvCompactedPacts :: !(HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) - , _menvMpa :: !(IO (IORef MemPoolAccess)) + , _menvMpa :: !(ChainMap (IORef MemPoolAccess)) , _menvMiner :: !Miner , _menvChainId :: !ChainId } @@ -103,12 +102,12 @@ makeLenses ''MultiEnv type PactTestM = ReaderT MultiEnv IO newtype MempoolCmdBuilder = MempoolCmdBuilder - { _mempoolCmdBuilder :: BlockHeader -> CmdBuilder + { _mempoolCmdBuilder :: ChainId -> BlockCreationTime -> CmdBuilder } -- | Block filler. A 'Nothing' result means "skip this filler". newtype MempoolBlock = MempoolBlock - { _mempoolBlock :: BlockHeader -> Maybe [MempoolCmdBuilder] + { _mempoolBlock :: ChainId -> BlockCreationTime -> Maybe [MempoolCmdBuilder] } -- | Mempool with an ordered list of fillers. @@ -126,42 +125,49 @@ data PactTxTest = PactTxTest tests :: TestTree tests = testGroup testName - [ test generousConfig freeGasModel "pact4coin3UpgradeTest" pact4coin3UpgradeTest - , test generousConfig freeGasModel "pact42UpgradeTest" pact42UpgradeTest - , test generousConfig freeGasModel "minerKeysetTest" minerKeysetTest - , test timeoutConfig freeGasModel "txTimeoutTest" txTimeoutTest - , test generousConfig getGasModel "chainweb213Test" chainweb213Test - , test generousConfig getGasModel "pact43UpgradeTest" pact43UpgradeTest - , test generousConfig getGasModel "pact431UpgradeTest" pact431UpgradeTest - , test generousConfig getGasModel "chainweb215Test" chainweb215Test - , test generousConfig getGasModel "chainweb216Test" chainweb216Test - , test generousConfig getGasModel "pact45UpgradeTest" pact45UpgradeTest - , test generousConfig getGasModel "pact46UpgradeTest" pact46UpgradeTest - , test generousConfig getGasModel "chainweb219UpgradeTest" chainweb219UpgradeTest - , test generousConfig getGasModel "pactLocalDepthTest" pactLocalDepthTest - , test generousConfig getGasModel "pact48UpgradeTest" pact48UpgradeTest - , test generousConfig getGasModel "pact49UpgradeTest" pact49UpgradeTest - , test generousConfig getGasModel "pact410UpgradeTest" pact410UpgradeTest - , test generousConfig getGasModel "chainweb223Test" chainweb223Test - , test generousConfig getGasModel "compactAndSyncTest" compactAndSyncTest - , test generousConfig getGasModel "compactionCompactsUnmodifiedTables" compactionCompactsUnmodifiedTables - , quirkTest + [ 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 + , test generousConfig "checkTransferCreate" checkTransferCreate ] + where - testName = "Chainweb.Test.Pact.PactMultiChainTest" + testName = "Chainweb.Test.Pact4.PactMultiChainTest" -- This is way more than what is used in production, but during testing -- we can be generous. - generousConfig = testPactServiceConfig { _pactBlockGasLimit = 300_000 } - timeoutConfig = testPactServiceConfig { _pactBlockGasLimit = 100_000 } - - test pactConfig gasmodel tname f = - withDelegateMempool $ \dmpio -> testCaseSteps tname $ \step -> + generousConfig = testPactServiceConfig { _pactNewBlockGasLimit = 300_000 } + timeoutConfig = testPactServiceConfig + { _pactNewBlockGasLimit = 300_000_000 + , _pactTxTimeLimit = Just 10_000 + } + + test pactConfig tname f = + testCaseSteps tname $ \step -> withTestBlockDb testVersion $ \bdb -> do - (iompa,mpa) <- dmpio + mempools <- onAllChains testVersion $ \_chain -> do + r <- newIORef mempty + return (r, delegateMemPoolAccess r) let logger = hunitDummyLogger step - withWebPactExecutionServiceCompaction logger testVersion pactConfig bdb mpa gasmodel $ \(pact,pacts,_cPact,cPacts) -> + withWebPactExecutionServiceCompaction logger testVersion pactConfig bdb (snd <$> mempools) $ \(pact,pacts,_cPact,cPacts) -> runReaderT f $ - MultiEnv bdb pact pacts cPacts (return iompa) noMiner cid + MultiEnv bdb pact pacts cPacts (fst <$> mempools) noMiner cid minerKeysetTest :: PactTestM () minerKeysetTest = do @@ -176,7 +182,7 @@ minerKeysetTest = do -- run block 5 (fork for chainweb213) r <- try $ runCut' assertSatisfies "badMiner fails after fork" r $ \case - Left (CoinbaseFailure t) -> "Invalid miner key" `T.isInfixOf` t + Left (CoinbaseFailure (Pact4CoinbaseFailure t)) -> "Invalid miner key" `T.isInfixOf` t _ -> False where @@ -191,23 +197,26 @@ txTimeoutTest = do -- we inline some of runBlockTest here because its assertions -- don't make sense for tx timeout let pts = - [ buildBasic $ mkExec' "(+ 1 1)" - , buildBasicGas 1000 $ mkExec' "(enumerate 0 999999999999)" - , buildBasic $ mkExec' "(+ 2 2)" + [ 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 + mempoolBadlistRef <- setPactMempool chid $ PactMempool $ List.singleton - $ blockForChain chid - $ MempoolBlock $ \_ -> pure pts + $ 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)) @@ -231,7 +240,7 @@ chainweb213Test = do -- run block 25 runBlockTest [ PactTxTest buildModCmd1 $ - assertTxGas "Old gas cost" 56 + assertTxGas "Old gas cost" 47 , PactTxTest (buildSimpleCmd' "(list 1 2 3)") $ assertTxFailure "list failure 1_1" listErrMsg @@ -239,27 +248,27 @@ chainweb213Test = do assertTxSuccess "mod db installs" $ pString "TableCreated" , PactTxTest (buildSimpleCmd' "(free.dbmod.fkeys)") $ - assertTxGas "fkeys gas cost 1" 214 + assertTxGas "fkeys gas cost 1" 205 , PactTxTest (buildSimpleCmd' "(free.dbmod.ffolddb)") $ - assertTxGas "ffolddb gas cost 1" 215 + assertTxGas "ffolddb gas cost 1" 206 , PactTxTest (buildSimpleCmd' "(free.dbmod.fselect)") $ - assertTxGas "fselect gas cost 1" 215 + assertTxGas "fselect gas cost 1" 206 ] -- run block 26 runBlockTest [ PactTxTest buildModCmd2 $ - assertTxGas "New gas cost" 60065 + assertTxGas "New gas cost" 60056 , PactTxTest (buildSimpleCmd' "(list 1 2 3)") $ assertTxFailure "list failure 2_1" - "Gas limit (50000) exceeded: 1000013" + "Gas limit (50000) exceeded: 1000004" , PactTxTest (buildSimpleCmd' "(free.dbmod.fkeys)") $ - assertTxGas "fkeys gas cost 2" 40014 + assertTxGas "fkeys gas cost 2" 40005 , PactTxTest (buildSimpleCmd' "(free.dbmod.ffolddb)") $ - assertTxGas "ffolddb gas cost 2" 40015 + assertTxGas "ffolddb gas cost 2" 40006 , PactTxTest (buildSimpleCmd' "(free.dbmod.fselect)") $ - assertTxGas "fselect gas cost 2" 40015 + assertTxGas "fselect gas cost 2" 40006 ] @@ -287,22 +296,22 @@ pactLocalDepthTest = do runToHeight 53 runBlockTest [ PactTxTest (buildCoinXfer "(coin.transfer 'sender00 'sender01 1.0)") $ - assertTxGas "Coin transfer pre-fork" 1583 + assertTxGas "Coin transfer pre-fork" 1574 ] runBlockTest [ PactTxTest (buildCoinXfer "(coin.transfer 'sender00 'sender01 1.0)") $ - assertTxGas "Coin post-fork" 1583 + 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.6834) + 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.6834) + 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.8417) + 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) @@ -338,14 +347,14 @@ pact45UpgradeTest = do [ 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" 35 + 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" 20 + 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" 1583 + assertTxGas "Coin transfer pre-fork" 1574 ] -- chainweb217 fork runBlockTest @@ -359,7 +368,7 @@ pact45UpgradeTest = do (buildBasicGas 70_000 $ tblModule "tBl") $ assertTxFailure "mod53 table update fails after fork" "" , PactTxTest (buildCoinXfer "(coin.transfer 'sender00 'sender01 1.0)") $ - assertTxGas "Coin post-fork" 709 + assertTxGas "Coin post-fork" 700 ] -- run local to check error lr <- runLocal "0" cid $ set cbGasLimit 70_000 $ set cbRPC (tblModule "tBl") $ defaultCmd @@ -380,7 +389,7 @@ pact45UpgradeTest = do $ mkExec' code where coinCaps = [ mkGasCap, mkTransferCap "sender00" "sender01" 1.0 ] - coinTxBuyTransferGas = 216 + coinTxBuyTransferGas = 206 buildSimpleCmd code = buildBasicGas 3000 $ mkExec' code @@ -391,7 +400,7 @@ runLocalWithDepth :: T.Text -> Maybe RewindDepth -> ChainId -> CmdBuilder -> Pac runLocalWithDepth nonce depth cid' cmd = do pact <- getPactService cid' cwCmd <- buildCwCmd nonce testVersion cmd - liftIO $ try @_ @PactException $ _pactLocal pact Nothing Nothing depth cwCmd + liftIO $ try @_ @PactException $ _pactLocal pact Nothing Nothing depth (Pact4.unparseTransaction cwCmd) getPactService :: ChainId -> PactTestM PactExecutionService getPactService cid' = do @@ -427,7 +436,7 @@ pact43UpgradeTest = do runBlockTest [ PactTxTest buildMod $ - assertTxGas "Old gas cost" 120332 + assertTxGas "Old gas cost" 120323 , PactTxTest buildModPact $ assertTxFailure "Should not resolve new pact native: continue" @@ -452,7 +461,7 @@ pact43UpgradeTest = do runBlockTest [ PactTxTest buildMod $ - assertTxGas "Old gas cost" 120296 + assertTxGas "Old gas cost" 120287 , PactTxTest buildModPact $ assertTxSuccess "Should resolve continue in a module defn" @@ -527,7 +536,7 @@ chainweb215Test = do -- execute pre-fork xchain transfer (blocc0) runBlockTest [ PactTxTest xsend $ \cr -> do - evs <- mkSendEvents 0.0426 v4Hash + evs <- mkSendEvents 0.0416 v4Hash assertTxEvents "Transfer events @ block 31" evs cr ] send0 <- txResult 0 @@ -542,7 +551,7 @@ chainweb215Test = do let blockSend42 = [ PactTxTest xsend $ \cr -> do - evs <- mkSendEvents 0.0429 v5Hash + evs <- mkSendEvents 0.0419 v5Hash assertTxEvents "Transfer events @ block 42 - post-fork send" evs cr ] blockRecv42 = @@ -551,10 +560,12 @@ chainweb215Test = do assertTxEvents "Transfer events @ block 42" evs cr ] - void $ setPactMempool $ PactMempool - [ testsToBlock cid blockSend42 - , testsToBlock chain0 blockRecv42 - ] + void $ setPactMempool cid $ PactMempool + [ testsToBlock blockSend42 ] + + void $ setPactMempool chain0 $ PactMempool + [ testsToBlock blockRecv42 ] + runCut' withChain cid $ testCurrentBlock blockSend42 withChain chain0 $ testCurrentBlock blockRecv42 @@ -605,7 +616,7 @@ chainweb215Test = do ] mkRecdEvents h h' = sequence - [ mkTransferEvent "sender00" "NoMiner" 0.0258 "coin" h + [ 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" @@ -705,17 +716,17 @@ pact42UpgradeTest = do -- 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] - ] + -- 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" [] @@ -763,9 +774,9 @@ chainweb216Test = do runBlockTest [ PactTxTest (buildSimpleCmd formatGas) $ - assertTxGas "Pre-fork format gas" 21 + assertTxGas "Pre-fork format gas" 11 , PactTxTest (buildSimpleCmd tryGas) $ - assertTxGas "Pre-fork try" 19 + assertTxGas "Pre-fork try" 9 , PactTxTest (buildSimpleCmd defineNonNamespacedPreFork) $ assertTxSuccess "Should pass when defining a non-namespaced keyset" @@ -779,9 +790,9 @@ chainweb216Test = do runBlockTest [ PactTxTest (buildSimpleCmd formatGas) $ - assertTxGas "Post-fork format gas increase" 48 + assertTxGas "Post-fork format gas increase" 38 , PactTxTest (buildSimpleCmd tryGas) $ - assertTxGas "Post-fork try should charge a bit more gas" 20 + 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" @@ -1083,16 +1094,16 @@ pact48UpgradeTest = do -- run block 84 (before the pact48 fork) runBlockTest - [ PactTxTest runConcat $ assertTxGas "Old concat gas cost" 231 - , PactTxTest runFormat $ assertTxGas "Old format gas cost" 238 - , PactTxTest runReverse $ assertTxGas "Old reverse gas cost" 4232 + [ 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" 280 - , PactTxTest runFormat $ assertTxGas "New format gas cost" 233 - , PactTxTest runReverse $ assertTxGas "New reverse gas cost" 4272 + [ 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 @@ -1166,7 +1177,7 @@ pact410UpgradeTest = do (pBool True) , PactTxTest bareSignerPrefixedKey $ - assertTxFailure + assertTxFailure' "WebAuthn prefixed keys should not be enforceable with bare signers" "Keyset failure (keys-all): [WEBAUTHN...]" @@ -1186,12 +1197,12 @@ pact410UpgradeTest = do (pBool True) , PactTxTest prefixedSignerBareKey $ - assertTxFailure + assertTxFailure' "WebAuthn bare keys should throw an error when read" "Invalid keyset" , PactTxTest invalidPrefixedKey $ - assertTxFailure + assertTxFailure' "Invalid WebAuthn prefixed keys should throw an error when read" "Invalid keyset" @@ -1241,36 +1252,37 @@ chainweb223Test :: PactTestM () chainweb223Test = do -- run past genesis, upgrades - runToHeight 119 + 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")) - ] + -- 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 + [ + -- 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]]] @@ -1287,10 +1299,11 @@ compactAndSyncTest = do 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 @@ -1334,56 +1347,84 @@ compactionCompactsUnmodifiedTables = do -- due to a duplicate row syncTo afterWrite -quirkTest :: TestTree -quirkTest = do - -- fake stand-in for the request key of the quirked command, so that we can - -- construct the version without knowing the request key of the command - -- that will use the version. - -- TODO: make this nicer once MempoolCmdBuilder is gone. - let fakeReqKey = RequestKey (Hash mempty) - let fakeVersion = quirkedGasTestVersion fakeReqKey - let genesisHeader = genesisBlockHeader fakeVersion cid - let mempoolCmdBuilder = - buildBasic (mkExec' "(+ 1 2)") - -- this is how setPactMempool builds the commands - -- we fake the header here to ensure the request key is consistent later on - let cmd = unsafeDupablePerformIO $ - buildCwCmd - (sshow genesisHeader) - fakeVersion - (_mempoolCmdBuilder mempoolCmdBuilder genesisHeader) - -- once we have the request key, we can make it a quirk in the version - -- we use to actually run the command. - let !realReqKey = RequestKey (toUntypedHash $ _cmdHash cmd) - let realVersion = quirkedGasTestVersion realReqKey - -- we have to unregister the already registered fake version in order - -- for the real quirked version to be in the registry when we validate - -- blocks - withResource (unregisterVersion fakeVersion >> registerVersion realVersion) (\_ -> pure ()) $ \_ -> - withDelegateMempool $ \dmpio -> testCaseSteps "quirkTest" $ \step -> - withTestBlockDb realVersion $ \bdb -> do - (iompa,mpa) <- dmpio - let logger = hunitDummyLogger step - withWebPactExecutionServiceCompaction logger realVersion testPactServiceConfig bdb mpa getGasModel $ \(pact,pacts,_cPact,cPacts) -> - flip runReaderT (MultiEnv bdb pact pacts cPacts (return iompa) noMiner cid) $ do - runToHeight 99 - - -- run the command once without it being quirked, to establish - -- a baseline gas value - runBlockTest - [ PactTxTest mempoolCmdBuilder $ - assertTxGas "not-quirked gas" 230 - ] - -- run the command quirked, with the previously calculated request - -- key realReqKey, and assert that the gas has been changed - let quirkedTests = - [ PactTxTest mempoolCmdBuilder $ - \cr -> assertTxGas "quirked gas" 1 cr - ] - void $ setPactMempool' (Just genesisHeader) - $ PactMempool [testsToBlock cid quirkedTests] - runCut' - testCurrentBlock quirkedTests +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 ] + + +-- TODO: pact5. This test cannot work as constructed anymore. It will need to be redone. +-- quirkTest :: TestTree +-- quirkTest = do +-- -- fake stand-in for the request key of the quirked command, so that we can +-- -- construct the version without knowing the request key of the command +-- -- that will use the version. +-- -- TODO: make this nicer once MempoolCmdBuilder is gone. +-- let fakeReqKey = RequestKey (Hash mempty) +-- let fakeVersion = quirkedGasTestVersion fakeReqKey +-- let genesisHeader = genesisBlockHeader fakeVersion cid +-- let mempoolCmdBuilder = +-- buildBasic (mkExec' "(+ 1 2)") +-- -- this is how setPactMempool builds the commands +-- -- we fake the header here to ensure the request key is consistent later on +-- let cmd = unsafeDupablePerformIO $ +-- buildCwCmd +-- (sshow genesisHeader) +-- fakeVersion +-- (_mempoolCmdBuilder mempoolCmdBuilder cid (view blockCreationTime genesisHeader)) +-- -- once we have the request key, we can make it a quirk in the version +-- -- we use to actually run the command. +-- let !realReqKey = RequestKey (toUntypedHash $ _cmdHash cmd) +-- let realVersion = quirkedGasTestVersion realReqKey +-- -- we have to unregister the already registered fake version in order +-- -- for the real quirked version to be in the registry when we validate +-- -- blocks +-- withResource (unregisterVersion fakeVersion >> registerVersion realVersion) (\_ -> pure ()) $ \_ -> +-- testCaseSteps "quirkTest" $ \step -> +-- withTestBlockDb realVersion $ \bdb -> do +-- mempools <- onAllChains realVersion $ \_cid -> do +-- r <- newIORef mempty +-- return (r, delegateMemPoolAccess r) +-- let logger = hunitDummyLogger step +-- withWebPactExecutionService logger realVersion testPactServiceConfig bdb (snd <$> mempools) $ \(pact,pacts) -> +-- flip runReaderT (MultiEnv bdb pact pacts (fst <$> mempools) noMiner cid) $ do +-- runToHeight 99 + +-- -- run the command once without it being quirked, to establish +-- -- a baseline gas value +-- runBlockTest +-- [ PactTxTest mempoolCmdBuilder $ +-- assertTxGas "not-quirked gas" 221 +-- ] +-- rs <- txResults +-- liftIO $ print rs +-- liftIO $ print realReqKey +-- -- run the command quirked, with the previously calculated request +-- -- key realReqKey, and assert that the gas has been changed +-- let quirkedTests = +-- [ PactTxTest mempoolCmdBuilder $ +-- \cr -> assertTxGas "quirked gas" 1 cr +-- ] +-- void $ setPactMempool' (Just genesisHeader) cid +-- $ PactMempool [testsToBlock quirkedTests] +-- runCut' +-- rs <- txResults +-- liftIO $ print rs +-- testCurrentBlock quirkedTests pact4coin3UpgradeTest :: PactTestM () pact4coin3UpgradeTest = do @@ -1427,17 +1468,17 @@ pact4coin3UpgradeTest = do let v3Hash = "1os_sLAUYvBzspn5jjawtRpJWiH1WPfhyNraeVvSIwU" block22 = [ PactTxTest buildHashCmd $ \cr -> do - gasEv0 <- mkTransferEvent "sender00" "NoMiner" 0.0013 "coin" v3Hash + 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.0014 "coin" v3Hash + 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.0015 "coin" v3Hash + 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 @@ -1457,15 +1498,15 @@ pact4coin3UpgradeTest = do block22_0 = [ PactTxTest (buildXReceive xproof) $ \cr -> do -- test receive XChain events - gasEvRcv <- mkTransferEvent "sender00" "NoMiner" 0.0014 "coin" v3Hash + gasEvRcv <- mkTransferEvent "sender00" "NoMiner" 0.0247 "coin" v3Hash rcvTfr <- mkTransferEvent "" "sender00" 0.0123 "coin" v3Hash assertTxEvents "Events for txRcv" [gasEvRcv,rcvTfr] cr ] - void $ setPactMempool $ PactMempool - [ testsToBlock cid block22 - , testsToBlock chain0 block22_0 - ] + void $ setPactMempool cid $ PactMempool + [ testsToBlock block22 ] + void $ setPactMempool chain0 $ PactMempool + [ testsToBlock block22_0 ] runCut' withChain cid $ do testCurrentBlock block22 @@ -1516,44 +1557,37 @@ pact4coin3UpgradeTest = do -- | 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 (IORef (Set TransactionHash)) +setPactMempool :: ChainId -> PactMempool -> PactTestM (IORef (Set TransactionHash)) setPactMempool = setPactMempool' Nothing -setPactMempool' :: Maybe BlockHeader -> PactMempool -> PactTestM (IORef (Set TransactionHash)) -setPactMempool' fakeParentBh (PactMempool fs) = do +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 - setMempool mpa $ mempty { + 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 blockHeader = do + go v ref mempoolPreBlockCheck bHeight bHash bCreationTime = do mps <- readIORef ref let runMps i = \case [] -> return mempty - (mp:r) -> case _mempoolBlock mp blockHeader of + (mp:r) -> case _mempoolBlock mp chain bCreationTime of Just bs -> do writeIORef ref (take i mps ++ r) - let parentBh = fromMaybe blockHeader fakeParentBh + let parentBct = fromMaybe bCreationTime (view blockCreationTime <$> fakeParentBh) cmds <- fmap V.fromList $ forM bs $ \b -> - buildCwCmd (sshow parentBh) v $ _mempoolCmdBuilder b parentBh - validationResults <- mempoolPreBlockCheck bHeight bHash cmds - return $ fmap fst $ V.filter snd (V.zip cmds validationResults) + 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 -filterBlock :: (BlockHeader -> Bool) -> MempoolBlock -> MempoolBlock -filterBlock f (MempoolBlock b) = MempoolBlock $ \mi -> - if f mi then b mi else Nothing - -blockForChain :: ChainId -> MempoolBlock -> MempoolBlock -blockForChain chid = filterBlock $ \bh -> - view blockChainId bh == chid - runCut' :: PactTestM () runCut' = do pact <- view menvPact @@ -1562,7 +1596,10 @@ runCut' = do liftIO $ runCut testVersion bdb pact (offsetBlockTime second) zeroNoncer miner resetMempool :: PactTestM () -resetMempool = view menvMpa >>= \r -> setMempool r mempty +resetMempool = do + mems <- view menvMpa + forM_ mems $ \ref -> + liftIO $ writeIORef ref mempty currentCut :: PactTestM Cut currentCut = view menvBdb >>= liftIO . readMVar . _bdbCut @@ -1640,13 +1677,13 @@ assertTxFailure' msg needle tx = runBlockTest :: HasCallStack => [PactTxTest] -> PactTestM () runBlockTest pts = do chid <- view menvChainId - void $ setPactMempool $ PactMempool [testsToBlock chid pts] + void $ setPactMempool chid $ PactMempool [testsToBlock pts] runCut' testCurrentBlock pts -- | Convert tests to block for specified chain. -testsToBlock :: ChainId -> [PactTxTest] -> MempoolBlock -testsToBlock chid pts = blockForChain chid $ MempoolBlock $ \_ -> +testsToBlock :: [PactTxTest] -> MempoolBlock +testsToBlock pts = MempoolBlock $ \_ _ -> pure $ map _pttBuilder pts -- | No tests in this list should even be submitted to the mempool, @@ -1654,7 +1691,7 @@ testsToBlock chid pts = blockForChain chid $ MempoolBlock $ \_ -> expectInvalid :: String -> [MempoolCmdBuilder] -> PactTestM () expectInvalid msg pts = do chid <- view menvChainId - void $ setPactMempool $ PactMempool [blockForChain chid $ MempoolBlock $ \_ -> pure pts] + void $ setPactMempool chid $ PactMempool [MempoolBlock $ \_ _ -> Just pts] _ <- runCut' rs <- txResults liftIO $ assertEqual msg mempty rs @@ -1679,9 +1716,10 @@ runToHeight bhi = do runToHeight bhi buildXSend :: [SigCapability] -> MempoolCmdBuilder -buildXSend caps = MempoolCmdBuilder $ \bh -> +buildXSend caps = MempoolCmdBuilder $ \chain bct -> set cbSigners [mkEd25519Signer' sender00 caps] - $ setFromHeader bh + $ set cbChainId chain + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC (mkExec "(coin.transfer-crosschain 'sender00 'sender00 (read-keyset 'k) \"0\" 0.0123)" $ @@ -1732,11 +1770,6 @@ signWebAuthn00 = signSender00 :: CmdBuilder -> CmdBuilder signSender00 = set cbSigners [mkEd25519Signer' sender00 []] -setFromHeader :: BlockHeader -> CmdBuilder -> CmdBuilder -setFromHeader bh = - set cbChainId (view blockChainId bh) - . set cbCreationTime (toTxCreationTime $ _bct $ view blockCreationTime bh) - buildBasic :: PactRPC T.Text -> MempoolCmdBuilder @@ -1750,9 +1783,10 @@ buildBasic' :: (CmdBuilder -> CmdBuilder) -> PactRPC T.Text -> MempoolCmdBuilder -buildBasic' f r = MempoolCmdBuilder $ \bh -> +buildBasic' f r = MempoolCmdBuilder $ \chain bct -> f $ signSender00 - $ setFromHeader bh + $ set cbChainId chain + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC r $ defaultCmd @@ -1760,9 +1794,10 @@ buildBasicWebAuthnBareSigner' :: (CmdBuilder -> CmdBuilder) -> PactRPC T.Text -> MempoolCmdBuilder -buildBasicWebAuthnBareSigner' f r = MempoolCmdBuilder $ \bh -> +buildBasicWebAuthnBareSigner' f r = MempoolCmdBuilder $ \chain bct -> f $ signWebAuthn00 - $ setFromHeader bh + $ set cbChainId chain + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC r $ defaultCmd @@ -1770,9 +1805,10 @@ buildBasicWebAuthnPrefixedSigner' :: (CmdBuilder -> CmdBuilder) -> PactRPC T.Text -> MempoolCmdBuilder -buildBasicWebAuthnPrefixedSigner' f r = MempoolCmdBuilder $ \bh -> +buildBasicWebAuthnPrefixedSigner' f r = MempoolCmdBuilder $ \chain bct -> f $ signWebAuthn00Prefixed - $ setFromHeader bh + $ set cbChainId chain + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC r $ defaultCmd diff --git a/test/unit/Chainweb/Test/Pact/PactReplay.hs b/test/unit/Chainweb/Test/Pact4/PactReplay.hs similarity index 93% rename from test/unit/Chainweb/Test/Pact/PactReplay.hs rename to test/unit/Chainweb/Test/Pact4/PactReplay.hs index ace3ab3c7c..6479a4cd95 100644 --- a/test/unit/Chainweb/Test/Pact/PactReplay.hs +++ b/test/unit/Chainweb/Test/Pact4/PactReplay.hs @@ -8,7 +8,7 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -module Chainweb.Test.Pact.PactReplay where +module Chainweb.Test.Pact4.PactReplay where import Control.Monad (forM_, unless, void) import Control.Monad.Catch @@ -35,13 +35,13 @@ import Chainweb.Graph import Chainweb.Test.Cut.TestBlockDb import Chainweb.Test.Utils import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types + import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Test.TestVersions import Chainweb.Time import Chainweb.TreeDB @@ -54,6 +54,10 @@ 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 @@ -81,7 +85,7 @@ tests rdb = ] where genblock = genesisBlockHeader testVer cid - label = "Chainweb.Test.Pact.PactReplay" + label = "Chainweb.Test.Pact4.PactReplay" forkLimit fl = testPactServiceConfig { _pactReorgLimit = fl } @@ -104,8 +108,8 @@ onRestart mpio iop step = do testMemPoolAccess :: MemPoolAccess testMemPoolAccess = mempty - { mpaGetBlock = \_g validate bh hash ph -> do - let (BlockCreationTime t) = view blockCreationTime ph + { mpaGetBlock = \_g validate bh hash bct -> do + let (BlockCreationTime t) = bct getTestBlock t validate bh hash } where @@ -118,17 +122,16 @@ testMemPoolAccess = mempty set cbRPC (mkExec' "1") $ defaultCmd let outtxs = V.singleton tx - oks <- validate bHeight hash outtxs - unless (V.and oks) $ fail $ mconcat + 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 oks + , show [ fmap (bimap (sshow @_ @String) (const ())) oks ] ] - return outtxs - + return $ V.fromList [ t | Right t <- V.toList oks ] dupegenMemPoolAccess :: IO MemPoolAccess dupegenMemPoolAccess = do @@ -143,14 +146,14 @@ dupegenMemPoolAccess = do set cbSigners [mkEd25519Signer' sender00 []] $ set cbRPC (mkExec' "1") $ defaultCmd - oks <- validate bHeight bHash outtxs - unless (V.and oks) $ fail $ mconcat + 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 oks + , show [ fmap (bimap (sshow @_ @String) (const ())) oks ] ] - return outtxs + return $ V.fromList $ [ t | Right t <- V.toList oks ] } -- | This is a regression test for correct initialization of the checkpointer @@ -330,7 +333,7 @@ mineBlock ph nonce iop = timeout 5000000 go >>= \case -- assemble block without nonce and timestamp (_, q, bdb) <- iop bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill ph q - let payload = blockInProgressToPayloadWithOutputs bip + let payload = forAnyPactVersion finalizeBlock bip let creationTime = BlockCreationTime diff --git a/test/unit/Chainweb/Test/Pact/PactSingleChainTest.hs b/test/unit/Chainweb/Test/Pact4/PactSingleChainTest.hs similarity index 62% rename from test/unit/Chainweb/Test/Pact/PactSingleChainTest.hs rename to test/unit/Chainweb/Test/Pact4/PactSingleChainTest.hs index e40cdba8e7..b91d6cd057 100644 --- a/test/unit/Chainweb/Test/Pact/PactSingleChainTest.hs +++ b/test/unit/Chainweb/Test/Pact4/PactSingleChainTest.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} @@ -6,43 +7,45 @@ {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ViewPatterns #-} -module Chainweb.Test.Pact.PactSingleChainTest +module Chainweb.Test.Pact4.PactSingleChainTest ( tests ) where import Control.Arrow ((&&&)) -import Control.Concurrent.Async (withAsync) +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 Data.Ord (Down(..)) import Patience.Map qualified as PatienceM import Patience.Map (Delta(..)) -import Streaming.Prelude qualified as S -import Data.Int (Int64) -import Data.Aeson (object, (.=), Value(..), eitherDecode) +import Data.Aeson (object, (.=), Value(..)) import qualified Data.ByteString.Lazy as BL -import Data.Either (isLeft, isRight, fromRight) +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 Database.SQLite3 qualified as Lite +import qualified Database.SQLite3 as Lite +import qualified Streaming.Prelude as S import GHC.Stack @@ -56,12 +59,13 @@ import Pact.Types.Hash import Pact.Types.Info import Pact.Types.Persistence import Pact.Types.PactError -import Pact.Types.RowData -import Pact.Types.Util (fromText') +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 @@ -73,32 +77,31 @@ 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.Backend.PactState (PactRowContents(..)) -import Chainweb.Pact.Backend.Types hiding (RunnableBlock(..)) import Chainweb.Pact.Service.BlockValidation hiding (local) import Chainweb.Pact.Service.PactQueue (PactQueue, newPactQueue) -import Chainweb.Pact.Service.Types hiding (runBlock) -import Chainweb.Pact.PactService (runPactService) 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.Pact.Utils -import Chainweb.Test.Pact.Utils qualified as Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Test.Utils import Chainweb.Test.TestVersions import Chainweb.Time -import Chainweb.Transaction (ChainwebTransaction) +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) -import Pact.Types.SQLite (SType(..), RType(..)) -import Pact.Types.SQLite qualified as Pact 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 @@ -141,7 +144,7 @@ tests rdb = testGroup testName , compactionResilientToRowIdOrdering rdb ] where - testName = "Chainweb.Test.Pact.PactSingleChainTest" + testName = "Chainweb.Test.Pact4.PactSingleChainTest" test = test' rdb testWithConf = testWithConf' rdb testTimeout = testTimeout' rdb @@ -188,7 +191,7 @@ runBlockE :: (HasCallStack) => PactQueue -> TestBlockDb -> TimeSpan Micros -> IO runBlockE q bdb timeOffset = do ph <- getParentTestBlockDb bdb cid bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) q - let nb = blockInProgressToPayloadWithOutputs bip + let nb = forAnyPactVersion finalizeBlock bip let blockTime = add timeOffset $ _bct $ view blockCreationTime ph forM_ (chainIds testVersion) $ \c -> do let o | c == cid = nb @@ -197,7 +200,7 @@ runBlockE q bdb timeOffset = do nextH <- getParentTestBlockDb bdb cid try (validateBlock nextH (CheckablePayloadWithOutputs nb) q) --- edmund: why does any of this return PayloadWithOutputs instead of a +-- 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 @@ -234,8 +237,9 @@ newBlockAndContinue refIO reqIO = testCase "newBlockAndContinue" $ do , V.fromList [ c3 ] ] - bipStart <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) q - let ParentHeader ph = _blockInProgressParentHeader bipStart + -- 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 @@ -247,7 +251,7 @@ newBlockAndContinue refIO reqIO = testCase "newBlockAndContinue" $ do (_blockInProgressParentHeader bipContinued) (_blockInProgressParentHeader bipFinal) assertBool "made progress (2)" (bipContinued /= bipFinal) - let nbContinued = blockInProgressToPayloadWithOutputs bipFinal + let nbContinued = finalizeBlock bipFinal -- add block to database let blockTime = add second $ _bct $ view blockCreationTime ph forM_ (chainIds testVersion) $ \c -> do @@ -265,7 +269,7 @@ newBlockAndContinue refIO reqIO = testCase "newBlockAndContinue" $ do [ c1, c2, c3 ] ] bipAllAtOnce <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) q - let nbAllAtOnce = blockInProgressToPayloadWithOutputs bipAllAtOnce + 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 @@ -283,13 +287,13 @@ newBlockNoFill refIO reqIO = testCase "newBlockNoFill" $ do set cbRPC (mkExec "1" (object [])) $ defaultCmd setMempool refIO =<< mempoolOf [V.fromList [c1]] - noFillPwo <- fmap blockInProgressToPayloadWithOutputs . throwIfNoHistory =<< + 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 blockInProgressToPayloadWithOutputs . throwIfNoHistory =<< + fillPwo <- fmap (forAnyPactVersion finalizeBlock) . throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) q assertEqual "an filled newblock has transactions with a full mempool" @@ -302,7 +306,7 @@ newBlockAndValidationFailure refIO reqIO = testCase "newBlockAndValidationFailur setOneShotMempool refIO =<< goldenMemPool bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) q - let nb = blockInProgressToPayloadWithOutputs bip + let nb = forAnyPactVersion finalizeBlock bip let blockTime = add second $ _bct $ view blockCreationTime genesisHeader forM_ (chainIds testVersion) $ \c -> do let o | c == cid = nb @@ -311,22 +315,21 @@ newBlockAndValidationFailure refIO reqIO = testCase "newBlockAndValidationFailur nextH <- getParentTestBlockDb bdb cid - let nextH' = nextH - & blockPayloadHash .~ BlockPayloadHash (unsafeMerkleLogHash "0000000000000000000000000000001d") + 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 = fromRight (error "can't parse") $ fromText' "WgnuCg6L_l6lzbjWtBfMEuPtty_uGcNrUol5HGREO_o" + 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 -> RowData -toRowData v = case eitherDecode encV of - Left e -> error $ - "toRowData: failed to encode as row data. " <> e <> "\n" <> show encV - Right r -> r +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 @@ -363,7 +366,7 @@ rosettaFailsWithoutFullHistory rdb = replicateM_ 10 $ void $ runBlock q bdb second targetSqlEnv <- targetSqlEnvIO - Utils.sigmaCompact srcSqlEnv targetSqlEnv (BlockHeight 5) + sigmaCompact srcSqlEnv targetSqlEnv (BlockHeight 5) -- This needs to run after the previous test -- Annoyingly, we must inline the PactService util starts here. @@ -394,7 +397,7 @@ rewindPastMinBlockHeightFails rdb = compactionSetup "rewindPastMinBlockHeightFails" rdb testPactServiceConfig $ \cr -> do replicateM_ 10 $ runBlock cr.srcPactQueue cr.blockDb second - Utils.sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight 5) + sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight 5) -- Genesis block header; compacted away by now let bh = genesisBlockHeader testVersion cid @@ -416,19 +419,20 @@ pactStateSamePreAndPostCompaction rdb = let numBlocks :: Num a => a numBlocks = 100 - let makeTx :: Word -> BlockHeader -> IO ChainwebTransaction - makeTx nth bh = buildCwCmd (sshow (nth, bh)) testVersion + 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]] - $ setFromHeader bh + $ set cbChainId cid + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") $ defaultCmd replicateM_ numBlocks $ do - runBlockWithTx_ cr.mempoolRef cr.srcPactQueue cr.blockDb - $ \n _ _ bHeader -> makeTx n bHeader + runTxInBlock_ cr.mempoolRef cr.srcPactQueue cr.blockDb + $ \n _ _ bct -> makeTx n bct statePreCompaction <- getLatestPactState cr.srcSqlEnv - Utils.sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight numBlocks) + sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight numBlocks) statePostCompaction <- getLatestPactState cr.targetSqlEnv comparePactStateBeforeAndAfter statePreCompaction statePostCompaction @@ -444,37 +448,38 @@ compactionIsIdempotent rdb = let numBlocks :: Num a => a numBlocks = 100 - let makeTx :: Word -> BlockHeader -> IO ChainwebTransaction - makeTx nth bh = buildCwCmd (sshow (nth, bh)) testVersion + 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]] - $ setFromHeader bh + $ set cbChainId cid + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") $ defaultCmd replicateM_ numBlocks $ do - runBlockWithTx_ cr.mempoolRef cr.srcPactQueue cr.blockDb - $ \n _ _ bHeader -> makeTx n bHeader + runTxInBlock_ cr.mempoolRef cr.srcPactQueue cr.blockDb + $ \n _ _ bct -> makeTx n bct twiceSqlEnv <- twiceSqlEnvIO let targetHeight = BlockHeight numBlocks -- Compact 'src' into 'target' - Utils.sigmaCompact cr.srcSqlEnv cr.targetSqlEnv targetHeight + sigmaCompact cr.srcSqlEnv cr.targetSqlEnv targetHeight -- Get table contents of 'target' statePostCompaction1 <- getPactUserTables cr.targetSqlEnv -- Compact 'target' into 'twice' - Utils.sigmaCompact cr.targetSqlEnv twiceSqlEnv targetHeight + 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 PactRowContents)) + 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, PactRowContents r.rowData r.txId)) sortedRows + pure $ M.fromList $ List.map (\r -> (T.decodeUtf8 r.rowKey, PS.PactRowContents r.rowData r.txId)) sortedRows state1 <- ensureIsCompactedAndSortRows statePostCompaction1 state2 <- ensureIsCompactedAndSortRows statePostCompaction2 @@ -486,19 +491,19 @@ compactionDoesNotDisruptDuplicateDetection :: () -> TestTree compactionDoesNotDisruptDuplicateDetection rdb = do compactionSetup "compactionDoesNotDisruptDuplicateDetection" rdb testPactServiceConfig $ \cr -> do - let makeTx :: IO ChainwebTransaction + 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 <- runBlockWithTx cr.mempoolRef cr.srcPactQueue cr.blockDb (\_ _ _ _ -> makeTx) + e1 <- runTxInBlock cr.mempoolRef cr.srcPactQueue cr.blockDb (\_ _ _ _ -> makeTx) assertBool "First tx submission succeeds" (isRight e1) - Utils.sigmaCompact cr.srcSqlEnv cr.targetSqlEnv =<< PS.getLatestBlockHeight cr.srcSqlEnv + sigmaCompact cr.srcSqlEnv cr.targetSqlEnv =<< PS.getLatestBlockHeight cr.srcSqlEnv - e2 <- runBlockWithTx cr.mempoolRef cr.targetPactQueue cr.blockDb (\_ _ _ _ -> makeTx) - assertBool "First tx submission fails" (isLeft e2) + 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. @@ -513,7 +518,7 @@ compactionUserTablesDropped rdb = gasLimit = 70_000 pactCfg = testPactServiceConfig { - _pactBlockGasLimit = gasLimit + _pactNewBlockGasLimit = gasLimit } in compactionSetup "compactionUserTablesDropped" rdb pactCfg $ \cr -> do @@ -522,7 +527,7 @@ compactionUserTablesDropped rdb = let halfwayPoint :: Integral a => a halfwayPoint = numBlocks `div` 2 - let createTable :: Word -> Text -> IO ChainwebTransaction + let createTable :: Word -> Text -> IO Pact4.Transaction createTable n tblName = do let tx = T.unlines [ "(namespace 'free)" @@ -547,7 +552,7 @@ compactionUserTablesDropped rdb = madeAfterTable <- newIORef @Bool False replicateM_ numBlocks $ do setMempool cr.mempoolRef $ mempty { - mpaGetBlock = \_ _ mBlockHeight _ _ -> do + mpaGetBlock = \_ validate mBlockHeight mBlockHash _ -> do let mkTable madeRef tbl = do madeYet <- readIORef madeRef if madeYet @@ -557,7 +562,9 @@ compactionUserTablesDropped rdb = n <- atomicModifyIORef' supply $ \a -> (a + 1, a) tx <- createTable n tbl writeIORef madeRef True - pure (V.fromList [tx]) + [Right t] <- + V.toList <$> validate mBlockHeight mBlockHash ((fmap . fmap . fmap) _pcCode $ V.singleton tx) + pure (V.singleton t) if mBlockHeight <= halfwayPoint then do @@ -575,7 +582,7 @@ compactionUserTablesDropped rdb = let msg = "Table " ++ T.unpack tbl ++ " should exist pre-compaction, but it doesn't." assertBool msg (isJust (M.lookup tbl statePre)) - Utils.sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight halfwayPoint) + sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight halfwayPoint) statePost <- getPactUserTables cr.targetSqlEnv flip assertBool (isJust (M.lookup freeBeforeTbl statePost)) $ @@ -594,21 +601,22 @@ compactionGrandHashUnchanged rdb = let numBlocks :: Num a => a numBlocks = 100 - let makeTx :: Word -> BlockHeader -> IO ChainwebTransaction - makeTx nth bh = buildCwCmd (sshow nth) testVersion + let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction + makeTx nth bct = buildCwCmd (sshow nth) testVersion $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] - $ setFromHeader bh + $ set cbChainId cid + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") $ defaultCmd replicateM_ numBlocks - $ runBlockWithTx_ cr.mempoolRef cr.srcPactQueue cr.blockDb - $ \n _ _ blockHeader -> makeTx n blockHeader + $ runTxInBlock_ cr.mempoolRef cr.srcPactQueue cr.blockDb + $ \n _ _ bct -> makeTx n bct let targetHeight = BlockHeight numBlocks hashPreCompaction <- computeGrandHash (PS.getLatestPactStateAt cr.srcSqlEnv targetHeight) - Utils.sigmaCompact cr.srcSqlEnv cr.targetSqlEnv 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 @@ -624,15 +632,16 @@ compactionResilientToRowIdOrdering rdb = -- Just run a bunch of blocks setOneShotMempool cr.mempoolRef =<< goldenMemPool - let makeTx :: Word -> BlockHeader -> IO ChainwebTransaction - makeTx nth bh = buildCwCmd (sshow nth) testVersion + let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction + makeTx nth bct = buildCwCmd (sshow nth) testVersion $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] - $ setFromHeader bh + $ set cbChainId cid + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") $ defaultCmd replicateM_ numBlocks - $ runBlockWithTx_ cr.mempoolRef cr.srcPactQueue cr.blockDb - $ \n _ _ blockHeader -> makeTx n blockHeader + $ 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 @@ -649,18 +658,18 @@ compactionResilientToRowIdOrdering rdb = -- -- 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" [] [RText, RInt] $ \rows -> do + 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 - [SText rowkey, SInt txid] -> do - Pact.bindParams stmt [SText rowkey, SInt txid, SInt newRowId] + [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 - Utils.sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight numBlocks) + sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight numBlocks) -- Get the state post-randomisation and post-compaction statePostCompaction <- getLatestPactState cr.targetSqlEnv @@ -699,16 +708,354 @@ comparePactStateBeforeAndAfter statePreCompaction statePostCompaction = do putStrLn "" assertFailure "pact state check failed" +-- -- Test that PactService fails if Rosetta is enabled and we don't have all of +-- -- the history. +-- -- +-- -- We do this in two stages: +-- -- +-- -- 1: +-- -- - Start PactService with Rosetta disabled +-- -- - Run some blocks +-- -- - Compact to some arbitrary greater-than-genesis height +-- -- 2: +-- -- - Start PactService with Rosetta enabled +-- -- - Catch the exception that should arise at the start of PactService, +-- -- when performing the history check +-- rosettaFailsWithoutFullHistory :: () +-- => RocksDb +-- -> TestTree +-- rosettaFailsWithoutFullHistory rdb = +-- withTemporaryDir $ \iodir -> +-- withSqliteDb cid iodir $ \sqlEnvIO -> +-- withDelegateMempool $ \dm -> +-- independentSequentialTestGroup "rosettaFailsWithoutFullHistory" +-- [ +-- -- Run some blocks and then compact +-- withPactTestBlockDb' testVersion cid rdb sqlEnvIO mempty testPactServiceConfig $ \reqIO -> +-- testCase "runBlocksAndCompact" $ do +-- (sqlEnv, q, bdb) <- reqIO + +-- mempoolRef <- fmap (pure . fst) dm + +-- setOneShotMempool mempoolRef =<< goldenMemPool +-- replicateM_ 10 $ void $ runBlock q bdb second + +-- compact Error [C.NoVacuum] sqlEnv (C.Target (BlockHeight 5)) + +-- -- This needs to run after the previous test +-- -- Annoyingly, we must inline the PactService util starts here. +-- -- ResourceT will help clean all this up +-- , testCase "PactService Should fail" $ do +-- pactQueue <- newPactQueue 2000 +-- blockDb <- mkTestBlockDb testVersion rdb +-- bhDb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb blockDb) cid +-- sqlEnv <- sqlEnvIO +-- mempool <- fmap snd dm +-- let payloadDb = _bdbPayloadDb blockDb +-- let cfg = testPactServiceConfig { _pactFullHistoryRequired = True } +-- let logger = genericLogger System.LogLevel.Error (\_ -> return ()) +-- e <- try $ runPactService testVersion cid logger Nothing pactQueue mempool bhDb payloadDb sqlEnv cfg +-- case e of +-- Left (FullHistoryRequired {}) -> do +-- pure () +-- Left err -> do +-- assertFailure $ "Expected FullHistoryRequired exception, instead got: " ++ show err +-- Right _ -> do +-- assertFailure "Expected FullHistoryRequired exception, instead there was no exception at all." +-- ] + +-- rewindPastMinBlockHeightFails :: () +-- => RocksDb +-- -> TestTree +-- rewindPastMinBlockHeightFails rdb = +-- compactionSetup "rewindPastMinBlockHeightFails" rdb testPactServiceConfig $ \cr -> do +-- replicateM_ 10 $ runBlock cr.pactQueue cr.blockDb second + +-- compact Error [C.NoVacuum] cr.sqlEnv (C.Target (BlockHeight 5)) + +-- -- Genesis block header; compacted away by now +-- let bh = genesisBlockHeader testVersion cid + +-- syncResult <- try (pactSyncToBlock bh cr.pactQueue) +-- 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.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 (UserTables "coin_coin-table") q + 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 [TxLog "coin_coin-table" "sender00" + (Just [PCore.TxLog "coin_coin-table" "sender00" (toRowData $ object [ "guard" .= object [ "pred" .= ("keys-all" :: T.Text) @@ -726,7 +1073,7 @@ getHistory refIO reqIO = testCase "getHistory" $ do assertEqual "check previous balance" (M.fromList [(RowKey "sender00", - (TxLog "coin_coin-table" "sender00" + (PCore.TxLog "coin_coin-table" "sender00" (toRowData $ object [ "guard" .= object [ "pred" .= ("keys-all" :: T.Text) @@ -741,7 +1088,7 @@ getHistory refIO reqIO = testCase "getHistory" $ do getHistoricalLookupNoTxs :: T.Text - -> (Maybe (TxLog RowData) -> IO ()) + -> (Maybe (PCore.TxLog PCore.RowData) -> IO ()) -> IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree @@ -755,7 +1102,7 @@ getHistoricalLookupNoTxs key assertF refIO reqIO = getHistoricalLookupWithTxs :: T.Text - -> (Maybe (TxLog RowData) -> IO ()) + -> (Maybe (PCore.TxLog PCore.RowData) -> IO ()) -> IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree @@ -767,14 +1114,16 @@ getHistoricalLookupWithTxs key assertF refIO reqIO = h <- getParentTestBlockDb bdb cid histLookup q h key >>= assertF -histLookup :: PactQueue -> BlockHeader -> T.Text -> IO (Maybe (TxLog RowData)) +histLookup :: PactQueue -> BlockHeader -> T.Text -> IO (Maybe (PCore.TxLog PCore.RowData)) histLookup q bh k = - throwIfNoHistory =<< pactHistoricalLookup bh (UserTables "coin_coin-table") (RowKey k) q + throwIfNoHistory =<< pactHistoricalLookup bh + (PCore.DUserTables (PCore.TableName "coin-table" (PCore.ModuleName "coin" Nothing))) + (PCore.RowKey k) q -assertSender00Bal :: Rational -> String -> Maybe (TxLog RowData) -> Assertion +assertSender00Bal :: Rational -> String -> Maybe (PCore.TxLog PCore.RowData) -> Assertion assertSender00Bal bal msg hist = assertEqual msg - (Just (TxLog "coin_coin-table" "sender00" + (Just (PCore.TxLog "coin_coin-table" "sender00" (toRowData $ object [ "guard" .= object [ "pred" .= ("keys-all" :: T.Text) @@ -788,12 +1137,6 @@ assertSender00Bal bal msg hist = signSender00 :: CmdBuilder -> CmdBuilder signSender00 = set cbSigners [mkEd25519Signer' sender00 []] -setFromHeader :: BlockHeader -> CmdBuilder -> CmdBuilder -setFromHeader bh = - set cbChainId (view blockChainId bh) - . set cbCreationTime (toTxCreationTime $ _bct $ view blockCreationTime bh) - - -- 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 @@ -801,7 +1144,8 @@ blockGasLimitTest _ reqIO = testCase "blockGasLimitTest" $ do let useGas g = do - bigTx <- buildCwCmd "cmd" testVersion $ set cbGasLimit g $ signSender00 $ set cbRPC (mkExec' "TESTING") defaultCmd + bigTx <- buildCwCmd "cmd" testVersion + $ set cbGasLimit g $ signSender00 $ set cbRPC (mkExec' "TESTING") defaultCmd let cr = CommandResult (RequestKey (Hash "0")) Nothing @@ -811,7 +1155,7 @@ blockGasLimitTest _ reqIO = testCase "blockGasLimitTest" $ do (V.singleton (bigTx, cr)) (CommandResult (RequestKey (Hash "h")) Nothing (PactResult $ Right $ pString "output") 0 Nothing Nothing Nothing []) - payload = toPayloadWithOutputs noMiner block + payload = toPayloadWithOutputs Pact4T noMiner block bh = newBlockHeader mempty (_payloadWithOutputsPayloadHash payload) @@ -824,31 +1168,47 @@ blockGasLimitTest _ reqIO = testCase "blockGasLimitTest" $ do Left (BlockGasLimitExceeded _) -> return () r -> - error $ "not a BlockGasLimitExceeded error: " <> sshow 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: " <> sshow 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 () - -- we consume much less than the maximum block gas limit and expect no such error. + 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 () - -- we consume zero gas and expect no such error. - useGas 0 >>= \case + 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 @@ -877,26 +1237,30 @@ mempoolRefillTest mpRefIO reqIO = testCase "mempoolRefillTest" $ do checkCount n = assertEqual "tx return count" n . V.length . _payloadWithOutputsTransactions mp supply txRefillMap = setMempool mpRefIO $ mempty { - mpaGetBlock = \BlockFill{..} _ _ _ bh -> case M.lookup _bfCount (M.fromList txRefillMap) of + mpaGetBlock = \BlockFill{..} validate bheight bhash bct -> + case M.lookup _bfCount (M.fromList txRefillMap) of Nothing -> return mempty - Just txs -> fmap V.fromList $ sequence $ map (next supply bh) txs + 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 bh = buildCwCmd (sshow (i, bh)) testVersion + goodTx i bct = buildCwCmd (sshow (i, bct)) testVersion $ signSender00 - $ setFromHeader bh + $ set cbChainId cid + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC (mkExec' "(+ 1 2)") $ defaultCmd - badTx i bh = buildCwCmd (sshow (i, bh)) testVersion + badTx i bct = buildCwCmd (sshow (i, bct)) testVersion $ signSender00 $ set cbSender "bad" - $ setFromHeader bh + $ set cbChainId cid + $ set cbCreationTime (toTxCreationTime $ _bct bct) $ set cbRPC (mkExec' "(+ 1 2)") $ defaultCmd @@ -924,20 +1288,22 @@ moduleNameFork mpRefIO reqIO = testCase "moduleNameFork" $ do moduleNameMempool :: T.Text -> T.Text -> MemPoolAccess moduleNameMempool ns mn = mempty - { mpaGetBlock = getTestBlock - } - where - getTestBlock _ _ _ _ bh = do - let txs = - [ "(namespace '" <> ns <> ") (module " <> mn <> " G (defcap G () (enforce false 'cannotupgrade)))" - , ns <> "." <> mn <> ".G" - ] - fmap V.fromList $ forM (zip txs [0..]) $ \(code,n :: Int) -> - buildCwCmd ("1" <> sshow n) testVersion $ - signSender00 $ - set cbCreationTime (toTxCreationTime $ _bct $ view blockCreationTime bh) $ - set cbRPC (mkExec' code) $ - defaultCmd + { 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 @@ -953,7 +1319,7 @@ mempoolCreationTimeTest mpRefIO reqIO = testCase "mempoolCreationTimeTest" $ do -- do pre-insert check with transaction at start + 15s tx <- makeTx "tx-now" (add s15 start) - void $ pactPreInsertCheck (V.singleton tx) q + void $ pactPreInsertCheck (V.singleton $ fmap (fmap _pcCode) tx) q setOneShotMempool mpRefIO $ mp tx -- b2 will be made at start + 30s @@ -969,14 +1335,13 @@ mempoolCreationTimeTest mpRefIO reqIO = testCase "mempoolCreationTimeTest" $ do $ set cbRPC (mkExec' "1") $ defaultCmd mp tx = mempty { - mpaGetBlock = \_ valid _ _ bh -> getBlock bh tx valid + mpaGetBlock = \_ valid bh bhash _ -> getBlock bh bhash tx valid } - getBlock bh tx valid = do - let txs = V.singleton tx - oks <- valid (view blockHeight bh) (view blockHash bh) txs - unless (V.and oks) $ throwM $ userError "Insert failed" - return txs + 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 @@ -1007,15 +1372,15 @@ preInsertCheckTimeoutTest _ reqIO = testCase "preInsertCheckTimeoutTest" $ do -- timeouts are tricky to trigger in GH actions. -- we're satisfied if it's triggered once in 100 runs. rs <- replicateM 100 - (pactPreInsertCheck (V.fromList [txCoinV3, txCoinV4, txCoinV5]) q) + (pactPreInsertCheck ((fmap . fmap . fmap) _pcCode $ V.fromList [txCoinV3, txCoinV4, txCoinV5]) q) assertBool "should get at least one InsertErrorTimedOut" $ any - (V.all (== Left InsertErrorTimedOut)) + (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 . requestKeyToTransactionHash . RequestKey . toUntypedHash @'Blake2b_256 + let hashToTxHashList = V.singleton . pact4RequestKeyToTransactionHash . RequestKey . toUntypedHash @'Blake2b_256 badHashRef <- newIORef $ hashToTxHashList initialHash badTx <- buildCwCmd "badListMPA" testVersion $ signSender00 @@ -1026,13 +1391,15 @@ badlistNewBlockTest mpRefIO reqIO = testCase "badlistNewBlockTest" $ do $ defaultCmd setOneShotMempool mpRefIO (badlistMPA badTx badHashRef) bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) reqQ - let resp = blockInProgressToPayloadWithOutputs bip + 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 = \_ _ _ _ _ -> return (V.singleton badTx) + { 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 } @@ -1042,21 +1409,22 @@ goldenNewBlock name mpIO mpRefIO reqIO = golden name $ do (_, reqQ, _) <- reqIO setOneShotMempool mpRefIO mp blockInProgress <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) reqQ - let resp = blockInProgressToPayloadWithOutputs blockInProgress + 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)) - goldenBytes resp blockInProgress + 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 _blockInProgressParentHeader) + , "parentHeader" J..= J.encodeWithAeson (_parentHeader $ fromJuste _blockInProgressParentHeader) , "pendingData" J..= J.object [ "pendingSuccessfulTxs" J..= J.array (encodeB64UrlNoPaddingText <$> List.sort (toList _pendingSuccessfulTxs)) @@ -1064,10 +1432,10 @@ goldenNewBlock name mpIO mpRefIO reqIO = golden name $ do (T.decodeUtf8 <$> List.sort (toList _pendingTableCreation)) , "pendingWrites" J..= pendingWritesJson ] - , "txId" J..= J.Aeson (fromIntegral @_ @Int _blockInProgressTxId) + , "txId" J..= J.Aeson (fromIntegral @_ @Int $ _blockHandleTxId _blockInProgressHandle) ] - where - SQLitePendingData{..} = _blockInProgressPendingData + where + SQLitePendingData{..} = _blockHandlePending _blockInProgressHandle pendingWritesJson = J.Object [ (T.decodeUtf8 _dkTable, J.Object [ (T.decodeUtf8 _dkRowKey, J.Object @@ -1079,7 +1447,7 @@ goldenNewBlock name mpIO mpRefIO reqIO = golden name $ do | (_dkTable, tableWrites) <- hmToSortedList _pendingWrites ] - goldenBytes :: PayloadWithOutputs -> BlockInProgress -> IO BL.ByteString + 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 @@ -1114,7 +1482,7 @@ goldenMemPool = do set cbRPC (mkExec code $ mkKeySetData "test-admin-keyset" [sender00]) $ defaultCmd -mempoolOf :: [V.Vector ChainwebTransaction] -> IO MemPoolAccess +mempoolOf :: [V.Vector Pact4.Transaction] -> IO MemPoolAccess mempoolOf blocks = do blocksRemainingRef <- newIORef blocks return mempty @@ -1125,13 +1493,15 @@ mempoolOf blocks = do outtxs <- atomicModifyIORef' blocksRemainingRef $ \case (b:bs) -> (bs, b) [] -> ([], mempty) - oks <- validate bHeight bHash outtxs - unless (V.and oks) $ fail $ mconcat + 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 oks ] - return outtxs + , show [ err | Left err <- V.toList oks ] + ] + return $ V.fromList [ t | Right t <- V.toList oks ] + data CompactionResources = CompactionResources { mempoolRef :: IO (IORef MemPoolAccess) @@ -1145,7 +1515,7 @@ data CompactionResources = CompactionResources compactionSetup :: () => String - -- ^ test pattern + -- ^ test pattern -> RocksDb -> PactServiceConfig -> (CompactionResources -> IO ()) @@ -1186,39 +1556,44 @@ compactionSetup pat rdb pactCfg f = , blockDb = blockDb } -runBlockWithTx :: () + +runTxInBlock :: () => IO (IORef MemPoolAccess) -- ^ mempoolRef -> PactQueue -> TestBlockDb - -> (Word -> BlockHeight -> BlockHash -> BlockHeader -> IO ChainwebTransaction) + -> (Word -> BlockHeight -> BlockHash -> BlockCreationTime -> IO Pact4.Transaction) -> IO (Either PactException PayloadWithOutputs) -runBlockWithTx mempoolRef pactQueue blockDb makeTx = do +runTxInBlock mempoolRef pactQueue blockDb makeTx = do madeTx <- newIORef @Bool False supply <- newIORef @Word 0 setMempool mempoolRef $ mempty { - mpaGetBlock = \_ _ bHeight bHash bHeader -> do + 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 bHeader + tx <- makeTx n bHeight bHash bct + valids <- valid bHeight bHash (V.singleton $ (fmap . fmap) _pcCode tx) writeIORef madeTx True - pure $ V.fromList [tx] + pure $ V.fromList + [ v + | Right v <- V.toList valids + ] } e <- runBlockE pactQueue blockDb second writeIORef madeTx False pure e -runBlockWithTx_ :: () +runTxInBlock_ :: () => IO (IORef MemPoolAccess) -- ^ mempoolRef -> PactQueue -> TestBlockDb - -> (Word -> BlockHeight -> BlockHash -> BlockHeader -> IO ChainwebTransaction) + -> (Word -> BlockHeight -> BlockHash -> BlockCreationTime -> IO Pact4.Transaction) -> IO PayloadWithOutputs -runBlockWithTx_ mempoolRef pactQueue blockDb makeTx = do - runBlockWithTx mempoolRef pactQueue blockDb makeTx >>= \case +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 diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact4/RemotePactTest.hs similarity index 94% rename from test/unit/Chainweb/Test/Pact/RemotePactTest.hs rename to test/unit/Chainweb/Test/Pact4/RemotePactTest.hs index c214e244aa..5f1baac931 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact4/RemotePactTest.hs @@ -20,7 +20,7 @@ -- Unit test for Pact execution via the Http Pact interface (/send, -- etc.) (inprocess) API in Chainweb -- -module Chainweb.Test.Pact.RemotePactTest +module Chainweb.Test.Pact4.RemotePactTest ( tests , withRequestKeys , polling @@ -95,10 +95,10 @@ 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.Service.Types -import Chainweb.Pact.Validations (defaultMaxTTL) -import Chainweb.Test.Pact.Utils -import Chainweb.Test.Pact.Utils qualified as Utils +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 @@ -107,6 +107,7 @@ 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 @@ -117,6 +118,10 @@ nNodes = 1 v :: ChainwebVersion v = instantCpmTestVersion petersonChainGraph +vNetworkId :: Pact.NetworkId +vNetworkId = Pact.NetworkId $ getChainwebVersionName $ _versionName v + + cid :: HasCallStack => ChainId cid = head . toList $ chainIds v @@ -144,7 +149,7 @@ withRequestKeys t cenv = do -- random chain sampling works with our test harnesses. -- tests :: RocksDb -> TestTree -tests rdb = testGroup "Chainweb.Test.Pact.RemotePactTest" +tests rdb = testGroup "Chainweb.Test.Pact4.RemotePactTest" [ withResourceT (withNodeDbDirs rdb nNodes) $ \dbDirs -> withResourceT (withNodesAtLatestBehavior v id =<< liftIO dbDirs) $ \net -> let cenv = _getServiceClientEnv <$> net @@ -179,6 +184,7 @@ tests rdb = testGroup "Chainweb.Test.Pact.RemotePactTest" 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" $ @@ -220,6 +226,8 @@ invalidCommandTest rdb = runResourceT $ do iot <- liftIO $ toTxCreationTime @Integer <$> getCurrentTimeIntegral + let prefix cmd = "Validation failed for hash " <> sshow (_cmdHash cmd) <> ": " + cmdParseFailure <- liftIO $ buildTextCmd "bare-command" v $ set cbSigners [mkEd25519Signer' sender00 []] $ set cbTTL defaultMaxTTL @@ -228,7 +236,7 @@ invalidCommandTest rdb = runResourceT $ do $ set cbRPC (mkExec "(+ 1" (mkKeySetData "sender00" [sender00])) $ defaultCmd -- Why does pact just return 'mzero' here... - sendExpect [cmdParseFailure] (== "Validation of command at position 0 failed: Pact parsing error: Failed reading: mzero") + sendExpect [cmdParseFailure] (== (prefix cmdParseFailure <> "Pact parse error: Failed reading: mzero")) cmdInvalidPayloadHash <- liftIO $ do bareCmd <- buildTextCmd "bare-command" v @@ -241,7 +249,7 @@ invalidCommandTest rdb = runResourceT $ do pure $ bareCmd { _cmdHash = Pact.hash "fakehash" } - sendExpect [cmdInvalidPayloadHash] (== "Validation of command at position 0 failed: Command failed validation: The hash of the payload was invalid.") + sendExpect [cmdInvalidPayloadHash] (== (prefix cmdInvalidPayloadHash <> "Invalid transaction hash")) cmdSignersSigsLengthMismatch1 <- liftIO $ do bareCmd <- buildTextCmd "bare-command" v @@ -254,7 +262,7 @@ invalidCommandTest rdb = runResourceT $ do pure $ bareCmd { _cmdSigs = [] } - sendExpect [cmdSignersSigsLengthMismatch1] (== "Validation of command at position 0 failed: Command failed validation: The number of signers and signatures do not match. Number of signers: 1. Number of signatures: 0.") + sendExpect [cmdSignersSigsLengthMismatch1] (== (prefix cmdSignersSigsLengthMismatch1 <> "Invalid transaction sigs")) cmdSignersSigsLengthMismatch2 <- liftIO $ do bareCmd <- buildTextCmd "bare-command" v @@ -268,7 +276,7 @@ invalidCommandTest rdb = runResourceT $ do { -- This is an invalid ED25519 signature, but length signers == length signatures is checked first _cmdSigs = [ED25519Sig "fakeSig"] } - sendExpect [cmdSignersSigsLengthMismatch2] (== "Validation of command at position 0 failed: Command failed validation: The number of signers and signatures do not match. Number of signers: 0. Number of signatures: 1.") + sendExpect [cmdSignersSigsLengthMismatch2] (== (prefix cmdSignersSigsLengthMismatch2 <> "Invalid transaction sigs")) -- TODO: It's hard to test for invalid schemes, because it's baked into -- chainwebversion. @@ -287,7 +295,7 @@ invalidCommandTest rdb = runResourceT $ do pure $ bareCmd { _cmdSigs = [ED25519Sig "fakeSig"] } - sendExpect [cmdInvalidUserSig] (== "Validation of command at position 0 failed: Command failed validation: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") + sendExpect [cmdInvalidUserSig] (== (prefix cmdInvalidUserSig <> "Invalid transaction sigs")) cmdGood <- liftIO $ buildTextCmd "good-command" v $ set cbSigners [mkEd25519Signer' sender00 []] @@ -298,12 +306,12 @@ invalidCommandTest rdb = runResourceT $ do $ 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] (== "Validation of command at position 0 failed: Command failed validation: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") + sendExpect [cmdInvalidUserSig, cmdGood] (== (prefix cmdInvalidUserSig <> "Invalid transaction sigs")) -- 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] (== "Validation of command at position 1 failed: Command failed validation: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") + sendExpect [cmdGood, cmdInvalidUserSig] (== (prefix cmdInvalidUserSig <> "Invalid transaction sigs")) -- | Check that txlogs don't problematically access history -- post-compaction. @@ -631,7 +639,7 @@ localChainDataTest t cenv = do 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 "instant-CPM-peterson") (Just nonce) + 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 @@ -664,6 +672,10 @@ localPreflightSimTest t cenv step = do Right LocalTimeout -> assertFailure "Preflight should never produce a timeout" Right LocalResultWithWarns{} -> pure () + Right LocalPact5PreflightResult{} -> + assertFailure "Preflight /local call produced Pact5 result in Pact4-only tests" + Right (LocalPact5ResultLegacy _) -> + assertFailure "Preflight /local call produced Pact5 result in Pact4-only tests" step "Execute preflight /local tx - preflight+signoverify known /send success" cmd0' <- mkRawTx mv psid psigs @@ -719,6 +731,10 @@ localPreflightSimTest t cenv step = do assertFailure "Preflight should never produce a timeout" Right MetadataValidationFailure{} -> assertFailure "Preflight produced an impossible result" + Right LocalPact5PreflightResult{} -> + assertFailure "Preflight /local call produced Pact5 result in Pact4-only tests" + Right (LocalPact5ResultLegacy _) -> + assertFailure "Preflight /local call produced Pact5 result in Pact4-only tests" Right (LocalResultWithWarns cr' ws) -> do let crbh :: Integer = fromIntegral $ fromMaybe 0 $ crGetBlockHeight cr' expectedbh = 1 + fromIntegral currentBlockHeight @@ -742,6 +758,10 @@ localPreflightSimTest t cenv step = do assertFailure "Preflight should never produce a timeout" Right MetadataValidationFailure{} -> assertFailure "Preflight produced an impossible result" + Right LocalPact5PreflightResult{} -> + assertFailure "Preflight /local call produced Pact5 result in Pact4-only tests" + Right (LocalPact5ResultLegacy _) -> + assertFailure "Preflight /local call produced Pact5 result in Pact4-only tests" Right (LocalResultWithWarns cr' ws) -> do let crbh :: Integer = fromIntegral $ fromMaybe 0 $ crGetBlockHeight cr' expectedbh = toInteger $ 1 + (fromIntegral currentBlockHeight') - rewindDepth @@ -784,7 +804,7 @@ localPreflightSimTest t cenv step = 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 "instant-CPM-peterson") (Just nonce) + c <- Pact.mkExec code A.Null (pm t) kps [] (Just vNetworkId) (Just nonce) pure (succ nn, c) mkCmdBuilder sigs pcid limit price = do @@ -802,6 +822,7 @@ localPreflightSimTest t cenv step = do 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] @@ -818,14 +839,14 @@ pollBadKeyTest cenv step = do sid <- liftIO $ mkChainId v maxBound 0 step "RequestKeys of length > 32 fail fast" - runClientM (pactPollApiClient v sid (Poll tooBig)) cenv >>= \case + runClientM (pactPollWithQueryApiClient v sid Nothing (pact4Poll $ Poll tooBig)) cenv >>= \case Left _ -> return () - Right r -> assertFailure $ "Poll succeeded with response: " <> show r + Right (Pact5.PollResponse r) -> assertFailure $ "Poll succeeded with response: " <> show r step "RequestKeys of length < 32 fail fast" - runClientM (pactPollApiClient v sid (Poll tooSmall)) cenv >>= \case + runClientM (pactPollWithQueryApiClient v sid Nothing (pact4Poll $ Poll tooSmall)) cenv >>= \case Left _ -> return () - Right r -> assertFailure $ "Poll succeeded with response: " <> show r + Right (Pact5.PollResponse r) -> assertFailure $ "Poll succeeded with response: " <> show r where toRk = (NEL.:| []) . RequestKey . Hash . SB.toShort @@ -867,7 +888,7 @@ sendValidationTest t cenv step = do 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 "instant-CPM-peterson") (Just $ sshow n) + 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 @@ -917,7 +938,7 @@ ethSpvTest t cenv step = do 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 "instant-CPM-peterson") (Just "1") + cmd <- liftIO $ Pact.mkExec txcode (txdata proof) pm ks [] (Just vNetworkId) (Just "1") return $ SubmitBatch (pure cmd) txcode = "(verify-spv 'ETH (read-msg))" @@ -949,8 +970,8 @@ spvTest t cenv step = 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 "instant-CPM-peterson") (Just "1") - cmd2 <- liftIO $ Pact.mkExec txcode txdata pm ks [] (Just "instant-CPM-peterson") (Just "2") + 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 @@ -1025,7 +1046,7 @@ txTooBigGasTest t cenv step = do 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 "instant-CPM-peterson") n + cmd <- liftIO $ Pact.mkExec code cdata pm ks [] (Just vNetworkId) n return $ SubmitBatch (pure cmd) txcode0 = T.concat ["[", T.replicate 10 " 1", "]"] @@ -1257,7 +1278,7 @@ mkSingletonBatch 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 "instant-CPM-peterson") nonce + cmd <- liftIO $ Pact.mkExec c dd (pmk t) ks [] (Just vNetworkId) nonce return $ SubmitBatch (cmd NEL.:| []) testSend :: Pact.TxCreationTime -> MVar Int -> ClientEnv -> IO RequestKeys @@ -1267,7 +1288,7 @@ testBatch'' :: Pact.ChainId -> Pact.TxCreationTime -> Pact.TTLSeconds -> MVar In 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 "instant-CPM-peterson") (Just nonce) + 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 diff --git a/test/unit/Chainweb/Test/Pact/RewardsTest.hs b/test/unit/Chainweb/Test/Pact4/RewardsTest.hs similarity index 87% rename from test/unit/Chainweb/Test/Pact/RewardsTest.hs rename to test/unit/Chainweb/Test/Pact4/RewardsTest.hs index 07d995f582..04e9d74791 100644 --- a/test/unit/Chainweb/Test/Pact/RewardsTest.hs +++ b/test/unit/Chainweb/Test/Pact4/RewardsTest.hs @@ -1,6 +1,6 @@ {-# LANGUAGE RankNTypes #-} -module Chainweb.Test.Pact.RewardsTest +module Chainweb.Test.Pact4.RewardsTest ( tests ) where @@ -12,7 +12,7 @@ import Pact.Parse import Chainweb.Graph import Chainweb.Miner.Pact -import Chainweb.Pact.PactService.ExecBlock +import Chainweb.Pact.PactService.Pact4.ExecBlock import Chainweb.Test.TestVersions import Chainweb.Version @@ -20,7 +20,7 @@ v :: ChainwebVersion v = instantCpmTestVersion petersonChainGraph tests :: TestTree -tests = testGroup "Chainweb.Test.Pact.RewardsTest" +tests = testGroup "Chainweb.Test.Pact4.RewardsTest" [ testGroup "Miner Rewards Unit Tests" [ rewardsTest ] diff --git a/test/unit/Chainweb/Test/Pact/SPV.hs b/test/unit/Chainweb/Test/Pact4/SPV.hs similarity index 96% rename from test/unit/Chainweb/Test/Pact/SPV.hs rename to test/unit/Chainweb/Test/Pact4/SPV.hs index f12117d35a..ad5225fc86 100644 --- a/test/unit/Chainweb/Test/Pact/SPV.hs +++ b/test/unit/Chainweb/Test/Pact4/SPV.hs @@ -9,6 +9,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} + -- | -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT @@ -17,7 +18,7 @@ -- -- Pact Service SPV Support roundtrip tests -- -module Chainweb.Test.Pact.SPV +module Chainweb.Test.Pact4.SPV ( -- * test suite tests -- * repl tests @@ -78,28 +79,29 @@ import Chainweb.BlockHeight import Chainweb.Cut import Chainweb.Graph import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types + import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.SPV.CreateProof import Chainweb.Test.Cut import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Test.Utils import Chainweb.Test.TestVersions import Chainweb.Time -import Chainweb.Transaction +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils hiding (check) import Chainweb.Version as Chainweb import Chainweb.WebPactExecutionService import Data.LogMessage +import Chainweb.Pact.Types (MemPoolAccess, mpaGetBlock) -- | Note: These tests are intermittently non-deterministic due to the way -- random chain sampling works with our test harnesses. -- tests :: TestTree -tests = testGroup "Chainweb.Test.Pact.SPV" +tests = testGroup "Chainweb.Test.Pact4.SPV" [ testCaseSteps "standard SPV verification round trip" standard , testCaseSteps "contTXOUTOld" contTXOUTOld , testCaseSteps "contTXOUTNew" contTXOUTNew @@ -260,7 +262,9 @@ 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) freeGasModel $ \(pact,_) -> do + 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 @@ -325,11 +329,15 @@ cutToPayloadOutputs c pdb = do toCR (TransactionOutput t) = fromJuste $ decodeStrict' t return txs -chainToMPA' :: MVar TransactionGenerator -> MemPoolAccess -chainToMPA' f = mempty - { mpaGetBlock = \_g _pc hi ha he -> do +chainToMPA' :: ChainId -> MVar TransactionGenerator -> MemPoolAccess +chainToMPA' chain f = mempty + { mpaGetBlock = \_g pc hi ha he -> do tg <- readMVar f - tg (view blockChainId he) hi ha he + 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 } @@ -340,8 +348,8 @@ type TransactionGenerator = Chainweb.ChainId -> BlockHeight -> BlockHash - -> BlockHeader - -> IO (Vector ChainwebTransaction) + -> BlockCreationTime + -> IO (Vector Pact4.Transaction) type BurnGenerator = ChainwebVersion -> Time Micros -> MVar PactId -> Chainweb.ChainId -> Chainweb.ChainId -> IO TransactionGenerator @@ -447,7 +455,7 @@ createCont -> MVar PactId -> Maybe ContProof -> Time Micros - -> IO (Vector ChainwebTransaction) + -> IO (Vector Pact4.Transaction) createCont v cid pidv proof time = do pid <- readMVar pidv fmap Vector.singleton $ diff --git a/test/unit/Chainweb/Test/Pact/SQLite.hs b/test/unit/Chainweb/Test/Pact4/SQLite.hs similarity index 94% rename from test/unit/Chainweb/Test/Pact/SQLite.hs rename to test/unit/Chainweb/Test/Pact4/SQLite.hs index 0c6877712b..fcf60a7fab 100644 --- a/test/unit/Chainweb/Test/Pact/SQLite.hs +++ b/test/unit/Chainweb/Test/Pact4/SQLite.hs @@ -3,7 +3,7 @@ {-# LANGUAGE TypeApplications #-} -- | --- Module: Chainweb.Test.Pact.SQLite +-- Module: Chainweb.Test.Pact4.SQLite -- Copyright: Copyright © 2022 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -11,7 +11,7 @@ -- -- TODO -- -module Chainweb.Test.Pact.SQLite +module Chainweb.Test.Pact4.SQLite ( tests ) where @@ -39,8 +39,9 @@ import Test.Tasty.HUnit -- internal modules -import Chainweb.Pact.Backend.Types + import Chainweb.Test.Utils +import Chainweb.Pact.Backend.Types (SQLiteEnv) -- -------------------------------------------------------------------------- -- -- Tests @@ -267,20 +268,20 @@ testAgg n dbVarIO tblIO = do [[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) + 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 + 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 -- -------------------------------------------------------------------------- -- diff --git a/test/unit/Chainweb/Test/Pact/TTL.hs b/test/unit/Chainweb/Test/Pact4/TTL.hs similarity index 93% rename from test/unit/Chainweb/Test/Pact/TTL.hs rename to test/unit/Chainweb/Test/Pact4/TTL.hs index 8f34c9fa7f..f199f2ce4c 100644 --- a/test/unit/Chainweb/Test/Pact/TTL.hs +++ b/test/unit/Chainweb/Test/Pact4/TTL.hs @@ -1,11 +1,14 @@ {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -module Chainweb.Test.Pact.TTL +module Chainweb.Test.Pact4.TTL ( tests ) where import Control.Concurrent.MVar @@ -16,6 +19,7 @@ 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 @@ -27,15 +31,15 @@ import Chainweb.BlockHeader import Chainweb.BlockHeaderDB hiding (withBlockHeaderDb) import Chainweb.BlockHeaderDB.Internal (unsafeInsertBlockHeaderDb) import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types + import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Service.Types -import Chainweb.Pact.Validations (defaultLenientTimeSlop) +import Chainweb.Pact.Types +import Chainweb.Pact4.Validations (defaultLenientTimeSlop) import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Test.Utils import Chainweb.Test.TestVersions import Chainweb.Time @@ -44,6 +48,7 @@ import Chainweb.Version import Chainweb.Version.Utils import Chainweb.Storage.Table.RocksDB +import Data.Either (isRight) -- -------------------------------------------------------------------------- -- -- Settings @@ -71,7 +76,7 @@ genblock = genesisBlockHeader testVer (someChainId testVer) -- validation. -- tests :: RocksDb -> TestTree -tests rdb = testGroup "Chainweb.Test.Pact.TTL" +tests rdb = testGroup "Chainweb.Test.Pact4.TTL" [ testGroup "timing tests" [ withTestPact rdb testTxTime , withTestPact rdb testTxTimeLenient @@ -174,8 +179,8 @@ modAt f = modAtTtl f defTtl modAtTtl :: (Time Micros -> Time Micros) -> Seconds -> MemPoolAccess modAtTtl f (Seconds t) = mempty - { mpaGetBlock = \_ validate bh hash ph -> do - let txTime = toTxCreationTime $ f $ _bct $ view blockCreationTime ph + { 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 @@ -184,8 +189,10 @@ modAtTtl f (Seconds t) = mempty $ set cbRPC (mkExec' "1") $ defaultCmd - unlessM (and <$> validate bh hash outtxs) $ throwM DoPreBlockFailure - return outtxs + tos <- validate bh hash $ (fmap . fmap . fmap) _pcCode outtxs + + unless (all isRight tos) $ throwM DoPreBlockFailure + return $ V.fromList [ to | Right to <- V.toList tos ] } -- -------------------------------------------------------------------------- -- @@ -220,7 +227,7 @@ doNewBlock ctxIO mempool parent nonce t = do error "Test failure: mempool access is not empty. Some previous test step failed unexpectedly" bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill parent (_ctxQueue ctx) - let payload = blockInProgressToPayloadWithOutputs bip + let payload = forAnyPactVersion finalizeBlock bip let creationTime = BlockCreationTime . add (secondsToTimeSpan t) -- 10 seconds diff --git a/test/unit/Chainweb/Test/Pact/TransactionTests.hs b/test/unit/Chainweb/Test/Pact4/TransactionTests.hs similarity index 88% rename from test/unit/Chainweb/Test/Pact/TransactionTests.hs rename to test/unit/Chainweb/Test/Pact4/TransactionTests.hs index 65544bae21..88334342bb 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionTests.hs +++ b/test/unit/Chainweb/Test/Pact4/TransactionTests.hs @@ -5,6 +5,10 @@ {-# 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. @@ -14,7 +18,7 @@ -- -- Test func in TransactionExec -- -module Chainweb.Test.Pact.TransactionTests ( tests ) where +module Chainweb.Test.Pact4.TransactionTests ( tests ) where import Test.Tasty import Test.Tasty.HUnit @@ -45,7 +49,6 @@ import Pact.Types.RPC import Pact.Types.Runtime import Pact.Types.SPV - -- internal chainweb modules import Chainweb.BlockCreationTime @@ -53,10 +56,10 @@ import Chainweb.BlockHeader.Internal import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Miner.Pact -import Chainweb.Pact.Service.Types -import Chainweb.Pact.Templates -import Chainweb.Pact.TransactionExec -import Chainweb.Pact.Types +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 @@ -64,7 +67,8 @@ import Chainweb.Utils import Chainweb.Version as V import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils +import qualified Chainweb.Pact4.ModuleCache as Pact4 -- ---------------------------------------------------------------------- -- @@ -102,7 +106,7 @@ logger = genericLogger L.Error (\_ -> return ()) -- Tests tests :: TestTree -tests = testGroup "Chainweb.Test.Pact.TransactionTests" +tests = testGroup "Chainweb.Test.Pact4.TransactionTests" [ testGroup "Pact Command Parsing" [ testCase "Build Exec with Data" buildExecWithData , testCase "Build Exec without Data" buildExecWithoutData @@ -125,15 +129,15 @@ tests = testGroup "Chainweb.Test.Pact.TransactionTests" [ 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 - ] + -- , 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 + -- ] ] -- ---------------------------------------------------------------------- -- @@ -151,10 +155,10 @@ ccReplTests ccFile = do failCC i e = assertFailure $ renderInfo (_faInfo i) <> ": " <> unpack e -loadCC :: FilePath -> IO (PactDbEnv LibState, ModuleCache) +loadCC :: FilePath -> IO (PactDbEnv LibState, Pact4.ModuleCache) loadCC = loadScript -loadScript :: FilePath -> IO (PactDbEnv LibState, ModuleCache) +loadScript :: FilePath -> IO (PactDbEnv LibState, Pact4.ModuleCache) loadScript fp = do (r, rst) <- execScript' Quiet fp either fail (const $ return ()) r @@ -162,7 +166,8 @@ loadScript fp = do (view (rEnv . eePactDb) rst) (view (rEnv . eePactDbVar) rst) mc = view (rEvalState . evalRefs . rsLoadedModules) rst - return (pdb, moduleCacheFromHashMap mc) + -- TODO: setup eval env & run the code & and pass + return (pdb, Pact4.moduleCacheFromHashMap mc) -- ---------------------------------------------------------------------- -- -- Template vuln tests @@ -254,10 +259,10 @@ testCoinbase797DateFix = testCaseSteps "testCoinbase791Fix" $ \step -> do where doCoinbaseExploit pdb mc height localCmd precompile testResult = do - let ctx = TxContext (mkTestParentHeader $ height - 1) noPublicMeta + let ctx = Pact4.TxContext (mkTestParentHeader $ height - 1) noPublicMeta miner - void $ applyCoinbase Mainnet01 logger pdb miner 0.1 ctx - (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled precompile) mc + 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 @@ -287,9 +292,9 @@ testCoinbaseEnforceFailure :: Assertion testCoinbaseEnforceFailure = do (pdb,mc) <- loadCC coinReplV4 r <- tryAllSynchronous $ - applyCoinbase toyVersion logger pdb badMiner 0.1 - (TxContext someParentHeader noPublicMeta) - (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled False) mc + 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 @@ -359,12 +364,12 @@ testUpgradeScript :: FilePath -> V.ChainId -> BlockHeight - -> (T2 (CommandResult [TxLogJson]) (Maybe ModuleCache) -> IO ()) + -> (T2 (CommandResult [TxLogJson]) (Maybe Pact4.ModuleCache) -> IO ()) -> IO () testUpgradeScript script cid bh test = do (pdb, mc) <- loadScript script - r <- tryAllSynchronous $ applyCoinbase v logger pdb noMiner 0.1 (TxContext parent noPublicMeta) - (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled False) mc + 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 diff --git a/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs b/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs new file mode 100644 index 0000000000..d33b68cc80 --- /dev/null +++ b/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs @@ -0,0 +1,14 @@ +module Chainweb.Test.Pact4.VerifierPluginTest +( tests +) where + +import Test.Tasty + +import qualified Chainweb.Test.Pact4.VerifierPluginTest.Transaction +import qualified Chainweb.Test.Pact4.VerifierPluginTest.Unit + +tests :: TestTree +tests = testGroup "Chainweb.Test.Pact4.VerifierPluginTest" + [ Chainweb.Test.Pact4.VerifierPluginTest.Unit.tests + , Chainweb.Test.Pact4.VerifierPluginTest.Transaction.tests + ] diff --git a/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs b/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs new file mode 100644 index 0000000000..cd7d8edcde --- /dev/null +++ b/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs @@ -0,0 +1,287 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ViewPatterns #-} + +module Chainweb.Test.Pact5.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.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import Hedgehog hiding (Update) +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +import Numeric.AffineSpace +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.PactValue +import Pact.Core.Persistence +import qualified Streaming.Prelude as Stream +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 GHC.Stack +import Chainweb.Pact.Backend.Types +import qualified Chainweb.Pact.PactService.Checkpointer.Internal as Checkpointer + +-- | A @DbAction f@ is a description of some action on the database together with an f-full of results for it. +type DbValue = Integer +data DbAction f + = DbRead !T.Text RowKey (f (Either Text (Maybe DbValue))) + | DbWrite WriteType !T.Text RowKey DbValue (f (Either Text ())) + | DbKeys !T.Text (f (Either Text [RowKey])) + | DbSelect !T.Text (f (Either Text [(RowKey, Integer)])) + | DbCreateTable T.Text (f (Either Text ())) + +mkTableName :: T.Text -> TableName +mkTableName n = TableName n (ModuleName "mod" Nothing) + +genDbAction :: Gen (DbAction (Const ())) +genDbAction = do + let tn = Gen.choice [pure "A", pure "B", pure "C"] + Gen.choice + [ DbRead + <$> tn + <*> Gen.choice (pure . RowKey <$> ["A", "B", "C"]) + <*> pure (Const ()) + , DbWrite + <$> genWriteType + <*> tn + <*> Gen.choice (pure . RowKey <$> ["A", "B", "C"]) + <*> fmap fromIntegral (Gen.int (Range.constant 0 5)) + <*> pure (Const ()) + , DbKeys <$> tn <*> pure (Const ()) + , DbSelect <$> tn <*> pure (Const ()) + , DbCreateTable <$> tn <*> pure (Const ()) + ] + where + genWriteType = Gen.choice $ fmap pure + [ Write + , Insert + , Update + ] + +-- a block is a list of actions +type DbBlock f = [DbAction f] + +genDbBlock :: Gen (DbBlock (Const ())) +genDbBlock = Gen.list (Range.constant 1 20) genDbAction + +genBlockHistory :: Gen [DbBlock (Const ())] +genBlockHistory = do + let create tn = DbCreateTable tn (Const ()) + blocks <- Gen.list (Range.linear 1 20) genDbBlock + -- we always start by making tables A and B to ensure the tests do something, + -- but we leave table C uncreated to leave some room for divergent table sets + return $ [create "A", create "B"] : blocks + +hoistDbAction :: (forall a. (Eq a, Show a) => f a -> g a) -> DbAction f -> DbAction g +hoistDbAction f (DbRead tn k r) = DbRead tn k (f r) +hoistDbAction f (DbWrite wt tn k v r) = DbWrite wt tn k v (f r) +hoistDbAction f (DbKeys tn ks) = DbKeys tn (f ks) +hoistDbAction f (DbSelect tn rs) = DbSelect tn (f rs) +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 + ) . fmap Right + +-- Run an empty DbAction, annotating it with its result +runDbAction :: PactDb CoreBuiltin Info -> DbAction (Const ()) -> IO (DbAction Identity) +runDbAction pactDB act = + fmap (hoistDbAction (\(Pair (Const ()) fa) -> fa)) + $ runDbAction' pactDB act + +extractInt :: RowData -> IO Integer +extractInt (RowData m) = evaluate (m ^?! ix (Field "k") . _PLiteral . _LInteger) + +-- Annotate a DbAction with its result, including any other contents it has +runDbAction' :: PactDb CoreBuiltin Info -> DbAction f -> IO (DbAction (Product f Identity)) +runDbAction' pactDB = \case + DbRead tn k v -> do + maybeValue <- tryShow $ ignoreGas noInfo $ _pdbRead pactDB (DUserTables (mkTableName tn)) k + integerValue <- (traverse . traverse) extractInt maybeValue + return $ DbRead tn k $ Pair v (Identity integerValue) + DbWrite wt tn k v s -> + fmap (DbWrite wt tn k v . Pair s . Identity) + $ tryShow $ ignoreGas noInfo + $ _pdbWrite pactDB wt (DUserTables (mkTableName tn)) k (RowData $ Map.singleton (Field "k") $ PLiteral $ LInteger v) + DbKeys tn ks -> + fmap (DbKeys tn . Pair ks . Identity) + $ tryShow $ ignoreGas noInfo $ _pdbKeys pactDB (DUserTables (mkTableName tn)) + DbSelect tn rs -> + fmap (DbSelect tn . Pair rs . Identity) + $ tryShow $ do + ks <- ignoreGas noInfo $ _pdbKeys pactDB (DUserTables (mkTableName tn)) + traverse (\k -> fmap (k,) . extractInt . fromJuste =<< ignoreGas noInfo (_pdbRead pactDB (DUserTables (mkTableName tn)) k)) ks + DbCreateTable tn s -> + fmap (DbCreateTable tn . Pair s . Identity) + $ tryShow (ignoreGas noInfo $ _pdbCreateUserTable pactDB (mkTableName tn)) + +-- 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) + ] + | 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 + +-- 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 + -> [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 + +-- 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 :: HasCallStack => 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 + _ <- 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 + + actualBh <- blockHeaderFromTxLogs ph txLogs + assertEqual "block header" expectedBh actualBh + return () + throwIfNoHistory hist + +tests :: TestTree +tests = testGroup "Pact5 Checkpointer tests" + [ withResourceT (liftIO . initCheckpointer testVer cid =<< withTempSQLiteResource) $ \cpIO -> + testCase "valid PactDb before genesis" $ do + cp <- cpIO + ((), _handle) <- (throwIfNoHistory =<<) $ + Checkpointer.readFrom cp Nothing Pact5T $ \db blockHandle -> do + doPact5DbTransaction db blockHandle Nothing $ \txdb -> + Pact.Core.runPactDbRegression txdb + return () + , withResourceT (liftIO . initCheckpointer testVer cid =<< withTempSQLiteResource) $ \cpIO -> + testProperty "readFrom with linear block history is valid" $ withTests 1000 $ property $ do + blocks <- forAll genBlockHistory + liftIO $ do + cp <- cpIO + -- extend this empty chain with the genesis block + ((), ()) <- Checkpointer.restoreAndSave cp Nothing $ Stream.yield $ Pact5RunnableBlock $ \_ _ hndl -> + return (((), gh), hndl) + -- 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 + ] + +testVer :: ChainwebVersion +testVer = pact5CheckpointerTestVersion singletonChainGraph + +cid :: ChainId +cid = unsafeChainId 0 + +gh :: BlockHeader +gh = genesisBlockHeader 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 + . showString " " . showsPrec 11 k + . showString " " . showsPrec 11 v + showsPrec n (DbWrite wt tn k v r) = showParen (n > 10) $ + showString "DbWrite " . showsPrec 11 wt + . showString " " . showsPrec 11 tn + . showString " " . showsPrec 11 k + . showString " " . showsPrec 11 v + . showString " " . showsPrec 11 r + showsPrec n (DbKeys tn ks) = showParen (n > 10) $ + showString "DbKeys " . showsPrec 11 tn + . showString " " . showsPrec 11 ks + showsPrec n (DbSelect tn rs) = showParen (n > 10) $ + showString "DbSelect " . showsPrec 11 tn + . showString " " . showsPrec 11 rs + showsPrec n (DbCreateTable tn r) = showParen (n > 10) $ + showString "DbSelect " . showsPrec 11 tn + . showString " " . showsPrec 11 r diff --git a/test/unit/Chainweb/Test/Pact5/CutFixture.hs b/test/unit/Chainweb/Test/Pact5/CutFixture.hs new file mode 100644 index 0000000000..b86ba0bd06 --- /dev/null +++ b/test/unit/Chainweb/Test/Pact5/CutFixture.hs @@ -0,0 +1,311 @@ +{-# language + BangPatterns + , DataKinds + , DeriveAnyClass + , DerivingStrategies + , FlexibleContexts + , ImportQualifiedPost + , LambdaCase + , NumericUnderscores + , OverloadedStrings + , PackageImports + , RankNTypes + , RecordWildCards + , ScopedTypeVariables + , TemplateHaskell + , TypeApplications +#-} + +-- | A fixture which provides access to the internals of a running node, with +-- 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 + ( Fixture(..) + , mkFixture + , fixtureCutDb + , fixturePayloadDb + , fixtureWebBlockHeaderDb + , fixtureWebPactExecutionService + , fixtureMempools + , fixturePactQueues + , advanceAllChains + , advanceAllChains_ + , 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.ChainId +import Chainweb.ChainValue (ChainValue(..), ChainValueCasLookup, chainLookupM) +import Chainweb.Cut +import Chainweb.Cut.Create (InvalidSolvedHeader(..), WorkHeader(..), SolvedWork(..), extendCut, getCutExtension, newWorkHeaderPure) +import Chainweb.Cut.CutHashes +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.Pact4.Transaction qualified as Pact4 +import Chainweb.Payload +import Chainweb.Payload.PayloadStore +import Chainweb.Storage.Table.RocksDB +import Chainweb.Sync.WebBlockHeaderStore +import Chainweb.Test.Pact5.Utils +import Chainweb.Time +import Chainweb.Utils +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 +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 +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 Pact.Core.Command.Types +import Pact.Core.Hash qualified as Pact5 + +data Fixture = Fixture + { _fixtureCutDb :: CutDb RocksDbTable + , _fixturePayloadDb :: PayloadDb RocksDbTable + , _fixtureWebBlockHeaderDb :: WebBlockHeaderDb + , _fixtureWebPactExecutionService :: WebPactExecutionService + , _fixtureMempools :: ChainMap (MempoolBackend Pact4.UnparsedTransaction) + , _fixturePactQueues :: ChainMap PactQueue + } +makeLenses ''Fixture + +mkFixture :: ChainwebVersion -> PactServiceConfig -> RocksDb -> ResourceT IO Fixture +mkFixture v pactServiceConfig baseRdb = do + logger <- liftIO getTestLogger + (payloadDb, webBHDb) <- withBlockDbs v baseRdb + 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 + let cutHashesStore = cutHashesTable baseRdb + cutDb <- withTestCutDb logger v webBHDb payloadDb cutHashesStore webPact + + let fixture = Fixture + { _fixtureCutDb = cutDb + , _fixturePayloadDb = payloadDb + , _fixtureWebBlockHeaderDb = webBHDb + , _fixtureWebPactExecutionService = webPact + , _fixtureMempools = OnChains $ fst <$> perChain + , _fixturePactQueues = OnChains $ snd <$> perChain + } + _ <- 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 + => Fixture + -> IO (Cut, ChainMap (Vector (CommandResult Pact5.Hash Text))) +advanceAllChains Fixture{..} = do + let v = _chainwebVersion _fixtureCutDb + latestCut <- liftIO $ _fixtureCutDb ^. cut + let blockHeights = fmap (view blockHeight) $ latestCut ^. cutMap + let latestBlockHeight = maximum blockHeights + + (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 + + addNewPayload _fixturePayloadDb latestBlockHeight pwo + + return $ (newCut, (cid, commandResults) : acc) + ) + (latestCut, []) + (HashSet.toList (chainIdsAt v (latestBlockHeight + 1))) + + return (finalCut, onChains perChainCommandResults) + +advanceAllChains_ + :: HasCallStack + => Fixture + -> IO () +advanceAllChains_ f = void $ advanceAllChains f + +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 + + 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 + + destroy :: CutDb RocksDbTable -> 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.Pact5.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 + +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' + +-- | 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 + +-- | 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 + +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/Pact5/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact5/PactServiceTest.hs new file mode 100644 index 0000000000..d65ab2e0c5 --- /dev/null +++ b/test/unit/Chainweb/Test/Pact5/PactServiceTest.hs @@ -0,0 +1,567 @@ +{-# language + DataKinds + , FlexibleContexts + , ImpredicativeTypes + , ImportQualifiedPost + , LambdaCase + , NumericUnderscores + , OverloadedStrings + , PackageImports + , ScopedTypeVariables + , TypeApplications + , TemplateHaskell + , RecordWildCards + , TupleSections +#-} + +{-# options_ghc -fno-warn-gadt-mono-local-binds #-} + +module Chainweb.Test.Pact5.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 Chainweb.BlockHeader +import Chainweb.ChainId +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.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact5.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, testRocksDb) +import Chainweb.Test.TestVersions +import Chainweb.Test.Utils +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 (..)) +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 Pact4 +import PropertyMatchers ((?)) +import PropertyMatchers qualified as P +import Test.Tasty +import Test.Tasty.HUnit (assertBool, assertEqual, assertFailure, testCase) +import Text.Printf (printf) + +data Fixture = Fixture + { _fixtureBlockDb :: TestBlockDb + , _fixtureMempools :: ChainMap (MempoolBackend Pact4.UnparsedTransaction) + , _fixturePactQueues :: ChainMap PactQueue + } + +mkFixtureWith :: PactServiceConfig -> RocksDb -> ResourceT IO Fixture +mkFixtureWith pactServiceConfig baseRdb = do + sqlite <- withTempSQLiteResource + tdb <- liftIO $ mkTestBlockDb v =<< testRocksDb "fixture" 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 + logLevel <- liftIO getTestLogLevel + let logger = genericLogger logLevel Text.putStrLn + 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 fixture = Fixture + { _fixtureBlockDb = tdb + , _fixtureMempools = OnChains $ fst <$> perChain + , _fixturePactQueues = 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 [] + return fixture + +mkFixture :: RocksDb -> ResourceT IO Fixture +mkFixture baseRdb = do + mkFixtureWith testPactServiceConfig 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 "mempool excludes invalid transactions" (testMempoolExcludesInvalid baseRdb) + , testCase "lookup pact txs spec" (lookupPactTxsSpec baseRdb) + , testCase "failed txs should go into blocks" (failedTxsShouldGoIntoBlocks baseRdb) + ] + +successfulTx :: P.Prop (CommandResult log err) +successfulTx = P.fun _crResult ? P.match _PactResultOk P.succeed + +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 cid [cmd1, cmd2] + + -- we only care that they succeed; specifics regarding their outputs are in TransactionExecTest + results & + P.propful ? onChain cid ? + P.propful ? 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 cid $ \ph pactQueue mempool -> do + insertMempool 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 cid $ \ph pactQueue _ -> do + nonEmptyBip <- throwIfNotPact5 =<< throwIfNoHistory =<< + newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue + return $ finalizeBlock nonEmptyBip + + results & + P.propful ? onChain cid ? + P.propful ? 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 cid $ \ph pactQueue mempool -> do + -- insert all transactions + insertMempool 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.propful ? onChain cid ? + P.propful ? 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 cid $ \ph pactQueue mempool -> do + mempoolClear mempool + insertMempool mempool CheckedInsert [cmd3] + bipStart <- throwIfNotPact5 =<< throwIfNoHistory =<< + newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue + + insertMempool mempool CheckedInsert [cmd2] + bipContinued <- throwIfNoHistory =<< continueBlock bipStart pactQueue + + insertMempool 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 + { _cbRPC = mkExec' "1" + , _cbSigners = [mkEd25519Signer' sender00 []] + , _cbChainId = cid + , _cbGasPrice = GasPrice 1.0 + , _cbGasLimit = GasLimit (Gas 400) + } + tx2 <- buildCwCmd v $ defaultCmd + { _cbRPC = mkExec' "2" + , _cbSigners = [mkEd25519Signer' sender00 []] + , _cbChainId = cid + , _cbGasPrice = GasPrice 2.0 + , _cbGasLimit = GasLimit (Gas 400) + } + timeoutTx <- buildCwCmd v defaultCmd + { _cbRPC = mkExec' $ foldr (\_ expr -> "(map (lambda (x) (+ x 1))" <> expr <> ")") "(enumerate 1 100000)" [1..6_000 :: Word] -- make a huge nested tx + , _cbSigners = + [ mkEd25519Signer' sender00 [] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _cbGasPrice = GasPrice 1.5 + , _cbGasLimit = GasLimit (Gas 5000) + } + + _ <- advanceAllChains fixture $ onChain cid $ \ph pactQueue mempool -> do + insertMempool 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.propful ? Vector.fromList + [ P.pair + (P.fun _cmdHash ? P.equals (_cmdHash tx2)) + successfulTx + ] + return $ finalizeBlock bip + + pure () + +testMempoolExcludesInvalid :: RocksDb -> IO () +testMempoolExcludesInvalid 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 + { _cbRPC = mkExec' "(not a valid pact tx" + , _cbSigners = + [ mkEd25519Signer' sender00 [] + ] + , _cbSender = "sender00" + , _cbChainId = cid + } + + 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 + { _cbSigners = + [ CmdSigner + { _csSigner = Signer + { _siScheme = Nothing + , _siPubKey = fst sender00 + , _siAddress = Nothing + , _siCapList = [] + } + , _csPrivKey = snd sender01 + } + ] + } + + let pact4Hash = Pact5.Hash . Pact4.unHash . Pact4.toUntypedHash . Pact4._cmdHash + _ <- advanceAllChains fixture $ onChain cid $ \ph pactQueue mempool -> do + insertMempool mempool CheckedInsert [regularTx1] + bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue + return $ finalizeBlock bip + + _ <- advanceAllChains fixture $ onChain cid $ \ph pactQueue mempool -> do + mempoolInsert mempool CheckedInsert $ Vector.fromList [badParse, badSigs] + insertMempool mempool CheckedInsert [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 cid + insertMempool mempool CheckedInsert [badUnique, badFuture, badPast, badTxHash] + + let badTxHashes = + [ pact4Hash badParse + , _cmdHash badUnique + , _cmdHash badFuture + , _cmdHash badPast + , _cmdHash badTxHash + , pact4Hash badSigs + ] + inMempool <- lookupMempool 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 cid $ \ph pactQueue mempool -> do + insertMempool 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 cid) + 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 cid) + 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 + { _cbRPC = mkExec' "(namespace 'free) (module mod G (defcap G () true) (defun f () true)) (describe-module \"free.mod\")" + , _cbSigners = + [ mkEd25519Signer' sender00 + [] + ] + , _cbSender = "sender00" + , _cbChainId = cid + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.1 + , _cbGasLimit = GasLimit (Gas 1000) + } + + -- Depth 0 + _ <- advanceAllChains fixture $ onChain cid $ \ph pactQueue mempool -> do + insertMempool 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 () + +{- +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 +-} + +cid :: ChainId +cid = unsafeChainId 0 + +v :: ChainwebVersion +v = pact5InstantCpmTestVersion singletonChainGraph + +coinModuleName :: ModuleName +coinModuleName = ModuleName "coin" Nothing + +advanceAllChainsWithTxs :: Fixture -> ChainMap [Pact5.Transaction] -> IO (ChainMap (Vector TestPact5CommandResult)) +advanceAllChainsWithTxs fixture txsPerChain = + advanceAllChains fixture $ + txsPerChain <&> \txs ph pactQueue mempool -> do + mempoolClear mempool + insertMempool 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) + -> 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 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 cid) ph pactQueue mempool + added <- addTestBlockDb _fixtureBlockDb + (succ $ view blockHeight ph) + (Nonce 0) + (\_ _ -> creationTime) + c + payload + 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' + commandResults :: Vector TestPact5CommandResult + <- forM + (_payloadWithOutputsTransactions payload') + (decodeOrThrow' + . LBS.fromStrict + . _transactionOutputBytes + . snd) + -- assert on the command results + return (c, commandResults) + + return (onChains commandResults) + +getCut :: Fixture -> IO Cut +getCut Fixture{..} = getCutTestBlockDb _fixtureBlockDb + +revert :: Fixture -> Cut -> IO () +revert Fixture{..} c = do + setCutTestBlockDb _fixtureBlockDb c + forM_ (HashSet.toList (chainIds v)) $ \chain -> 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 + { _cbRPC = mkExec' $ + "(coin.transfer \"sender00\" \"sender01\" " <> + -- if the number doesn't end with a decimal part, even if it's zero, Pact will + -- throw an error + T.pack (printf "%.4f" (realToFrac transferAmount :: Double)) <> + ")" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] + , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal transferAmount] + ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice transferAmount + , _cbGasLimit = GasLimit (Gas 1000) + } diff --git a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs new file mode 100644 index 0000000000..2fe4e99a6d --- /dev/null +++ b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs @@ -0,0 +1,580 @@ +{-# language + DataKinds + , DeriveAnyClass + , DerivingStrategies + , FlexibleContexts + , ImpredicativeTypes + , ImportQualifiedPost + , LambdaCase + , NumericUnderscores + , OverloadedStrings + , PatternSynonyms + , PackageImports + , ScopedTypeVariables + , TypeApplications + , TemplateHaskell + , RecordWildCards + , TupleSections +#-} + +{-# options_ghc -fno-warn-gadt-mono-local-binds #-} + +-- temporary +{-# options_ghc -Wwarn -fno-warn-name-shadowing -fno-warn-unused-top-binds #-} +{-# LANGUAGE PartialTypeSignatures #-} +{-# OPTIONS_GHC -Wno-partial-type-signatures #-} + +module Chainweb.Test.Pact5.RemotePactTest + ( tests + ) where + +import Control.Concurrent.Async +import Control.Exception.Safe +import Control.Lens +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 qualified as Aeson +import Data.Aeson.Lens qualified as A +import Data.ByteString.Base64.URL qualified as B64U +import Data.ByteString.Lazy qualified as BL +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.List.NonEmpty (NonEmpty) +import Data.List.NonEmpty qualified as NE +import Data.Maybe (fromMaybe) +import Data.String (fromString) +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import Network.Connection qualified as HTTP +import Network.HTTP.Client.TLS qualified as HTTP +import Network.Socket qualified as Network +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 PropertyMatchers ((?)) +import PropertyMatchers qualified as P +import Servant.Client +import Test.Tasty +import Test.Tasty.HUnit (assertEqual, testCase, testCaseSteps) + +import Pact.Core.Capabilities +import Pact.Core.Command.RPC (ContMsg (..)) +import Pact.Core.Command.Server qualified as Pact5 +import Pact.Core.Command.Types +import Pact.Core.DefPacts.Types +import Pact.Core.Gas.Types +import Pact.Core.Hash qualified as Pact5 +import Pact.Core.Names +import Pact.Core.PactValue +import Pact.Core.SPV +import Pact.JSON.Encode qualified as J +import Pact.Types.Command qualified as Pact4 +import Pact.Types.API qualified as Pact4 +import Pact.Types.Hash qualified as Pact4 + +import Chainweb.BlockHeader (blockHeight) +import Chainweb.ChainId +import Chainweb.CutDB.RestAPI.Server (someCutGetServer) +import Chainweb.Graph (petersonChainGraph, singletonChainGraph) +import Chainweb.Mempool.Mempool (TransactionHash (..)) +import Chainweb.Pact.RestAPI.Client +import Chainweb.Pact.RestAPI.Server +import Chainweb.Pact.Types +import Chainweb.RestAPI.Utils (someServerApplication) +import Chainweb.SPV.CreateProof (createTransactionOutputProof_) +import Chainweb.Storage.Table.RocksDB +import Chainweb.Test.Pact5.CmdBuilder +import Chainweb.Test.Pact5.CutFixture qualified as CutFixture +import Chainweb.Test.Pact5.Utils +import Chainweb.Test.TestVersions +import Chainweb.Test.Utils (TestPact5CommandResult, deadbeef) +import Chainweb.Utils +import Chainweb.Version +import Chainweb.WebPactExecutionService + +data Fixture = Fixture + { _cutFixture :: CutFixture.Fixture + , _serviceClientEnv :: ClientEnv + } +makeLenses ''Fixture + +mkFixture :: ChainwebVersion -> RocksDb -> ResourceT IO Fixture +mkFixture v baseRdb = do + fixture <- CutFixture.mkFixture v testPactServiceConfig baseRdb + logger <- liftIO getTestLogger + + let mkSomePactServerData chainId = PactServerData + { _pactServerDataCutDb = fixture ^. CutFixture.fixtureCutDb + , _pactServerDataMempool = fixture ^. CutFixture.fixtureMempools ^?! atChain chainId + , _pactServerDataLogger = logger + , _pactServerDataPact = mkPactExecutionService (fixture ^. CutFixture.fixturePactQueues ^?! atChain chainId) + } + let pactServer = somePactServers v $ List.map (\chainId -> (chainId, mkSomePactServerData chainId)) (HashSet.toList (chainIds v)) + let cutGetServer = someCutGetServer v (fixture ^. CutFixture.fixtureCutDb) + let app = someServerApplication (pactServer <> cutGetServer) + + (_fingerprint, cert, key) <- liftIO $ generateLocalhostCertificate @RsaCert 1 + + -- Run pact server API + (port, socket) <- snd <$> allocate W.openFreePort (Network.close . snd) + _ <- allocate + (async $ + W.runTLSSocket (tlsServerSettings cert key) W.defaultSettings socket app + ) + cancel + + serviceClientEnv <- liftIO $ do + let defaultTLSSettings = (HTTP.TLSSettingsSimple True False False TLS.defaultSupported) + httpManager <- HTTP.newTlsManagerWith (HTTP.mkManagerSettings defaultTLSSettings Nothing) + return $ mkClientEnv httpManager $ BaseUrl + { baseUrlScheme = Https + , baseUrlHost = "127.0.0.1" + , baseUrlPort = port + , baseUrlPath = "" + } + + return $ Fixture + { _cutFixture = fixture + , _serviceClientEnv = serviceClientEnv + } + +tests :: RocksDb -> TestTree +tests rdb = testGroup "Pact5 RemotePactTest" + [ testCase "pollingInvalidRequestKeyTest" (pollingInvalidRequestKeyTest rdb) + , testCase "pollingConfirmationDepthTest" (pollingConfirmationDepthTest rdb) + , testCase "spvTest" (spvTest rdb) + , testCase "invalidTxsTest" (invalidTxsTest rdb) + , testCaseSteps "caplistTest" (caplistTest rdb) + ] + +pollingInvalidRequestKeyTest :: RocksDb -> IO () +pollingInvalidRequestKeyTest baseRdb = runResourceT $ do + let v = pact5InstantCpmTestVersion singletonChainGraph + let cid = unsafeChainId 0 + fixture <- mkFixture v baseRdb + let clientEnv = fixture ^. serviceClientEnv + + liftIO $ do + pollResult <- polling v cid clientEnv (NE.singleton pactDeadBeef) + assertEqual "invalid poll should return no results" pollResult HashMap.empty + +pollingConfirmationDepthTest :: RocksDb -> IO () +pollingConfirmationDepthTest baseRdb = runResourceT $ do + let v = pact5InstantCpmTestVersion singletonChainGraph + let cid = unsafeChainId 0 + fixture <- mkFixture v baseRdb + let clientEnv = fixture ^. serviceClientEnv + + let trivialTx :: ChainId -> Word -> CmdBuilder + trivialTx cid n = defaultCmd + { _cbRPC = mkExec' (sshow n) + , _cbSigners = + [ mkEd25519Signer' sender00 [] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _cbGasPrice = GasPrice 0.1 + , _cbGasLimit = GasLimit (Gas 1_000) + } + + liftIO $ do + cmd1 <- buildTextCmd v (trivialTx cid 42) + cmd2 <- buildTextCmd v (trivialTx cid 43) + let rks = Pact5.cmdToRequestKey cmd1 NE.:| [Pact5.cmdToRequestKey cmd2] + + let expectSuccessful :: (HasCallStack, _) => P.Prop (HashMap RequestKey (CommandResult _ _)) + expectSuccessful = P.propful ? HM.fromList + [ (Pact5.cmdToRequestKey cmd1, P.fun _crResult ? P.equals (PactResultOk (PInteger 42))) + , (Pact5.cmdToRequestKey cmd2, P.fun _crResult ? P.equals (PactResultOk (PInteger 43))) + ] + let expectEmpty :: (HasCallStack, _) => _ + expectEmpty = P.equals HashMap.empty + + sending v cid clientEnv (cmd1 NE.:| [cmd2]) + >>= P.equals rks + + pollingWithDepth v cid clientEnv rks Nothing + >>= expectEmpty + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 0)) + >>= expectEmpty + + CutFixture.advanceAllChains_ (fixture ^. cutFixture) + + pollingWithDepth v cid clientEnv rks Nothing + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 0)) + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 1)) + >>= expectEmpty + + CutFixture.advanceAllChains_ (fixture ^. cutFixture) + + pollingWithDepth v cid clientEnv rks Nothing + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 0)) + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 1)) + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 2)) + >>= expectEmpty + + CutFixture.advanceAllChains_ (fixture ^. cutFixture) + + pollingWithDepth v cid clientEnv rks Nothing + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 0)) + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 1)) + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 2)) + >>= expectSuccessful + pollingWithDepth v cid clientEnv rks (Just (ConfirmationDepth 3)) + >>= expectEmpty + + return () + +spvTest :: RocksDb -> IO () +spvTest baseRdb = runResourceT $ do + let v = pact5InstantCpmTestVersion petersonChainGraph + fixture <- mkFixture v baseRdb + let clientEnv = fixture ^. serviceClientEnv + + let srcChain = unsafeChainId 0 + let targetChain = unsafeChainId 9 + + liftIO $ do + send <- 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])) + $ set cbSender "sender00" + $ set cbChainId srcChain + $ set cbGasPrice (GasPrice 0.01) + $ set cbGasLimit (GasLimit (Gas 1_000)) + $ defaultCmd + + sendReqKey <- fmap NE.head $ sending v srcChain clientEnv (NE.singleton send) + (sendCut, _) <- CutFixture.advanceAllChains (fixture ^. cutFixture) + sendCr <- fmap (HashMap.! sendReqKey) $ pollingWithDepth v srcChain clientEnv (NE.singleton sendReqKey) (Just (ConfirmationDepth 0)) + let cont = fromMaybe (error "missing continuation") (_crContinuation sendCr) + + _ <- replicateM_ 10 $ do + CutFixture.advanceAllChains (fixture ^. cutFixture) + let sendHeight = sendCut ^?! ixg srcChain . blockHeight + spvProof <- createTransactionOutputProof_ (fixture ^. cutFixture . CutFixture.fixtureWebBlockHeaderDb) (fixture ^. cutFixture . CutFixture.fixturePayloadDb) targetChain srcChain sendHeight 0 + let contMsg = ContMsg + { _cmPactId = _peDefPactId cont + , _cmStep = succ $ _peStep cont + , _cmRollback = _peStepHasRollback cont + , _cmData = PUnit + , _cmProof = Just (ContProof (B64U.encode (BL.toStrict (A.encode spvProof)))) + } + + recv <- buildTextCmd v + $ set cbSigners + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] + ] + ] + $ set cbRPC (mkCont contMsg) + $ set cbChainId targetChain + $ set cbGasPrice (GasPrice 0.01) + $ set cbGasLimit (GasLimit (Gas 1_000)) + $ defaultCmd + recvReqKey <- fmap NE.head $ sending v targetChain clientEnv (NE.singleton recv) + _ <- CutFixture.advanceAllChains (fixture ^. cutFixture) + recvCr <- fmap (HashMap.! recvReqKey) $ polling v targetChain clientEnv (NE.singleton recvReqKey) + recvCr + & P.allTrue + [ P.fun _crResult ? P.match _PactResultOk P.succeed + , P.fun _crEvents ? P.propful + [ P.succeed + , P.allTrue + [ 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 + ] + ] + + pure () + + pure () + +fails :: Exception e => P.Prop e -> P.Prop (IO a) +fails p actual = try actual >>= \case + Left e -> p e + _ -> P.fail "a failed computation" actual + +invalidTxsTest :: RocksDb -> IO () +invalidTxsTest baseRdb = runResourceT $ do + let v = pact5InstantCpmTestVersion petersonChainGraph + fixture <- mkFixture v baseRdb + let clientEnv = fixture ^. serviceClientEnv + + let cid = unsafeChainId 0 + + let assertExnContains expectedErrStr (SendingException actualErrStr) + | expectedErrStr `List.isInfixOf` actualErrStr = P.succeed actualErrStr + | otherwise = + P.fail ("Error containing: " <> fromString expectedErrStr) actualErrStr + + let validationFailedPrefix cmd = "Validation failed for hash " <> sshow (_cmdHash cmd) <> ": " + + liftIO $ do + cmdParseFailure <- buildTextCmd v + $ set cbChainId cid + $ set cbRPC (mkExec "(+ 1" PUnit) + $ defaultCmd + sending v cid clientEnv (NE.singleton cmdParseFailure) + & fails ? assertExnContains "Pact parse error" + + cmdInvalidPayloadHash <- do + bareCmd <- buildTextCmd v + $ set cbChainId cid + $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) + $ defaultCmd + pure $ bareCmd + { _cmdHash = Pact5.hash "fakehash" + } + sending v cid clientEnv (NE.singleton cmdInvalidPayloadHash) + & fails ? assertExnContains (validationFailedPrefix cmdInvalidPayloadHash <> "Invalid transaction hash") + + cmdSignersSigsLengthMismatch1 <- do + bareCmd <- buildTextCmd v + $ set cbSigners [mkEd25519Signer' sender00 []] + $ set cbChainId cid + $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) + $ defaultCmd + pure $ bareCmd + { _cmdSigs = [] + } + sending v cid clientEnv (NE.singleton cmdSignersSigsLengthMismatch1) + & fails ? assertExnContains (validationFailedPrefix cmdSignersSigsLengthMismatch1 <> "Invalid transaction sigs") + + cmdSignersSigsLengthMismatch2 <- liftIO $ do + bareCmd <- buildTextCmd v + $ set cbSigners [] + $ 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"] + } + sending v cid clientEnv (NE.singleton cmdSignersSigsLengthMismatch2) + & fails ? assertExnContains (validationFailedPrefix cmdSignersSigsLengthMismatch2 <> "Invalid transaction sigs") + + cmdInvalidUserSig <- liftIO $ do + bareCmd <- buildTextCmd v + $ set cbSigners [mkEd25519Signer' sender00 []] + $ set cbChainId cid + $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) + $ defaultCmd + pure $ bareCmd + { _cmdSigs = [ED25519Sig "fakeSig"] + } + + sending v cid clientEnv (NE.singleton cmdInvalidUserSig) + & fails ? assertExnContains (validationFailedPrefix cmdInvalidUserSig <> "Invalid transaction sigs") + + cmdGood <- buildTextCmd v + $ set cbSigners [mkEd25519Signer' sender00 []] + $ 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. + sending v cid clientEnv (NE.fromList [cmdInvalidUserSig, cmdGood]) + & fails ? assertExnContains (validationFailedPrefix cmdInvalidUserSig <> "Invalid transaction sigs") + -- 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. + sending v cid clientEnv (NE.fromList [cmdGood, cmdInvalidUserSig]) + & fails ? assertExnContains (validationFailedPrefix cmdInvalidUserSig <> "Invalid transaction sigs") + +caplistTest :: RocksDb -> (String -> IO ()) -> IO () +caplistTest baseRdb step = runResourceT $ do + let testCaseStep m = liftIO (step m) + + testCaseStep "setting up" + let v = pact5InstantCpmTestVersion petersonChainGraph + fixture <- mkFixture v baseRdb + let clientEnv = fixture ^. serviceClientEnv + + let cid = unsafeChainId 0 + + liftIO $ do + + tx0 <- buildTextCmd v + $ set cbSigners + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] + , CapToken (QualifiedName "TRANSFER" (ModuleName "coin" Nothing)) + [ PString "sender00" + , PString "sender01" + , PDecimal 100.0 + ] + ] + ] + $ set cbChainId cid + $ set cbRPC (mkExec "(coin.transfer \"sender00\" \"sender01\" 100.0)" PUnit) + $ defaultCmd + + testCaseStep "sending" + + recvReqKey <- fmap NE.head $ sending v cid clientEnv (NE.fromList [tx0]) + + testCaseStep "advancing chains" + + CutFixture.advanceAllChains_ (fixture ^. cutFixture) + + testCaseStep "polling" + + polling v cid clientEnv (NE.fromList [recvReqKey]) >>= + P.propful ? HashMap.singleton recvReqKey ? + P.allTrue + [ 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 + ] + + +{- + recvPwos <- runCutWithTx v pacts targetMempoolRef blockDb $ \_n _bHeight _bHash bHeader -> do + buildCwCmd "transfer-crosschain" v + $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap]] + $ set cbRPC (mkCont contMsg) + $ setFromHeader bHeader + $ set cbChainId targetChain + $ set cbGasPrice 0.01 + $ set cbTTL 100 + $ defaultCmd +-} + +{- +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 + ] +-} + +newtype PollingException = PollingException String + deriving stock (Show) + deriving anyclass (Exception) + +polling :: () + => ChainwebVersion + -> ChainId + -> ClientEnv + -> NonEmpty RequestKey + -> IO (HashMap RequestKey TestPact5CommandResult) +polling v cid clientEnv rks = do + pollingWithDepth v cid clientEnv rks Nothing + +pollingWithDepth :: () + => ChainwebVersion + -> ChainId + -> ClientEnv + -> NonEmpty RequestKey + -> Maybe ConfirmationDepth + -> IO (HashMap RequestKey TestPact5CommandResult) +pollingWithDepth v cid clientEnv rks mConfirmationDepth = do + poll <- runClientM (pactPollWithQueryApiClient v cid mConfirmationDepth (Pact5.PollRequest rks)) clientEnv + case poll of + Left e -> do + throwM (PollingException (show e)) + Right (Pact5.PollResponse response) -> do + return response + +newtype SendingException = SendingException String + deriving stock (Show) + deriving anyclass (Exception) + +sending :: () + => ChainwebVersion + -> ChainId + -> ClientEnv + -> NonEmpty (Command Text) + -> IO (NonEmpty RequestKey) +sending v cid clientEnv cmds = do + let batch = Pact4.SubmitBatch (NE.map toPact4Command cmds) + send <- runClientM (pactSendApiClient v cid batch) clientEnv + case send of + Left (FailureResponse _req resp) -> do + throwM (SendingException (T.unpack $ T.decodeUtf8 $ BL.toStrict (responseBody resp))) + Left e -> + throwM (SendingException (show e)) + Right (Pact4.RequestKeys response) -> do + return (NE.map toPact5RequestKey response) + +toPact5RequestKey :: Pact4.RequestKey -> RequestKey +toPact5RequestKey = \case + Pact4.RequestKey (Pact4.Hash bytes) -> RequestKey (Pact5.Hash bytes) + +toPact4Command :: Command Text -> Pact4.Command Text +toPact4Command cmd4 = case Aeson.eitherDecodeStrictText (J.encodeText cmd4) of + Left err -> error $ "toPact4Command: decode failed: " ++ err + Right cmd5 -> cmd5 + +_successfulTx :: P.Prop (CommandResult log err) +_successfulTx = P.fun _crResult ? P.match _PactResultOk P.succeed + +pactDeadBeef :: RequestKey +pactDeadBeef = case deadbeef of + TransactionHash bytes -> RequestKey (Pact5.Hash bytes) diff --git a/test/unit/Chainweb/Test/Pact5/SPVTest.hs b/test/unit/Chainweb/Test/Pact5/SPVTest.hs new file mode 100644 index 0000000000..c42a6bfbed --- /dev/null +++ b/test/unit/Chainweb/Test/Pact5/SPVTest.hs @@ -0,0 +1,260 @@ +{-# language + DataKinds + , FlexibleContexts + , ImpredicativeTypes + , ImportQualifiedPost + , LambdaCase + , NumericUnderscores + , OverloadedStrings + , PackageImports + , ScopedTypeVariables + , TypeApplications + , TemplateHaskell + , RecordWildCards + , TupleSections +#-} + +{-# options_ghc -fno-warn-gadt-mono-local-binds #-} + +{-# options_ghc -Wwarn #-} +{-# options_ghc -w #-} + +module Chainweb.Test.Pact5.SPVTest + ( tests + ) where + +import Data.ByteString.Base16 qualified as Base16 +import Chainweb.Block (Block (_blockPayloadWithOutputs)) +import System.Environment (lookupEnv, setEnv) +import Control.Applicative ((<|>)) +import Data.List qualified as List +import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Service.BlockValidation +import Chainweb.Pact.Service.PactInProcApi +import Chainweb.Mempool.Consensus +import Chainweb.Pact.PactService +import Chainweb.Pact.Service.PactQueue +import Chainweb.BlockCreationTime +import Chainweb.BlockHeader +import Chainweb.ChainId +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.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.SQLite.DirectV2 (close_v2) +import Chainweb.Pact.Backend.Utils +import Chainweb.Pact.PactService +import Chainweb.Pact.PactService (initialPayloadState, withPactService) +import Chainweb.Pact.PactService.Checkpointer (SomeBlockM (..), readFrom, restoreAndSave) +import Chainweb.Pact.PactService.Checkpointer.Internal (initCheckpointerResources) +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.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.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.TestVersions +import Chainweb.Test.Utils +import Chainweb.Time +import Chainweb.Utils +import Chainweb.Utils (T2 (..), fromJuste) +import Chainweb.Utils.Serialization (runGetS, runPutS) +import Chainweb.Version +import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) +import Chainweb.WebPactExecutionService +import Control.Concurrent +import Control.Concurrent.MVar +import Control.Exception (evaluate) +import Control.Exception.Safe +import Control.Lens hiding (only) +import Control.Monad +import Control.Monad.Except +import Control.Monad.IO.Class +import Control.Monad.Reader +import Control.Monad.Trans.Resource +import Data.Aeson qualified as Aeson +import Data.ByteString (ByteString) +import Data.ByteString.Lazy qualified as LBS +import Data.Decimal +import Data.Foldable +import Data.Functor.Const +import Data.Functor.Identity +import Data.Functor.Product +import Data.Graph (Tree) +import Data.HashMap.Strict qualified as HashMap +import Data.HashSet qualified as HashSet +import Data.HashSet (HashSet) +import Data.IORef +import Data.Map qualified as Map +import Data.Maybe (fromMaybe) +import Data.MerkleLog (MerkleNodeType (..), merkleLeaf, merkleRoot, merkleTree) +import Data.Set qualified as Set +import Data.String (fromString) +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 Data.Text.IO qualified as Text +import Data.Tree qualified as Tree +import Data.Vector (Vector) +import Data.Vector qualified as Vector +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 Numeric.AffineSpace +import Pact.Core.Builtin +import Pact.Core.Capabilities +import Pact.Core.ChainData hiding (_chainId) +import Pact.Core.Command.RPC +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.Gen +import Pact.Core.Hash qualified as Pact5 +import Pact.Core.Info +import Pact.Core.Literal +import Pact.Core.Names +import Pact.Core.Names (ModuleName (ModuleName)) +import Pact.Core.PactDbRegression +import Pact.Core.PactDbRegression qualified as Pact.Core +import Pact.Core.PactValue +import Pact.Core.Persistence +import Pact.Core.Persistence (PactDb (_pdbRead)) +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 PropertyMatchers ((?)) +import PropertyMatchers qualified as P +import Streaming.Prelude qualified as Stream +import System.LogLevel +import System.LogLevel (LogLevel (..)) +import Test.Tasty +import Test.Tasty.HUnit (assertBool, assertEqual, assertFailure, testCase) +import Test.Tasty.Hedgehog +import Text.Show.Pretty (pPrint) +import Text.Printf (printf) +import Control.Concurrent.Async (forConcurrently) +import Data.Bool +import System.IO.Unsafe + +{- +roundtrip + :: Word32 + -- ^ source chain id + -> Word32 + -- ^ target chain id + -> BurnGenerator + -- ^ burn tx generator + -> CreatesGenerator + -- ^ create tx generator + -> (String -> IO ()) + -> IO (CutOutputs, CutOutputs) +roundtrip = roundtrip' testVer + +roundtrip' + :: 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' 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 + + 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) +-} + +{- +standard :: (String -> IO ()) -> Assertion +standard step = do + (c1,c3) <- roundtrip 0 1 burnGen createSuccess step + checkResult c1 0 "ObjectMap" + checkResult c3 1 "Write succeeded" +-} + +tests :: RocksDb -> TestTree +tests baseRdb = testGroup "Pact5 SPVTest" + [ --testCase "simple end to end" (simpleEndToEnd baseRdb) + ] + +successfulTx :: P.Prop (CommandResult log err) +successfulTx = P.fun _crResult ? P.match _PactResultOk P.succeed + +cid = unsafeChainId 0 +v = pact5InstantCpmTestVersion singletonChainGraph diff --git a/test/unit/Chainweb/Test/Pact5/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact5/TransactionExecTest.hs new file mode 100644 index 0000000000..2e074d620e --- /dev/null +++ b/test/unit/Chainweb/Test/Pact5/TransactionExecTest.hs @@ -0,0 +1,898 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# 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 (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 (Text) +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 (getTestLogLevel) +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.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.JSON.Encode qualified as J +import PropertyMatchers ((?)) +import PropertyMatchers qualified as P +import Test.Tasty +import Test.Tasty.HUnit (assertBool, assertEqual, testCase) +import Text.Printf +import Chainweb.Logger +import Chainweb.Pact.Backend.Types + +coinModuleName :: ModuleName +coinModuleName = ModuleName "coin" Nothing + +-- 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.allTrue + [ P.fun _peName n + , P.fun _peArgs args + , P.fun _peModule modName + ] + +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 "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 ordering spec" (testEventOrdering baseRdb) + , testCase "writes from failed transaction should not make it into the db" (testWritesFromFailedTxDontMakeItIn 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 + liftIO $ do + tdb <- mkTestBlockDb ver =<< testRocksDb "testBuyGasShouldTakeGasTokensFromTheTransactionSender" rdb + 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 + { _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = 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 + { _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = 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 + { _cbSigners = + [ mkEd25519Signer' sender00 [CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) []] + , mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin" Nothing)) [] + , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin2" Nothing)) [] + ] + ] + , _cbSender = "sender00" + , _cbChainId = 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) + >>= 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 + { _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = 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 + +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 + { _cbRPC = mkExec' "(+ 1 \"hello\")" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _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 noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.allTrue + [ 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.allTrue + [ P.fun _txDomain ? P.equals "coin_coin-table" + , P.fun _txKey ? P.equals "sender00" + ] + , P.allTrue + [ 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 + { _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] + ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _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 noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.allTrue + [ 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.allTrue + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.allTrue + [ 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 + { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 10) + } + 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 (view payloadObj <$> cmd))) + (TransactionEnv logger gasEnv) + gasUsed <- readIORef (_geGasRef gasEnv) + + assertEqual "runPayload gas used" (MilliGas 1_750) gasUsed + + pure payloadResult >>= P.match _Right ? P.allTrue + [ 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 + { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" + , _cbSigners = [] + , _cbSender = "sender00" + , _cbChainId = cid + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 500) + } + let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} + logger <- testLogger + applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) + >>= P.allTrue + -- 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 $ + 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 + { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 500) + } + let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} + let expectedGasConsumed = 73 + logger <- testLogger + applyCmd logger Nothing pactDb txCtx noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.allTrue + -- 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.allTrue + [ 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.allTrue + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.allTrue + [ 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 + +applyCmdVerifierSpec :: RocksDb -> IO () +applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ + pactTransaction Nothing $ \pactDb -> do + -- Define module with capability + do + cmd <- buildCwCmd v defaultCmd + { _cbRPC = mkExec' $ T.unlines + [ "(namespace 'free)" + , "(module m G" + , " (defcap G () (enforce-verifier 'allow))" + , " (defun x () (with-capability (G) 1))" + , ")" + ] + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _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 noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.allTrue + -- gas buy event + [ P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 568]) + (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 284 + , P.fun _crContinuation ? P.equals ? Nothing + ] + + let baseCmd = defaultCmd + { _cbRPC = mkExec' "(free.m.x)" + , _cbSender = "sender00" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbChainId = cid + , _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 noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.allTrue + -- 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.allTrue + [ 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 noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.allTrue + -- gas buy event + [ P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 162]) + (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 81 + , 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 + { _cbRPC = mkExec' "(+ 1 \"abc\")" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _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 noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.allTrue + -- 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.allTrue + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.allTrue + [ 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 + { _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] ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _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 noSPVSupport (Gas 1) (view payloadObj <$> cmd) + e & P.match _Right + ? P.allTrue + [ 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) + ] + , P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") + -- 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.allTrue + [ 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.allTrue + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.allTrue + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender01" + ] + , P.allTrue + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.allTrue + [ 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.allTrue + [ 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.allTrue + [ 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 + { _cbRPC = mkExec' "(at 'hash (describe-module 'coin))" + , _cbSigners = + [ mkEd25519Signer' sender00 [CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) []] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 500) + } + _crResult <$> applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) + +testEventOrdering :: RocksDb -> IO () +testEventOrdering rdb = readFromAfterGenesis v rdb $ + pactTransaction Nothing $ \pactDb -> do + cmd <- buildCwCmd v defaultCmd + { _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] ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _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 noSPVSupport (Gas 1) (view payloadObj <$> cmd) + + e & P.match _Right + ? P.allTrue + [ P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "sender01", PDecimal 420]) + (P.equals coinModuleName) + , event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "sender01", PDecimal 69]) + (P.equals coinModuleName) + , event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 766]) + (P.equals coinModuleName) + ] + ] + +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 + { _cbRPC = mkExec' txt + , _cbSigners = + [ mkEd25519Signer' sender00 [CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) []] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 200_000) + } + + 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 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 + { _cbRPC = mkExec' "(module m g (defcap g () (enforce false \"non-upgradeable\"))) (enforce false \"boom\")" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] ] + ] + , _cbSender = "sender00" + , _cbChainId = cid + , _cbGasPrice = GasPrice 0.1 + , _cbGasLimit = GasLimit (Gas 200_000) + } + + logger <- testLogger + e <- applyCmd logger Nothing pactDb txCtx noSPVSupport (Gas 1) (view payloadObj <$> moduleDeploy) + e & P.match _Right ? P.succeed + + finalHandle <- use pbBlockHandle + + -- Assert that the writes from the failed transaction didn't make it into the db + liftIO $ do + let finalPendingWrites = _pendingWrites $ _blockHandlePending finalHandle + assertBool "there are pending writes to coin" (HashMap.member "coin_coin-table" finalPendingWrites) + assertBool "there are no pending writes to SYS:Modules" (not $ HashMap.member "SYS:Modules" finalPendingWrites) + +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/RestAPI.hs b/test/unit/Chainweb/Test/RestAPI.hs index d1deea3e84..88aea9b6bb 100644 --- a/test/unit/Chainweb/Test/RestAPI.hs +++ b/test/unit/Chainweb/Test/RestAPI.hs @@ -46,11 +46,12 @@ import Text.Read (readEither) -- internal modules +import Chainweb.Block import Chainweb.BlockHash (BlockHash) import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.BlockHeaderDB.Internal (unsafeInsertBlockHeaderDb) -import Chainweb.BlockHeaderDB.RestAPI (Block(..)) +import Chainweb.BlockHeaderDB.RestAPI import Chainweb.ChainId import Chainweb.Graph import Chainweb.Mempool.Mempool (MempoolBackend, MockTx) @@ -235,7 +236,7 @@ simpleClientSession envIO cid = [] -> liftIO $ assertFailure "blocksClient did return empty result" (h:_) -> return h assertExpectation "block client returned wrong entry" - (Expected (Block gbh0 (version ^?! versionGenesis . genesisBlockPayload . onChain cid))) + (Expected (Block gbh0 (version ^?! versionGenesis . genesisBlockPayload . atChain cid))) (Actual gen1Block) void $ liftIO $ step "put 3 new blocks" diff --git a/test/unit/Chainweb/Test/Rosetta.hs b/test/unit/Chainweb/Test/Rosetta.hs index da2f950719..c5d4ecb437 100644 --- a/test/unit/Chainweb/Test/Rosetta.hs +++ b/test/unit/Chainweb/Test/Rosetta.hs @@ -44,7 +44,7 @@ import Chainweb.Rosetta.RestAPI import Chainweb.Rosetta.Utils import Chainweb.Version import Chainweb.Version.RecapDevelopment -import Chainweb.Version.Testnet +import Chainweb.Version.Testnet04 import qualified Pact.Types.KeySet as P --- diff --git a/test/unit/Chainweb/Test/Rosetta/RestAPI.hs b/test/unit/Chainweb/Test/Rosetta/RestAPI.hs index f4a4bd8b58..ee43bd1cb9 100644 --- a/test/unit/Chainweb/Test/Rosetta/RestAPI.hs +++ b/test/unit/Chainweb/Test/Rosetta/RestAPI.hs @@ -50,7 +50,7 @@ import qualified Chainweb.Pact.Transactions.OtherTransactions as Other import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 import qualified Chainweb.Pact.Transactions.MainnetKADTransactions as MNKAD import Chainweb.Rosetta.Utils -import Chainweb.Test.Pact.Utils +import Chainweb.Test.Pact4.Utils import Chainweb.Test.RestAPI.Utils import Chainweb.Test.Utils import Chainweb.Test.TestVersions @@ -172,6 +172,7 @@ accountBalanceTests tio envIo = where req = AccountBalanceReq nid (AccountId "sender00" Nothing Nothing) Nothing + checkBalance :: HasCallStack => AccountBalanceResp -> Decimal -> IO () checkBalance resp bal1 = do let b0 = head $ _accountBalanceResp_balances resp b1 = kdaToRosettaAmount bal1 @@ -268,6 +269,8 @@ blockTransactionTests tio envIo = -- | Rosetta block endpoint tests -- +-- TODO: investigate +-- Attempt to buy gas failed with: : Failure: Tx Failed: read: row not found: sender00 blockTests :: String -> RosettaTest blockTests testname tio envIo = testCaseSteps testname $ \step -> do cenv <- envIo @@ -375,7 +378,7 @@ blockCoinV2RemediationTests _ envIo = _ -> assertFailure $ "coin v2 remediation block should have at least 3 transactions:" ++ " coinbase + 2 remediations" where - bhCoinV2Rem = v ^?! versionForks . at CoinV2 . _Just . onChain cid . _ForkAtBlockHeight . to getBlockHeight + bhCoinV2Rem = v ^?! versionForks . at CoinV2 . _Just . atChain cid . _ForkAtBlockHeight . to getBlockHeight req h = BlockReq nid $ PartialBlockId (Just h) Nothing block20ChainRemediationTests :: RosettaTest @@ -450,7 +453,7 @@ blockCoinV3RemediationTests _ envIo = _ -> assertFailure $ "coin v3 remediation block should have at least 3 transactions:" ++ " coinbase + 2 remediations" where - bhCoinV3Rem = v ^?! versionForks . at Pact4Coin3 . _Just . onChain cid . _ForkAtBlockHeight . to getBlockHeight + bhCoinV3Rem = v ^?! versionForks . at Pact4Coin3 . _Just . atChain cid . _ForkAtBlockHeight . to getBlockHeight req h = BlockReq nid $ PartialBlockId (Just h) Nothing -- | Rosetta mempool endpoint tests diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index b73f5169ef..3a08a751dc 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -59,7 +59,7 @@ import Chainweb.MerkleUniverse import Chainweb.Miner.Config import Chainweb.Miner.Pact import Chainweb.NodeVersion -import Chainweb.Pact.Service.Types +import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.PowHash import Chainweb.RestAPI.NetworkID diff --git a/test/unit/Chainweb/Test/Version.hs b/test/unit/Chainweb/Test/Version.hs index cee53ded9b..e1ad4ab5c3 100644 --- a/test/unit/Chainweb/Test/Version.hs +++ b/test/unit/Chainweb/Test/Version.hs @@ -37,7 +37,7 @@ import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet -import Chainweb.Version.Testnet +import Chainweb.Version.Testnet04 tests :: TestTree tests = testGroup "ChainwebVersion properties" @@ -52,7 +52,7 @@ propForVersions :: String -> (ChainwebVersion -> Property) -> TestTree propForVersions desc prop = testGroup desc [ testProperty "arbitrary versions" $ prop , testProperty "mainnet" $ prop Mainnet01 - , testProperty "testnet" $ prop Testnet04 + , testProperty "testnet04" $ prop Testnet04 , testProperty "recapDevnet" $ prop RecapDevelopment ] diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index 19ae440e50..4eed5ccbf1 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -12,11 +12,14 @@ -- Chainweb Test Suite -- -module Main ( main ) where +module Main ( main, setTestLogLevel ) where import Control.Monad.IO.Class import Control.Monad.Trans.Resource +import System.Environment +import System.LogLevel + import Test.Tasty import Test.Tasty.JsonReporter import Test.Tasty.QuickCheck @@ -27,6 +30,7 @@ import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.Storage.Table.RocksDB import Chainweb.Version.Development +import Chainweb.Version.Pact5Development import Chainweb.Version.RecapDevelopment import Chainweb.Version.Registry @@ -52,22 +56,27 @@ import qualified Chainweb.Test.Mempool.RestAPI (tests) import qualified Chainweb.Test.Mempool.Sync (tests) import qualified Chainweb.Test.Mining (tests) import qualified Chainweb.Test.Misc (tests) -import qualified Chainweb.Test.Pact.Checkpointer (tests) -import qualified Chainweb.Test.Pact.DbCacheTest (tests) -import qualified Chainweb.Test.Pact.GrandHash (tests) -import qualified Chainweb.Test.Pact.ModuleCacheOnRestart (tests) -import qualified Chainweb.Test.Pact.NoCoinbase (tests) -import qualified Chainweb.Test.Pact.PactExec (tests) -import qualified Chainweb.Test.Pact.PactMultiChainTest (tests) -import qualified Chainweb.Test.Pact.PactReplay (tests) -import qualified Chainweb.Test.Pact.PactSingleChainTest (tests) -import qualified Chainweb.Test.Pact.RemotePactTest (tests) -import qualified Chainweb.Test.Pact.RewardsTest (tests) -import qualified Chainweb.Test.Pact.SPV (tests) -import qualified Chainweb.Test.Pact.SQLite (tests) -import qualified Chainweb.Test.Pact.TTL (tests) -import qualified Chainweb.Test.Pact.TransactionTests (tests) -import qualified Chainweb.Test.Pact.VerifierPluginTest (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.Pact5.CheckpointerTest +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.RestAPI (tests) import qualified Chainweb.Test.Rosetta (tests) import qualified Chainweb.Test.Rosetta.RestAPI (tests) @@ -83,20 +92,26 @@ import qualified Data.Test.Word.Encoding (properties) import qualified P2P.Test.Node (properties) import qualified P2P.Test.TaskQueue (properties) +setTestLogLevel :: LogLevel -> IO () +setTestLogLevel l = setEnv "CHAINWEB_TEST_LOG_LEVEL" (show l) + main :: IO () main = do registerVersion RecapDevelopment registerVersion Development + registerVersion Pact5Development withTempRocksDb "chainweb-tests" $ \rdb -> runResourceT $ do (h0, db) <- withToyDB rdb toyChainId - liftIO $ defaultMainWithIngredients (consoleAndJsonReporter : defaultIngredients) + liftIO + $ defaultMainWithIngredients (consoleAndJsonReporter : defaultIngredients) $ adjustOption adj $ testGroup "Chainweb Tests" $ pactTestSuite rdb : mempoolTestSuite db h0 : nodeTestSuite rdb - : suite rdb + : suite rdb -- Coinbase Vuln Fix Tests are broken, waiting for Jose loadScript + where adj NoTimeout = Timeout (1_000_000 * 60 * 10) "10m" adj x = x @@ -107,24 +122,28 @@ mempoolTestSuite db genesisBlock = testGroup "Mempool Consensus Tests" pactTestSuite :: RocksDb -> TestTree pactTestSuite rdb = testGroup "Chainweb-Pact Tests" - [ Chainweb.Test.Pact.PactExec.tests - , Chainweb.Test.Pact.DbCacheTest.tests - , Chainweb.Test.Pact.Checkpointer.tests - , Chainweb.Test.Pact.PactMultiChainTest.tests - , Chainweb.Test.Pact.VerifierPluginTest.tests - , Chainweb.Test.Pact.PactSingleChainTest.tests rdb - , Chainweb.Test.Pact.PactReplay.tests rdb - , Chainweb.Test.Pact.ModuleCacheOnRestart.tests rdb - , Chainweb.Test.Pact.TTL.tests rdb - , Chainweb.Test.Pact.RewardsTest.tests - , Chainweb.Test.Pact.NoCoinbase.tests - , Chainweb.Test.Pact.GrandHash.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 -- BROKEN few tests + + , Chainweb.Test.Pact4.PactSingleChainTest.tests rdb + + , Chainweb.Test.Pact4.VerifierPluginTest.tests -- 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.Rosetta.RestAPI.tests rdb - , Chainweb.Test.Pact.RemotePactTest.tests rdb + , Chainweb.Test.Pact4.RemotePactTest.tests rdb -- BROKEN ] suite :: RocksDb -> [TestTree] @@ -137,15 +156,20 @@ suite rdb = , Chainweb.Test.BlockHeaderDB.PruneForks.tests , testProperties "Chainweb.Test.TreeDB" Chainweb.Test.TreeDB.properties ] - , Chainweb.Test.Pact.SQLite.tests + , Chainweb.Test.Pact4.SQLite.tests , Chainweb.Test.CutDB.tests rdb - , Chainweb.Test.Pact.TransactionTests.tests + , 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.Roundtrips.tests , Chainweb.Test.Rosetta.tests , Chainweb.Test.RestAPI.tests rdb , testGroup "SPV" [ Chainweb.Test.SPV.tests rdb - , Chainweb.Test.Pact.SPV.tests + , Chainweb.Test.Pact4.SPV.tests , Chainweb.Test.SPV.EventProof.properties ] , Chainweb.Test.Mempool.InMem.tests