diff --git a/cabal.project b/cabal.project index c4ff25d98e3..3c57b26e920 100644 --- a/cabal.project +++ b/cabal.project @@ -65,7 +65,6 @@ packages: lib/cardano-api-extra/ lib/crypto-primitives/ lib/coin-selection/ - lib/deposit-wallet/ lib/delta-chain/ lib/delta-store/ lib/delta-table/ @@ -293,9 +292,6 @@ package cardano-wallet-integration package cardano-wallet-test-utils tests: True -package cardano-deposit-wallet - tests: True - package std-gen-seed tests: True diff --git a/lib/deposit-wallet/LICENSE b/lib/deposit-wallet/LICENSE deleted file mode 100644 index f433b1a53f5..00000000000 --- a/lib/deposit-wallet/LICENSE +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/lib/deposit-wallet/cardano-deposit-wallet.cabal b/lib/deposit-wallet/cardano-deposit-wallet.cabal deleted file mode 100644 index 8d4bea54d21..00000000000 --- a/lib/deposit-wallet/cardano-deposit-wallet.cabal +++ /dev/null @@ -1,269 +0,0 @@ -cabal-version: 3.6 -build-type: Simple -name: cardano-deposit-wallet -version: 0.2025.1.9 -synopsis: A wallet for the Cardano blockchain. -description: Please see README.md -homepage: https://github.com/cardano-foundation/cardano-wallet -license: Apache-2.0 -license-file: LICENSE -author: Cardano Foundation (High Assurance Lab) -maintainer: hal@cardanofoundation.org -copyright: 2023 Cardano Foundation -category: Web -data-files: data/swagger.json -extra-source-files: - spec/**/*.lagda.md - -common language - default-language: Haskell2010 - default-extensions: - NoImplicitPrelude - OverloadedStrings - -common opts-lib - ghc-options: - -Wall -Wcompat -Wredundant-constraints -Wincomplete-uni-patterns - -Wincomplete-record-updates -Wunused-imports -Wunused-packages - - if flag(release) - ghc-options: -O2 -Werror - -common opts-exe - import: opts-lib - ghc-options: -threaded -rtsopts - -common no-delta-table-on-windows - if !os(windows) - build-depends: delta-table - other-modules: Cardano.Wallet.Deposit.IO.DB.Real - - else - other-modules: Cardano.Wallet.Deposit.IO.DB.Stub - -flag release - description: Enable optimization and `-Werror` - default: False - manual: True - -library - import: language, opts-lib, no-delta-table-on-windows - hs-source-dirs: src - build-depends: - , async - , base - , base16-bytestring - , base58-bytestring - , bech32 - , bech32-th - , bytestring - , cardano-addresses - , cardano-balance-tx - , cardano-crypto - , cardano-crypto-class - , cardano-ledger-api - , cardano-ledger-core - , cardano-strict-containers - , cardano-wallet - , cardano-wallet-network-layer - , cardano-wallet-primitive - , cardano-wallet-read - , containers - , contra-tracer - , customer-deposit-wallet-pure - , delta-store - , delta-types - , digest - , fingertree - , int-cast - , io-classes - , lens - , MonadRandom - , monoidal-containers - , mtl - , OddWord - , operational - , text - , time - , transformers - - exposed-modules: - Cardano.Wallet.Deposit.IO - Cardano.Wallet.Deposit.IO.DB - Cardano.Wallet.Deposit.IO.Network.Mock - Cardano.Wallet.Deposit.IO.Network.NodeToClient - Cardano.Wallet.Deposit.IO.Network.Type - Cardano.Wallet.Deposit.IO.Resource - Cardano.Wallet.Deposit.IO.Resource.Event - Cardano.Wallet.Deposit.Map - Cardano.Wallet.Deposit.Map.Timed - Cardano.Wallet.Deposit.Pure - Cardano.Wallet.Deposit.Pure.API.Address - Cardano.Wallet.Deposit.Pure.API.TxHistory - Cardano.Wallet.Deposit.Pure.Balance - Cardano.Wallet.Deposit.Pure.State.Creation - Cardano.Wallet.Deposit.Pure.State.Payment - Cardano.Wallet.Deposit.Pure.State.Payment.Inspect - Cardano.Wallet.Deposit.Pure.State.Rolling - Cardano.Wallet.Deposit.Pure.State.Signing - Cardano.Wallet.Deposit.Pure.State.Submissions - Cardano.Wallet.Deposit.Pure.State.TxHistory - Cardano.Wallet.Deposit.Pure.State.Type - Cardano.Wallet.Deposit.Pure.Submissions - Cardano.Wallet.Deposit.Pure.UTxO - Cardano.Wallet.Deposit.Read - Cardano.Wallet.Deposit.Testing.DSL - Cardano.Wallet.Deposit.Testing.DSL.ByTime - Cardano.Wallet.Deposit.Testing.DSL.Types - Cardano.Wallet.Deposit.Time - Cardano.Wallet.Deposit.Write - Cardano.Wallet.Deposit.Write.Keys - -test-suite scenario - import: language, opts-exe - type: exitcode-stdio-1.0 - hs-source-dirs: test/scenario - main-is: test-suite-scenario.hs - build-tool-depends: markdown-unlit:markdown-unlit - ghc-options: -pgmL markdown-unlit - build-depends: - , base - , bytestring - , cardano-crypto - , cardano-wallet-test-utils - , containers - , contra-tracer - , cardano-deposit-wallet - , delta-store - , hspec - - other-modules: - Test.Scenario.Blockchain - Test.Scenario.Wallet.Deposit.Exchanges - Test.Scenario.Wallet.Deposit.Run - -library http - import: language, opts-lib - visibility: public - hs-source-dirs: http - build-depends: - , aeson - , aeson-pretty - , base - , bytestring - , cardano-wallet-read - , contra-tracer - , cardano-deposit-wallet - , cardano-deposit-wallet:rest - , http-media - , insert-ordered-containers - , lens - , memory - , openapi3 - , servant - , servant-server - , text - , text-class - - exposed-modules: - Cardano.Wallet.Deposit.HTTP.Server - Cardano.Wallet.Deposit.HTTP.Types.API - Cardano.Wallet.Deposit.HTTP.Types.JSON - Cardano.Wallet.Deposit.HTTP.Types.JSON.Encoding - Cardano.Wallet.Deposit.HTTP.Types.OpenAPI - -library rest - import: language, opts-lib - visibility: public - hs-source-dirs: rest - build-depends: - , base - , bytestring - , cardano-addresses - , cardano-crypto - , cardano-ledger-byron - , contra-tracer - , crypto-primitives - , cardano-deposit-wallet - , customer-deposit-wallet-pure - , deepseq - , delta-store - , directory - , filepath - , memory - , serialise - , servant-server - , text - , transformers - - exposed-modules: - Cardano.Wallet.Deposit.REST - Cardano.Wallet.Deposit.REST.Catch - Cardano.Wallet.Deposit.REST.Start - Cardano.Wallet.Deposit.REST.Wallet.Create - -test-suite unit - import: language, opts-exe - type: exitcode-stdio-1.0 - hs-source-dirs: test/unit - main-is: test-suite-unit.hs - ghc-options: -Wno-unused-packages - build-depends: - , aeson - , aeson-pretty - , base - , base58-bytestring - , base16-bytestring - , bech32 - , bech32-th - , bytestring - , cardano-addresses - , address-derivation-discovery - , cardano-crypto - , cardano-crypto-class - , cardano-ledger-api - , cardano-ledger-core - , cardano-ledger-core:testlib - , cardano-ledger-shelley - , cardano-slotting - , cardano-wallet-read == 1.0.0.0 - , cardano-wallet-test-utils - , containers - , contra-tracer - , cardano-deposit-wallet - , customer-deposit-wallet-pure == 0.1.0.0 - , cardano-deposit-wallet:http - , cardano-deposit-wallet:rest - , data-default - , directory - , hspec - , hspec-golden - , lens - , openapi3 - , pretty-simple - , QuickCheck - , serialise - , temporary - , text - , time - , transformers - , with-utf8 - - build-tool-depends: hspec-discover:hspec-discover - other-modules: - Cardano.Wallet.Deposit.HTTP.JSON.JSONSpec - Cardano.Wallet.Deposit.HTTP.OpenAPISpec - Cardano.Wallet.Deposit.Map.TimedSpec - Cardano.Wallet.Deposit.Pure.API.AddressSpec - Cardano.Wallet.Deposit.Pure.API.TransactionSpec - Cardano.Wallet.Deposit.PureSpec - Cardano.Wallet.Deposit.RESTSpec - Cardano.Wallet.Deposit.Write.KeysSpec - Paths_cardano_deposit_wallet - Spec - -executable cardano-deposit-wallet - import: language, opts-exe - hs-source-dirs: exe - build-depends: base - main-is: cardano-deposit-wallet.hs diff --git a/lib/deposit-wallet/data/swagger.json b/lib/deposit-wallet/data/swagger.json deleted file mode 100644 index 11fdc9e2081..00000000000 --- a/lib/deposit-wallet/data/swagger.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "components": { - "schemas": { - "ApiT Address": { - "format": "hex", - "type": "string" - }, - "ApiT ChainPoint": { - "oneOf": [ - { - "enum": [ - "genesis" - ], - "type": "string" - }, - { - "properties": { - "header_hash": { - "description": "Hash (Blake2b_256) of a block header.", - "format": "hex", - "maxLength": 64, - "minLength": 64, - "type": "string" - }, - "slot_no": { - "maximum": 1.8446744073709551615e19, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - } - ] - }, - "ApiT Customer": { - "maximum": 2147483647, - "minimum": 0, - "type": "integer" - }, - "ApiT CustomerList": { - "items": { - "properties": { - "address": { - "format": "hex", - "type": "string" - }, - "customer": { - "maximum": 2147483647, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "type": "array" - } - } - }, - "info": { - "description": "This is the API for the deposit wallet", - "license": { - "name": "Apache 2", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html" - }, - "title": "Cardano Deposit Wallet API", - "version": "0.0.0.1" - }, - "openapi": "3.0.0", - "paths": { - "/customers": { - "parameters": [ - { - "in": "path", - "name": "customerId", - "schema": { - "$ref": "#/components/schemas/ApiT Customer" - } - } - ], - "put": { - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiT Address" - } - } - }, - "description": "Ok" - } - }, - "summary": "Add customer" - } - }, - "/network/local-tip": { - "get": { - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiT ChainPoint" - } - } - }, - "description": "Ok" - } - }, - "summary": "Obtain the chain point until which the wallet is synchronized against the network" - } - } - } -} \ No newline at end of file diff --git a/lib/deposit-wallet/exe/cardano-deposit-wallet.hs b/lib/deposit-wallet/exe/cardano-deposit-wallet.hs deleted file mode 100644 index 0e8df19982e..00000000000 --- a/lib/deposit-wallet/exe/cardano-deposit-wallet.hs +++ /dev/null @@ -1,4 +0,0 @@ -import Prelude - -main :: IO () -main = pure () diff --git a/lib/deposit-wallet/external-signing.md b/lib/deposit-wallet/external-signing.md deleted file mode 100644 index 258f82acd9f..00000000000 --- a/lib/deposit-wallet/external-signing.md +++ /dev/null @@ -1,109 +0,0 @@ -# Signing via external tools. - -Here are instructions how to derive respective keys via cardano-addresses, then sign -transactions CBOR and send the updated CBOR using cardano-cli - -Assumptions: -User have either mnemonic, *phrase.prv*, or extended account private key, *acct.xsk*. -User know which credential payment index is going to be used, *ix*. -Unsigned or partially signed CBOR of tx is available. - -Goal is to construct CBOR that is input CBOR with properly added witness from the credential signing key -being a dual of the input credential payment verificiation key. - -1. In case of missing *acct.xsk* it can be derived as follows: -```bash -$ cat phrase.prv -attract flight range human visual record trade mango chunk enough satoshi galaxy grit van shrug - -$ cardano-address key from-recovery-phrase Shelley < phrase.prv > root.xsk -$ root.xsk -$ cat root.xsk -root_xsk1dqh2lewgwnfzf0kreek8c2zx9csq2d8nh9ku5tvkkxjzypuy5402qnxrl3htj84qxchuxueg3nt7uv50v2mj9vynpdckslyvc24qqxeysye4h2c0cgdemujn8mcprgcstgjvkep30ygu4p3ch983chukqvusp4yk - -$ cardano-address key child 1857H/1815H/0H < root.xsk > acct.xsk -$ cat acct.xsk -acct_xsk10zeunvvghchkcg8w6achyn3usv642alx8f57rv9kzdzt7yuy540xs4r08lwq576a3v822z9jv8v7kjkjpqxdqtjzt4ukh6w8f57vg0fv6dzaq33pps7hwe5f70cztch0z7kj0552felguwn6n2u74h248g5na6u5 -``` - -2. Deriving extended verification keys and signing keys for role=0 and address ix=0, and hash that is credential -```bash -$ cardano-address key child 0/0 < acct.xsk > key.xsk -$ cat key.xsk -addr_xsk14pwgnh4q757kgfjn2w83prmh27kj58ety3acvt0jvx2lwxvy540gmd5gug2egr9dlzv4z04nm9jd26al494w9t6qhlzf07re2myu9mv7syp6aym49c0d97lfg8y0c36vgjv54qnwte6rz3f6x0ltnjqfwcnufn8e - -$ cardano-address key public --with-chain-code < key.xsk > key.xvk -$ cat key.xvk -addr_xvk1dkqjgyp2tdq0a0tre7qlhprdr88r497k072q0726lhux4xyfxtcfaqgr46fh2ts76ta7jswgl3r5c3yef2pxuhn5x9zn5vl7h8yqjas27h22j - -$ cardano-address key hash < key.xvk -addr_vkh1k70phz25qm9g6uxxguw8znnepqc5uu2mqx9yd7ea8yc7urscytf -$ cardano-address key hash --hex < key.xvk -b79e1b895406ca8d70c6471c714e7908314e715b018a46fb3d3931ee -``` - -3. Constructing enterprise address for preprod using *key.xvk* -```bash -$ cardano-address address payment --network-tag testnet < key.xvk -addr_test1vzmeuxuf2srv4rtscer3cu2w0yyrznn3tvqc53hm85unrmsg4m9cg -``` - -4. Mapping *key.xsk* to the key suitable for cardano-cli -```bash -$ cardano-cli key convert-cardano-address-key --shelley-payment-key --signing-key-file key.xsk --out-file key.skey -$ cat key.skey -{ - "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", - "description": "", - "cborHex": "5880a85c89dea0f53d642653538f108f7757ad2a1f2b247b862df26195f71984a55e8db688e215940cadf899513eb3d964d56bbfa96ae2af40bfc497f87956c9c2ed6d8124102a5b40febd63cf81fb846d19ce3a97d67f9407f95afdf86a988932f09e8103ae93752e1ed2fbe941c8fc474c44994a826e5e7431453a33feb9c80976" -} -``` -Remark: *cborHex* contains: -- prefix 5880 staking that the bytestring is 128 bytes -- signing key (64 bytes) -- verification key (32 bytes) -- chain code (32 bytes) -One can confirm this using `cardano-address key inspect`, `cardano-address key public` and `cardano-address key private` options - -5. The corresponding verification key -```bash -$ cardano-cli key verification-key --signing-key-file key.skey --verification-key-file key.vkey -$ cat key.vkey -{ - "type": "PaymentExtendedVerificationKeyShelley_ed25519_bip32", - "description": "", - "cborHex": "58406d8124102a5b40febd63cf81fb846d19ce3a97d67f9407f95afdf86a988932f09e8103ae93752e1ed2fbe941c8fc474c44994a826e5e7431453a33feb9c80976" -} -``` -Remark: *cborHex* contains: -- prefix 5840 staking that the bytestring is 64 bytes -- verification key (32 bytes) -- chain code (32 bytes) - -6. The corresponding key hash (the same like in point 2 above) -```bash -$ cardano-cli address key-hash --payment-verification-key-file key.vkey -b79e1b895406ca8d70c6471c714e7908314e715b018a46fb3d3931ee -``` - -7. Signing using cardano-cli. Here let's assume we have unsigned tx and we will use the above keys. -```bash -$ cat tx.unsigned -{ - "type":"Unwitnessed Tx ConwayEra", - "description":"Ledger Cddl Format", - "cborHex":"84a400d90102818258204fe1968fc521dffe2bb9799b9c6548e38cd5e1a593c7d43a251eeb92deadc3fe00018282581d60d23d12a37c21b84c8c7838d4bbda848fe7a6b7bfc3f54212238912ec1a000f424082581d601cbb2cdd51437bb9f43bdd1214984e8b2794e0cff25f47ba187494041b0000000253fa1907021a000288b9031a0498a97aa0f5f6"} - -$ cardano-cli conway transaction sign --signing-key-file key.skey --testnet-magic 1 --tx-body-file tx.unsigned --out-file tx.signed -$ cat tx.signed -{ - "type": "Witnessed Tx ConwayEra", - "description": "Ledger Cddl Format", - "cborHex": "84a400d90102818258204fe1968fc521dffe2bb9799b9c6548e38cd5e1a593c7d43a251eeb92deadc3fe00018282581d60d23d12a37c21b84c8c7838d4bbda848fe7a6b7bfc3f54212238912ec1a000f424082581d601cbb2cdd51437bb9f43bdd1214984e8b2794e0cff25f47ba187494041b0000000253fa1907021a000288b9031a0498a97aa100d90102818258206d8124102a5b40febd63cf81fb846d19ce3a97d67f9407f95afdf86a988932f058401a4757dc289f97684339ec766d1fcddfe1ebd50a53d7cccbb71b265e784dd6eb4bf87d5b6c2383e66f1a679f2ac0d97add6a890779096f0802690518223a8c04f5f6" -} -``` - -8. Submitting the signed tx -```bash -$ cardano-cli conway transaction submit --tx-file tx.signed --testnet-magic 1 -``` diff --git a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Server.hs b/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Server.hs deleted file mode 100644 index e16def75ebe..00000000000 --- a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Server.hs +++ /dev/null @@ -1,168 +0,0 @@ --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Implementation of our HTTP API. -module Cardano.Wallet.Deposit.HTTP.Server - ( api - , server - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.HTTP.Types.API - ( API - ) -import Cardano.Wallet.Deposit.HTTP.Types.JSON - ( Address - , ApiT (..) - , Customer - ) -import Cardano.Wallet.Deposit.IO - ( WalletBootEnv - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( createMnemonicFromWords - , credentialsFromEncodedXPub - , credentialsFromMnemonics - ) -import Cardano.Wallet.Deposit.REST - ( WalletResource - , WalletResourceM - , customerAddress - , listCustomers - ) -import Cardano.Wallet.Deposit.REST.Catch - ( catchRunWalletResourceM - ) -import Cardano.Wallet.Deposit.REST.Wallet.Create - ( PostWalletViaMnemonic (..) - , PostWalletViaXPub (..) - ) -import Control.Tracer - ( Tracer - ) -import Data.Functor - ( ($>) - ) -import Data.Proxy - ( Proxy (..) - ) -import Servant - ( Handler - , NoContent (..) - , err500 - , (:<|>) (..) - ) -import Servant.Server - ( Server - ) - -import qualified Cardano.Wallet.Deposit.REST as REST - -{----------------------------------------------------------------------------- - Types -------------------------------------------------------------------------------} -api :: Proxy API -api = Proxy - -server - :: Tracer IO () - -- ^ Tracer for wallet tip changes - -> Tracer IO String - -> FilePath - -> WalletBootEnv IO - -> WalletResource - -> Server API -server wtc tr dbDir wb r = - listCustomerH r - :<|> queryAddressH r - :<|> createWalletViaMnemonic wtc tr dbDir wb r - :<|> createWalletViaXPub wtc tr dbDir wb r - -createWalletViaMnemonic - :: Tracer IO () - -- ^ Tracer for wallet tip changes - -> Tracer IO String - -> FilePath - -> WalletBootEnv IO - -> WalletResource - -> PostWalletViaMnemonic - -> Handler NoContent -createWalletViaMnemonic - wtc - tracer - dir - boot - resource - (PostWalletViaMnemonic mnemonics' passphrase' users') = do - case createMnemonicFromWords mnemonics' of - Left e -> fail $ show e - Right someMnemonic -> do - let - initWallet :: WalletResourceM () - initWallet = - REST.initWallet - wtc - tracer - boot - dir - (credentialsFromMnemonics someMnemonic passphrase') - (fromIntegral users') - onlyOnWalletIntance resource initWallet $> NoContent - -createWalletViaXPub - :: Tracer IO () - -- ^ Tracer for wallet tip changes - -> Tracer IO String - -> FilePath - -> WalletBootEnv IO - -> WalletResource - -> PostWalletViaXPub - -> Handler NoContent -createWalletViaXPub - wtc - tracer - dir - boot - resource - (PostWalletViaXPub xpubText users') = do - result <- onlyOnWalletIntance resource initWallet - case result of - Left e -> fail e - Right () -> pure NoContent - where - initWallet :: WalletResourceM (Either String ()) - initWallet = case credentialsFromEncodedXPub xpubText of - Left e -> pure $ Left $ show e - Right credentials -> - Right - <$> REST.initWallet - wtc - tracer - boot - dir - credentials - (fromIntegral users') - -listCustomerH - :: WalletResource - -> Handler (ApiT [(Customer, Address)]) -listCustomerH wr = ApiT <$> onlyOnWalletIntance wr listCustomers - -queryAddressH - :: WalletResource - -> ApiT Customer - -> Handler (ApiT Address) -queryAddressH wr (ApiT customer) = do - mAddr <- onlyOnWalletIntance wr $ customerAddress customer - case mAddr of - Nothing -> fail $ "Address not found for customer " <> show customer - Just a -> pure $ ApiT a - -onlyOnWalletIntance - :: WalletResource - -> WalletResourceM a - -> Handler a -onlyOnWalletIntance wr = catchRunWalletResourceM wr err500 diff --git a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/API.hs b/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/API.hs deleted file mode 100644 index 0732c56fde9..00000000000 --- a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/API.hs +++ /dev/null @@ -1,59 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TypeOperators #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Servant Type for our HTTP API. -module Cardano.Wallet.Deposit.HTTP.Types.API - ( API - , NetworkAPI - ) -where - -import Cardano.Wallet.Deposit.HTTP.Types.JSON - ( Address - , ApiT - , ChainPoint - , Customer - , CustomerList - ) -import Cardano.Wallet.Deposit.REST.Wallet.Create - ( PostWalletViaMnemonic - , PostWalletViaXPub - ) -import Servant.API - ( Capture - , Get - , JSON - , Put - , PutNoContent - , ReqBody - , StdMethod (..) - , Verb - , (:<|>) - , (:>) - ) - -{----------------------------------------------------------------------------- - API -------------------------------------------------------------------------------} - -type API = - "customers" - :> Get '[JSON] (ApiT CustomerList) - :<|> "customers" - :> Capture "customerId" (ApiT Customer) - :> Put '[JSON] (ApiT Address) - :<|> "mnemonics" - :> ReqBody '[JSON] PostWalletViaMnemonic - :> PutNoContent - :<|> "xpub" - :> ReqBody '[JSON] PostWalletViaXPub - :> PutNoContent - -type NetworkAPI = - "network" - :> "local-tip" - :> Verb 'GET 200 '[JSON] (ApiT ChainPoint) diff --git a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/JSON.hs b/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/JSON.hs deleted file mode 100644 index 7fe975c6234..00000000000 --- a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/JSON.hs +++ /dev/null @@ -1,228 +0,0 @@ -{-# LANGUAGE DerivingVia #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# OPTIONS_GHC -Wno-orphans #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Data types with a JSON schema. -module Cardano.Wallet.Deposit.HTTP.Types.JSON - ( ApiT (..) - - -- * Re-exports - , Address - , Customer - , CustomerList - , ChainPoint (..) - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.HTTP.Types.JSON.Encoding - ( ViaText (..) - ) -import Cardano.Wallet.Deposit.HTTP.Types.OpenAPI - ( addressSchema - , chainPointSchema - , customerListSchema - , customerSchema - ) -import Cardano.Wallet.Deposit.Pure - ( Customer - ) -import Cardano.Wallet.Deposit.Read - ( Address - , ChainPoint (..) - ) -import Cardano.Wallet.Deposit.REST.Wallet.Create - ( PostWalletViaMnemonic - , PostWalletViaXPub - ) -import Control.Applicative - ( (<|>) - ) -import Data.Aeson - ( FromJSON (..) - , ToJSON (..) - , object - , withObject - , withText - , (.:) - , (.=) - ) -import Data.Aeson.Types - ( Parser - ) -import Data.Bifunctor - ( first - ) -import Data.ByteArray.Encoding - ( Base (Base16) - , convertFromBase - , convertToBase - ) -import Data.ByteString.Short - ( fromShort - , toShort - ) -import Data.OpenApi - ( NamedSchema (..) - , ToSchema (..) - ) -import Data.Text - ( Text - ) -import Data.Text.Class - ( FromText (..) - , TextDecodingError (..) - , ToText (..) - , getTextDecodingError - ) -import Servant - ( FromHttpApiData (..) - ) - -import qualified Cardano.Wallet.Read as Read -import qualified Cardano.Wallet.Read.Hash as Hash -import qualified Data.Text as T -import qualified Data.Text.Encoding as T - -{----------------------------------------------------------------------------- - Additional type definitions -------------------------------------------------------------------------------} - -type CustomerList = [(Customer, Address)] - -{----------------------------------------------------------------------------- - ApiT -------------------------------------------------------------------------------} - -newtype ApiT a = ApiT {unApiT :: a} - deriving (Eq, Ord, Show) - -{----------------------------------------------------------------------------- - JSON encodings -------------------------------------------------------------------------------} - --- Address -instance ToText (ApiT Address) where - toText = - T.decodeUtf8 - . convertToBase Base16 - . fromShort - . Read.toShortByteString - . unApiT - -instance FromText (ApiT Address) where - fromText t = do - bytes <- - first textDecodingError - . convertFromBase Base16 - $ T.encodeUtf8 t - maybe (Left errInvalidAddress) (Right . ApiT) - . Read.fromShortByteString - $ toShort bytes - where - errInvalidAddress = TextDecodingError $ "Invalid address: " <> show t - textDecodingError = TextDecodingError . show - --- FIXME: Bech32 encodings -deriving via ViaText (ApiT Address) instance FromJSON (ApiT Address) -deriving via ViaText (ApiT Address) instance ToJSON (ApiT Address) - -instance ToSchema (ApiT Address) where - declareNamedSchema _ = do - pure - $ NamedSchema - (Just "ApiT Address") - addressSchema - --- Customer -instance FromHttpApiData (ApiT Customer) where - parseUrlPiece = fmap (ApiT . toEnum) . fromText' - -instance FromJSON (ApiT Customer) where - parseJSON = fmap (ApiT . toEnum) . parseJSON - -instance ToJSON (ApiT Customer) where - toJSON = toJSON . fromEnum . unApiT - -instance ToSchema (ApiT Customer) where - declareNamedSchema _ = do - pure - $ NamedSchema - (Just "ApiT Customer") - customerSchema - --- | 'fromText' but with a simpler error type. -fromText' :: FromText a => Text -> Either Text a -fromText' = first (T.pack . getTextDecodingError) . fromText - -instance ToJSON (ApiT (Customer, Address)) where - toJSON (ApiT (c, a)) = - object - [ "customer" .= toJSON (ApiT c) - , "address" .= toJSON (ApiT a) - ] - -instance FromJSON (ApiT (Customer, Address)) where - parseJSON = withObject "ApiT (Customer, Address)" $ \obj -> do - customerApiT <- obj .: "customer" - addressApiT <- obj .: "address" - pure $ ApiT (unApiT customerApiT, unApiT addressApiT) - -instance FromJSON (ApiT CustomerList) where - parseJSON l = do - custoList <- (parseJSON l :: Parser [ApiT (Customer, Address)]) - pure $ ApiT (unApiT <$> custoList) - -instance ToJSON (ApiT CustomerList) where - toJSON (ApiT cl) = toJSON (toJSON . ApiT <$> cl) - -instance ToSchema (ApiT CustomerList) where - declareNamedSchema _ = do - pure - $ NamedSchema - (Just "ApiT CustomerList") - customerListSchema - -instance ToJSON (ApiT ChainPoint) where - toJSON (ApiT Read.GenesisPoint) = "genesis" - toJSON (ApiT (Read.BlockPoint{slotNo, headerHash})) = - object - [ "slot_no" - .= Read.unSlotNo slotNo - , "header_hash" - .= Hash.hashToTextAsHex headerHash - ] - -instance FromJSON (ApiT ChainPoint) where - parseJSON payload = parseOrigin payload <|> parseSlot payload - where - parseOrigin = withText "genesis" $ \txt -> - if txt == "genesis" - then pure $ ApiT Read.GenesisPoint - else fail "'genesis' is expected." - parseSlot = withObject "slot_no" $ \obj -> do - slotNo <- Read.SlotNo <$> obj .: "slot_no" - headerHashText <- obj .: "header_hash" - headerHash <- - case Hash.hashFromTextAsHex headerHashText of - Nothing -> fail "invalid 'header_hash'" - Just hash -> pure hash - pure $ ApiT Read.BlockPoint{slotNo, headerHash} - -instance ToSchema (ApiT ChainPoint) where - declareNamedSchema _ = do - pure - $ NamedSchema - (Just "ApiT ChainPoint") - chainPointSchema - -instance FromJSON PostWalletViaMnemonic - -instance FromJSON PostWalletViaXPub diff --git a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/JSON/Encoding.hs b/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/JSON/Encoding.hs deleted file mode 100644 index 2100be31f2e..00000000000 --- a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/JSON/Encoding.hs +++ /dev/null @@ -1,81 +0,0 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE UndecidableInstances #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Utilities for mapping data types to/from JSON. --- -module Cardano.Wallet.Deposit.HTTP.Types.JSON.Encoding - ( Custom (..) - , customOptions - , ViaText (..) - ) where - -import Prelude - -import Data.Aeson - ( FromJSON (..) - , GFromJSON - , GToJSON' - , Options (..) - , ToJSON (..) - , Value - , Zero - , camelTo2 - , defaultOptions - , genericParseJSON - , genericToJSON - , withText - ) -import Data.Aeson.Types - ( Parser - ) -import Data.Text.Class - ( FromText (..) - , ToText (toText) - ) -import GHC.Generics - ( Generic - , Rep - ) - -{----------------------------------------------------------------------------- - Generics -------------------------------------------------------------------------------} -newtype Custom a = Custom {unCustom :: a} - -instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (Custom a) - where - parseJSON = fmap Custom . genericParseJSON customOptions - -instance (Generic a, GToJSON' Value Zero (Rep a)) => ToJSON (Custom a) - where - toJSON = genericToJSON customOptions . unCustom - -customOptions :: Options -customOptions = defaultOptions - { fieldLabelModifier = camelTo2 '_' . dropWhile (== '_') - , omitNothingFields = True - } - -{----------------------------------------------------------------------------- - Text -------------------------------------------------------------------------------} -newtype ViaText a = ViaText {unViaText :: a} - -instance FromText a => FromJSON (ViaText a) where - parseJSON = fmap ViaText . fromTextJSON "" -instance ToText a => ToJSON (ViaText a) where - toJSON = toTextJSON . unViaText - -eitherToParser :: Show s => Either s a -> Parser a -eitherToParser = either (fail . show) pure - -toTextJSON :: ToText a => a -> Value -toTextJSON = toJSON . toText - -fromTextJSON :: FromText a => String -> Value -> Parser a -fromTextJSON n = withText n (eitherToParser . fromText) diff --git a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/OpenAPI.hs b/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/OpenAPI.hs deleted file mode 100644 index 5e75480cd89..00000000000 --- a/lib/deposit-wallet/http/Cardano/Wallet/Deposit/HTTP/Types/OpenAPI.hs +++ /dev/null @@ -1,247 +0,0 @@ -{-# LANGUAGE OverloadedLists #-} -{-# LANGUAGE ScopedTypeVariables #-} - -module Cardano.Wallet.Deposit.HTTP.Types.OpenAPI - ( generateOpenapi3 - , apiSchema - , depositPaths - , depositDefinitions - - , customerSchema - , addressSchema - , customerListSchema - , chainPointSchema - ) where - -import Prelude - -import Control.Lens - ( At (..) - , (&) - , (.~) - , (?~) - ) -import Data.Aeson.Encode.Pretty - ( encodePretty - ) -import Data.HashMap.Strict.InsOrd - ( InsOrdHashMap - ) -import Data.OpenApi - ( Definitions - , HasComponents (..) - , HasContent (..) - , HasDescription (..) - , HasEnum (..) - , HasFormat (..) - , HasGet (..) - , HasIn (..) - , HasInfo (..) - , HasItems (..) - , HasLicense (license) - , HasMaxLength (..) - , HasMaximum (..) - , HasMinLength (..) - , HasMinimum (..) - , HasName (..) - , HasOneOf (..) - , HasParameters (..) - , HasPaths (..) - , HasProperties (..) - , HasPut (..) - , HasSchema (..) - , HasSchemas (..) - , HasSummary (..) - , HasTitle (..) - , HasType (..) - , HasUrl (..) - , HasVersion (..) - , License - , OpenApi - , OpenApiItems (..) - , OpenApiType (..) - , Operation - , ParamLocation (..) - , PathItem - , Reference (..) - , Referenced (..) - , Schema - , URL (..) - , _Inline - ) -import Data.Word - ( Word64 - ) -import Network.HTTP.Media - ( MediaType - ) - -import qualified Data.ByteString.Lazy.Char8 as BL - -generateOpenapi3 :: BL.ByteString -generateOpenapi3 = encodePretty apiSchema - -apiSchema :: OpenApi -apiSchema :: OpenApi = - mempty - & info . title .~ "Cardano Deposit Wallet API" - & info . version .~ "0.0.0.1" - & info . description ?~ "This is the API for the deposit wallet" - & info . license ?~ license' - & paths .~ depositPaths - & components . schemas .~ depositDefinitions - -license' :: License -license' = - "Apache 2" - & url ?~ URL "https://www.apache.org/licenses/LICENSE-2.0.html" - -depositPaths :: InsOrdHashMap FilePath PathItem -depositPaths = - [ getCustomersListPath - , putCustomerPath - , getLocalTipPath - ] - -depositDefinitions :: Definitions Schema -depositDefinitions = - [ ("ApiT Customer", customerSchema) - , ("ApiT Address", addressSchema) - , ("ApiT CustomerList", customerListSchema) - , ("ApiT ChainPoint", chainPointSchema) - ] - --- | Paths -jsonMediaType :: MediaType -jsonMediaType = "application/json" - -getCustomersListPath :: (FilePath, PathItem) -getCustomersListPath = ("/customers", pathItem) - where - pathItem :: PathItem - pathItem = mempty & get ?~ operation - operation :: Operation - operation = - mempty - & summary ?~ summary' - & at 200 ?~ at200 - summary' = "Obtain the list of customers" - at200 = - "Ok" - & _Inline . content . at jsonMediaType - ?~ (mempty & schema ?~ Ref (Reference "ApiT CustomerList")) - -putCustomerPath :: (FilePath, PathItem) -putCustomerPath = ("/customers", pathItem) - where - pathItem :: PathItem - pathItem = - mempty - & put ?~ operation - & parameters - .~ [ Inline - $ mempty - & in_ .~ ParamPath - & name .~ "customerId" - & schema ?~ Ref (Reference "ApiT Customer") - ] - operation :: Operation - operation = - mempty - & summary ?~ summary' - & at 200 ?~ at200 - summary' = "Add customer" - at200 = - "Ok" - & _Inline . content . at jsonMediaType - ?~ (mempty & schema ?~ Ref (Reference "ApiT Address")) - --- | Input/Output type schemas -customerSchema :: Schema -customerSchema = - mempty - & type_ ?~ OpenApiInteger - & minimum_ ?~ 0 - & maximum_ ?~ 2147483647 - -addressSchema :: Schema -addressSchema = - mempty - & type_ ?~ OpenApiString - & format ?~ "hex" - -customerListItemSchema :: Schema -customerListItemSchema = - mempty - & type_ ?~ OpenApiObject - & properties - .~ [ ("customer", Inline customerSchema) - , ("address", Inline addressSchema) - ] - -customerListSchema :: Schema -customerListSchema = - mempty - & type_ ?~ OpenApiArray - & items - ?~ OpenApiItemsObject - (Inline customerListItemSchema) - -getLocalTipPath :: (FilePath, PathItem) -getLocalTipPath = ("/network/local-tip", pathItem) - where - pathItem :: PathItem - pathItem = mempty & get ?~ operation - operation :: Operation - operation = - mempty - & summary ?~ summary' - & at 200 ?~ at200 - summary' = "Obtain the chain point until which the wallet is synchronized against the network" - at200 = - "Ok" - & _Inline . content . at jsonMediaType - ?~ (mempty & schema ?~ Ref (Reference "ApiT ChainPoint")) - -chainPointSchema :: Schema -chainPointSchema = - mempty - & oneOf ?~ [Inline chainPointOriginSchema, Inline chainPointAtSlotSchema] - -chainPointOriginSchema :: Schema -chainPointOriginSchema = - mempty - & type_ ?~ OpenApiString - & enum_ ?~ ["genesis"] - -chainPointAtSlotSchema :: Schema -chainPointAtSlotSchema = - mempty - & type_ ?~ OpenApiObject - & properties - .~ [ ("slot_no", Inline slotSchema) - , ("header_hash", Inline headerHashSchema) - ] - -slotSchema :: Schema -slotSchema = - mempty - & type_ ?~ OpenApiInteger - & minimum_ ?~ 0 - & maximum_ ?~ fromIntegral (maxBound :: Word64) - -headerHashSchema :: Schema -headerHashSchema = - blake2b_256Schema - & description ?~ "Hash (Blake2b_256) of a block header." - -blake2b_256Schema :: Schema -blake2b_256Schema = - mempty - & type_ ?~ OpenApiString - & format ?~ "hex" - & minLength ?~ (32 * hexCharactersPerByte) - & maxLength ?~ (32 * hexCharactersPerByte) - -hexCharactersPerByte :: Integer -hexCharactersPerByte = 2 diff --git a/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST.hs b/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST.hs deleted file mode 100644 index 66ba07ddd35..00000000000 --- a/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST.hs +++ /dev/null @@ -1,491 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# OPTIONS_GHC -Wno-orphans #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- 'IO'-based interface to the Deposit Wallet --- where the wallet is treated as a mutable resource (~ REST). --- This interface can be mapped one-to-one to a HTTP interface. -module Cardano.Wallet.Deposit.REST - ( -- * Types - WalletResource - , WalletResourceM - , ErrDatabase (..) - , ErrLoadingDatabase (..) - , ErrCreatingDatabase (..) - , ErrWalletResource (..) - - -- * Running - , runWalletResourceM - - -- * Operations - - -- ** Initialization - , initWallet - , loadWallet - - -- ** Mapping between customers and addresses - , listCustomers - , customerAddress - , addressToCustomer - - -- ** Reading from the blockchain - , getWalletTip - , availableBalance - , getTxHistoryByCustomer - , getTxHistoryByTime - , WalletIO.ResolveAddress - - -- ** Writing to the blockchain - , createPayment - , getBIP32PathsForOwnedInputs - , signTx - , walletExists - , walletPublicIdentity - , deleteWallet - , deleteTheDepositWalletOnDisk - - -- * Internals - , inspectTx - , onWalletInstance - , networkTag - , resolveCurrentEraTx - , canSign - , submitTx - ) where - -import Prelude - -import Cardano.Address.Derivation - ( xpubToBytes - ) -import Cardano.Crypto.Wallet - ( XPrv - , XPub (..) - , unXPrv - , unXPub - , xprv - , xpub - ) -import Cardano.Wallet.Address.BIP32 - ( BIP32Path - ) -import Cardano.Wallet.Deposit.IO - ( WalletBootEnv - , WalletPublicIdentity - , genesisData - ) -import Cardano.Wallet.Deposit.IO.Resource - ( ErrResourceExists (..) - , ErrResourceMissing (..) - ) -import Cardano.Wallet.Deposit.Pure - ( CanSign - , Credentials - , CurrentEraResolvedTx - , Customer - , ErrCreatePayment - , InspectTx - , Passphrase - , Word31 - , fromCredentialsAndGenesis - ) -import Cardano.Wallet.Deposit.Pure.API.TxHistory - ( ByCustomer - , ByTime - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( accountXPubFromCredentials - ) -import Cardano.Wallet.Deposit.Read - ( Address - ) -import Codec.Serialise - ( Serialise (..) - , deserialise - , serialise - ) -import Control.DeepSeq - ( deepseq - ) -import Control.Monad.IO.Class - ( MonadIO (..) - ) -import Control.Monad.Trans.Class - ( lift - ) -import Control.Monad.Trans.Except - ( ExceptT (..) - , runExceptT - ) -import Control.Monad.Trans.Reader - ( ReaderT (..) - , ask - ) -import Control.Tracer - ( Tracer (..) - ) -import Cryptography.Hash.Blake - ( blake2b160 - ) -import Data.Bifunctor - ( first - ) -import Data.ByteArray.Encoding - ( Base (..) - , convertToBase - ) -import Data.ByteString - ( ByteString - ) -import Data.List - ( isPrefixOf - ) -import Data.Store - ( Store (..) - , newStore - ) -import System.Directory - ( listDirectory - , removeFile - ) -import System.FilePath - ( () - ) - -import qualified Cardano.Wallet.Deposit.IO as WalletIO -import qualified Cardano.Wallet.Deposit.IO.Network.Type as Network -import qualified Cardano.Wallet.Deposit.IO.Resource as Resource -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Data.ByteString.Char8 as B8 -import qualified Data.ByteString.Lazy as BL - -{----------------------------------------------------------------------------- - Types -------------------------------------------------------------------------------} - --- | Error indicating that the database could not be loaded. -data ErrLoadingDatabase - = ErrDatabaseNotFound FilePath - | ErrDatabaseCorrupted FilePath - | ErrMultipleDatabases [FilePath] - deriving (Show, Eq) - --- | Error indicating that the database could not be created. -newtype ErrCreatingDatabase - = ErrDatabaseAlreadyExists FilePath - deriving (Show, Eq) - --- | Error indicating that the database could not be loaded or created. -data ErrDatabase - = ErrLoadingDatabase ErrLoadingDatabase - | ErrCreatingDatabase ErrCreatingDatabase - deriving (Show, Eq) - --- | Mutable resource that may hold a 'WalletInstance'. -type WalletResource = - Resource.Resource ErrDatabase WalletIO.WalletInstance - --- | Error indicating that the 'WalletResource' does not hold a wallet. -data ErrWalletResource - = ErrNoWallet (Resource.ErrResourceMissing ErrDatabase) - | ErrWalletPresent - (Resource.ErrResourceExists ErrDatabase WalletIO.WalletInstance) - -instance Show ErrWalletResource where - show = \case - ErrNoWallet e -> case e of - ErrNotInitialized -> "Wallet is not initialized" - ErrStillInitializing -> "Wallet is still initializing" - ErrVanished e' -> "Wallet absent and vanished: " <> show e' - ErrFailedToInitialize e' -> - "Wallet failed to initialize (no wallet): " - <> show e' - ErrClosing -> "Wallet is closing" - ErrWalletPresent e -> case e of - ErrAlreadyInitializing -> "Wallet is already initializing" - ErrAlreadyInitialized _ -> "Wallet is already initialized" - ErrAlreadyVanished e' -> "Wallet vanished: " <> show e' - ErrAlreadyFailedToInitialize e' -> - "Wallet failed to initialize (wallet present): " - <> show e' - ErrAlreadyClosing -> "Wallet is already closing" - --- | Monad for acting on a 'WalletResource'. -type WalletResourceM = - ReaderT WalletResource (ExceptT ErrWalletResource IO) - --- | Run a 'WalletResourceM' action on a 'WalletResource'. -runWalletResourceM - :: WalletResourceM a - -> WalletResource - -> IO (Either ErrWalletResource a) -runWalletResourceM action resource = - runExceptT (runReaderT action resource) - --- | Run an 'IO' function on the 'WalletInstance'. -onWalletInstance - :: (WalletIO.WalletInstance -> IO a) - -> WalletResourceM a -onWalletInstance action = ReaderT $ \resource -> - ExceptT - $ first ErrNoWallet <$> Resource.onResource action resource - -{----------------------------------------------------------------------------- - Initialization -------------------------------------------------------------------------------} - --- | Prefix for deposit wallets on disk. -depositPrefix :: String -depositPrefix = "deposit-" - --- | Scan a directory for deposit wallets. -scanDirectoryForDepositPrefix :: FilePath -> IO [FilePath] -scanDirectoryForDepositPrefix dir = do - files <- listDirectory dir - pure $ filter (depositPrefix `isPrefixOf`) files - -deleteTheDepositWalletOnDisk :: FilePath -> IO () -deleteTheDepositWalletOnDisk dir = do - ds <- scanDirectoryForDepositPrefix dir - case ds of - [d] -> removeFile (dir d) - _ -> pure () - --- | Try to open an existing wallet -findTheDepositWalletOnDisk - :: WalletBootEnv IO - -> FilePath - -- ^ Path to the wallet database directory - -> (Either ErrLoadingDatabase WalletIO.WalletStore -> IO a) - -- ^ Action to run if the wallet is found - -> IO a -findTheDepositWalletOnDisk env dir action = do - ds <- scanDirectoryForDepositPrefix dir - case ds of - [d] -> do - (credentials, customers) <- - deserialise <$> BL.readFile (dir d) - let state = - fromCredentialsAndGenesis - credentials - (fromIntegral @Int customers) - (genesisData env) - store <- newStore - writeS store state - action $ Right store - [] -> action $ Left $ ErrDatabaseNotFound dir - ds' -> action $ Left $ ErrMultipleDatabases ((dir ) <$> ds') - -instance Serialise XPub where - encode = encode . unXPub - decode = do - b <- decode - case xpub b of - Right x -> pure x - Left e -> fail e - -instance Serialise XPrv where - encode = encode . unXPrv - decode = do - b :: ByteString <- decode - case xprv b of - Right x -> pure x - Left e -> fail e - -instance Serialise Credentials - --- | Try to create a new wallet -createTheDepositWalletOnDisk - :: Tracer IO String - -- ^ Tracer for logging - -> FilePath - -- ^ Path to the wallet database directory - -> Credentials - -- ^ Id of the wallet - -> Word31 - -- ^ Maximum customer index - -> (Maybe WalletIO.WalletStore -> IO a) - -- ^ Action to run if the wallet is created - -> IO a -createTheDepositWalletOnDisk _tr dir credentials users action = do - ds <- scanDirectoryForDepositPrefix dir - case ds of - [] -> do - let fp = dir depositPrefix <> hashWalletId credentials - BL.writeFile fp - $ serialise (credentials, fromIntegral users :: Int) - store <- newStore - action $ Just store - _ -> do - action Nothing - where - hashWalletId :: Credentials -> String - hashWalletId = - B8.unpack - . convertToBase Base16 - . blake2b160 - . xpubToBytes - . accountXPubFromCredentials - --- | Load an existing wallet from disk. -loadWallet - :: Tracer IO () - -- ^ Tracer for wallet tip changes - -> WalletIO.WalletBootEnv IO - -- ^ Environment for the wallet - -> FilePath - -- ^ Path to the wallet database directory - -> WalletResourceM () -loadWallet wtc bootEnv dir = do - let action - :: (WalletIO.WalletInstance -> IO b) -> IO (Either ErrDatabase b) - action f = findTheDepositWalletOnDisk bootEnv dir $ \case - Right wallet -> - Right - <$> WalletIO.withWalletLoad wtc - (WalletIO.WalletEnv bootEnv wallet) - f - Left e -> pure $ Left $ ErrLoadingDatabase e - resource <- ask - lift - $ ExceptT - $ first ErrWalletPresent - <$> Resource.putResource action resource - --- | Initialize a new wallet from an 'XPub'. -initWallet - :: Tracer IO () - -- ^ Tracer for wallet tip changes - -> Tracer IO String - -- ^ Tracer for logging - -> WalletIO.WalletBootEnv IO - -- ^ Environment for the wallet - -> FilePath - -- ^ Path to the wallet database directory - -> Credentials - -- ^ Id of the wallet - -> Word31 - -- ^ Max number of users ? - -> WalletResourceM () -initWallet wtc tr bootEnv dir credentials users = do - let action - :: (WalletIO.WalletInstance -> IO b) -> IO (Either ErrDatabase b) - action f = createTheDepositWalletOnDisk tr dir credentials users $ \case - Just wallet -> do - fmap Right - $ WalletIO.withWalletInit wtc - (WalletIO.WalletEnv bootEnv wallet) - credentials - users - $ \i -> do - addresses <- map snd <$> WalletIO.listCustomers i - addresses `deepseq` f i - Nothing -> - pure - $ Left - $ ErrCreatingDatabase - $ ErrDatabaseAlreadyExists dir - resource <- ask - lift - $ ExceptT - $ first ErrWalletPresent - <$> Resource.putResource action resource - -deleteWallet :: FilePath -> WalletResourceM () -deleteWallet dir = do - resource <- ask - lift - $ ExceptT - $ first ErrNoWallet - <$> Resource.closeResource resource - liftIO $ deleteTheDepositWalletOnDisk dir - -walletExists :: FilePath -> IO Bool -walletExists dir = do - r <- scanDirectoryForDepositPrefix dir - case r of - [] -> pure False - _ -> pure True - -walletPublicIdentity :: WalletResourceM WalletPublicIdentity -walletPublicIdentity = onWalletInstance WalletIO.walletPublicIdentity - -{----------------------------------------------------------------------------- - Operations -------------------------------------------------------------------------------} - --- | List all tracked customers addresses. -listCustomers :: WalletResourceM [(Customer, Address)] -listCustomers = onWalletInstance WalletIO.listCustomers - --- | Retrieve the address for a customer if it's tracked by the wallet. -customerAddress :: Customer -> WalletResourceM (Maybe Address) -customerAddress = onWalletInstance . WalletIO.customerAddress - -addressToCustomer :: WalletResourceM WalletIO.ResolveAddress -addressToCustomer = onWalletInstance WalletIO.addressToCustomer - -{----------------------------------------------------------------------------- - Operations - Reading from the blockchain -------------------------------------------------------------------------------} -getWalletTip :: WalletResourceM Read.ChainPoint -getWalletTip = onWalletInstance WalletIO.getWalletTip - -availableBalance :: WalletResourceM Read.Value -availableBalance = onWalletInstance WalletIO.availableBalance - -getTxHistoryByCustomer - :: WalletResourceM ByCustomer -getTxHistoryByCustomer = onWalletInstance WalletIO.getTxHistoryByCustomer - -getTxHistoryByTime - :: WalletResourceM ByTime -getTxHistoryByTime = onWalletInstance WalletIO.getTxHistoryByTime - -networkTag :: WalletResourceM Read.NetworkTag -networkTag = onWalletInstance WalletIO.networkTag - -{----------------------------------------------------------------------------- - Operations - Writing to blockchain -------------------------------------------------------------------------------} - -createPayment - :: [(Address, Read.Value)] - -> WalletResourceM (Either ErrCreatePayment CurrentEraResolvedTx) -createPayment = onWalletInstance . WalletIO.createPayment - -getBIP32PathsForOwnedInputs - :: Write.Tx - -> WalletResourceM [BIP32Path] -getBIP32PathsForOwnedInputs = - onWalletInstance . WalletIO.getBIP32PathsForOwnedInputs - -canSign :: WalletResourceM CanSign -canSign = onWalletInstance WalletIO.canSign - -signTx - :: Write.Tx - -> Passphrase - -> WalletResourceM (Maybe Write.Tx) -signTx tx = onWalletInstance . WalletIO.signTx tx - -inspectTx - :: CurrentEraResolvedTx - -> WalletResourceM InspectTx -inspectTx = onWalletInstance . WalletIO.inspectTx - -resolveCurrentEraTx :: Write.Tx -> WalletResourceM CurrentEraResolvedTx -resolveCurrentEraTx = onWalletInstance . WalletIO.resolveCurrentEraTx - -submitTx :: Write.Tx -> WalletResourceM (Either Network.ErrPostTx ()) -submitTx = onWalletInstance . WalletIO.submitTx diff --git a/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Catch.hs b/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Catch.hs deleted file mode 100644 index b4a331c9b7b..00000000000 --- a/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Catch.hs +++ /dev/null @@ -1,45 +0,0 @@ -module Cardano.Wallet.Deposit.REST.Catch - ( catchRunWalletResourceM - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.REST - ( WalletResource - , WalletResourceM - , runWalletResourceM - ) -import Control.Exception - ( SomeException (..) - , try - ) -import Control.Monad.IO.Class - ( MonadIO (..) - ) -import Control.Monad.Trans.Except - ( throwE - ) -import Servant - ( Handler (..) - , ServerError (..) - ) - -import qualified Data.ByteString.Lazy.Char8 as BL - --- | Catch and run a 'WalletResourceM' action, converting any exceptions to --- 'ServerError'. -catchRunWalletResourceM - :: WalletResource - -> ServerError - -> WalletResourceM a - -> Handler a -catchRunWalletResourceM s se f = do - er <- liftIO $ try $ runWalletResourceM f s - case er of - Right (Right a) -> - pure a - Right (Left e) -> - Handler $ throwE $ se{errBody = BL.pack $ show e} - Left (SomeException e) -> - Handler $ throwE $ se{errBody = BL.pack $ show e} diff --git a/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Start.hs b/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Start.hs deleted file mode 100644 index 89236c97bf6..00000000000 --- a/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Start.hs +++ /dev/null @@ -1,86 +0,0 @@ -{-# LANGUAGE ScopedTypeVariables #-} - -module Cardano.Wallet.Deposit.REST.Start - ( loadDepositWalletFromDisk - , newBootEnv - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.IO - ( WalletBootEnv (..) - ) -import Cardano.Wallet.Deposit.IO.Network.NodeToClient - ( CardanoBlock - , NetworkLayer - , StandardCrypto - , fromNetworkLayer - ) -import Cardano.Wallet.Deposit.REST - ( WalletResource - , loadWallet - , runWalletResourceM - , walletExists - ) -import Control.Monad - ( when - ) -import Control.Monad.IO.Class - ( MonadIO (..) - ) -import Control.Monad.Trans.Except - ( ExceptT (..) - , runExceptT - ) -import Control.Tracer - ( Tracer - , stdoutTracer - , traceWith - ) -import Data.Functor.Contravariant - ( (>$<) - ) - -import qualified Cardano.Chain.Genesis as Byron -import qualified Cardano.Wallet.Deposit.Read as Read - -lg :: (MonadIO m, Show a) => Tracer IO String -> String -> a -> m () -lg tr p x = liftIO $ traceWith tr $ p <> ": " <> show x - -loadDepositWalletFromDisk - :: Tracer IO () - -- ^ Tracer for wallet tip changes - -> Tracer IO String - -> FilePath - -> WalletBootEnv IO - -> WalletResource - -> IO () -loadDepositWalletFromDisk wtc tr dir env resource = do - result <- flip runWalletResourceM resource $ do - test <- liftIO $ walletExists dir - when test $ do - lg tr "Loading wallet from" dir - loadWallet wtc env dir - lg tr "Wallet loaded from" dir - pure test - case result of - Left e -> error $ show e - Right _ -> pure () - -newBootEnv - :: Maybe FilePath - -> NetworkLayer IO (CardanoBlock StandardCrypto) - -> IO (WalletBootEnv IO) -newBootEnv genesisFile nl = do - eGenesisData <- runExceptT $ case genesisFile of - Nothing -> ExceptT $ pure $ Right Read.mockGenesisDataMainnet - Just file -> fst <$> Byron.readGenesisData file - case eGenesisData of - Left e -> error $ show e - Right genesisData' -> - return - $ WalletBootEnv - (show >$< stdoutTracer) - genesisData' - (fromNetworkLayer nl) diff --git a/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Wallet/Create.hs b/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Wallet/Create.hs deleted file mode 100644 index 715b4d413ab..00000000000 --- a/lib/deposit-wallet/rest/Cardano/Wallet/Deposit/REST/Wallet/Create.hs +++ /dev/null @@ -1,32 +0,0 @@ -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DuplicateRecordFields #-} - -module Cardano.Wallet.Deposit.REST.Wallet.Create - ( PostWalletViaMnemonic (..) - , PostWalletViaXPub (..) - ) -where - -import Prelude - -import Data.Text - ( Text - ) -import GHC.Generics - ( Generic - ) - --- | Data for a request to create a wallet via a mnemonic. -data PostWalletViaMnemonic = PostWalletViaMnemonic - { mnemonics :: Text - , password :: Text - , trackedCustomers :: Int - } - deriving (Generic) - --- | Data for a request to create a wallet via an extended public key. -data PostWalletViaXPub = PostWalletViaXPub - { xpub :: Text - , trackedCustomers :: Int - } - deriving (Generic) diff --git a/lib/deposit-wallet/spec/cardano-deposit-wallet.lagda.md b/lib/deposit-wallet/spec/cardano-deposit-wallet.lagda.md deleted file mode 100644 index f2c62a03439..00000000000 --- a/lib/deposit-wallet/spec/cardano-deposit-wallet.lagda.md +++ /dev/null @@ -1,418 +0,0 @@ -# Specification: Customer Deposit Wallet - -## Synopsis - -🚧 DRAFT 2023-10-18 - -This document specifies the core functionality of a **customer deposit wallet**, -or **deposit wallet** for short. - -A customer deposit wallet allows you to track the origin of incoming funds: -Each customer is assigned a unique address belonging to the wallet; -a deposit made at this address is treated as originating from the customer. - -Technically, each customer is represented by a numerical index (natural number). -Essentially, the deposit wallet manages a mapping between indices and addresses, -and tracks incoming funds for each known address. - -# Setup - -This document is a [literate Agda][lagda] file: It contains prose that -describes and explains the specification, but it also contains definitions -and logical properties that can be checked by the proof assistant [Agda][]. - -We use Agda because we plan to create a **machine-checked proof** -that our implementation adheres to this specification. -Specifically, we plan to implement the core functionality in Agda, -i.e. the functionality specificied in this document, and export -the code to Haskell using [agda2hs][] so that the core functionality -can be embedded in a full software application. - - [agda]: https://github.com/agda/agda - [lagda]: https://agda.readthedocs.io/en/v2.6.4/tools/literate-programming.html - [agda2hs]: https://github.com/agda/agda2hs - -## Imports - -In order to formulate the specification, we need to import standard vocabulary: - -```agda -open import Haskell.Prelude -open import Relation.Nullary using (¬_) -open import Data.Product using () renaming (_×_ to both) -``` - -We also define a few conveniences: - -A predicate `_∈_` that records whether an item is an element of a list - -```agda -_∈_ : ∀ {a : Set} {{_ : Eq a}} → a → List a → Set -x ∈ xs = elem x xs ≡ True -``` - -The logical combinator "if and only if" - -```agda -_⟷_ : Set → Set → Set -x ⟷ y = both (x → y) (y → x) -``` - -```agda -isJust : ∀ {a : Set} → Maybe a → Bool -isJust (Just _) = True -isJust Nothing = False -``` - -```agda -isSubsetOf : ∀ {a : Set} {{_ : Eq a}} → List a → List a → Bool -isSubsetOf xs ys = all (λ x → elem x ys) xs -``` - -# Specification - -## Overview - -This specification of a **customer deposit wallet** -amounts to the specification of an abstract data type `WalletState`, -which represents the entire state of such a wallet. - -The goal of this document is to specify the operations -on this abstract data type and the logical properties that relate them. - -We define a `module` `DepositWallet` which is parametrized by -several definitions from the Cardano ledger, -but also by the abstract data type `WalletState` that we wish to specify. - -```agda -module - DepositWallet - (WalletState : Set) - (Address : Set) - {{_ : Eq Address}} - (Slot : Set) - (TxId : Set) - (Tx : Set) - (Value : Set) - {{_ : Eq Value}} - (PParams : Set) - where -``` - -## Operations - -We now list all auxiliary data types and all -operations supported by the abstract data type `WalletState`. -This list is meant for reference -— we will explain each of them in detail in the subsequent sections. - -Auxiliary data types: - -```agda - Customer = Nat - - record ValueTransfer : Set where - field - spent : Value - received : Value - - open ValueTransfer - - TxSummary : Set - TxSummary = Slot × TxId × ValueTransfer -``` - -Operations: - -```agda - record Operations : Set where - field - - listCustomers : WalletState → List (Customer × Address) - createAddress : Customer → WalletState → (Address × WalletState) - - availableBalance : WalletState → Value - applyTx : Tx → WalletState → WalletState - - getCustomerHistory : WalletState → Customer → List TxSummary - - createPayment - : List (Address × Value) - → PParams → WalletState → Maybe Tx -``` - -## Properties - -In subsequent sections, we will specify the properties that -the operations should satisfy. - -The following record collects the properties: - -```agda - record Properties - (O : Operations) - : Set₁ - where - open Operations O -``` - -(For some reason, it needs to be in `Set₁`.) - -### Mapping between Customers and Address - -The type `Customer` denotes a unique identier for a customer. -For reasons explained later, we choose to represent this type -as numerical indices, i.e. natural numbers: - - Customer = Nat - -The mapping between customers and addresses will be queried and established with -the following operations: - - listCustomers : WalletState → List (Customer × Address) - createAddress : Customer → WalletState → (Address × WalletState) - -Here, `listCustomers` lists all customer/address pairs that have been mapped to each other so far. -In turn, `createAddress` adds a new customer/address to the mapping. - -In order to express how these functions are related, we define - -```agda - knownCustomer : Customer → WalletState → Bool - knownCustomer c = elem c ∘ map fst ∘ listCustomers - - knownCustomerAddress : Address → WalletState → Bool - knownCustomerAddress address = elem address ∘ map snd ∘ listCustomers -``` - -Here, a `knownCustomer` is a `Customer` that appears in the result of `listCustomers`, -while `knownCustomerAddress` is an `Address` that appears in the result. -Note that a deposit wallet may own additional `Addresses` not included here, -such as change addresses — but these addresses are not customer addresses. - -The two operations are related by the property - -```agda - field - - prop_create-get - : ∀(c : Customer) (s0 : WalletState) - → let (address , s1) = createAddress c s0 - in knownCustomerAddress address s1 ≡ True -``` - -### Address derivation - -For compatibility with hardware wallets and the [BIP-32][] standard, -we derive the `Address` of each customer from the root private key -of the wallet in a deterministic fashion: - -```agda - deriveAddress : WalletState → (Customer → Address) - - prop_create-derive - : ∀(c : Customer) (s0 : WalletState) - → let (address , _) = createAddress c s0 - in deriveAddress s0 c ≡ address -``` - -Specifically, in the notation of [BIP-32][], we use - - deriveAddress : WalletState → Nat → Address - deriveAddress s ix = rootXPrv s / 1857' / 1815' / 0' / 0 / ix - -Here, `1857` is a new “purpose” identifier; we cannot reuse the [CIP-1852][] standard, because it behaves differently when discovering funds in blocks. - - [bip-32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki - [cip-1852]: https://cips.cardano.org/cips/cip1852/ - -This method of deriving addresses is also the reason why we choose -a concrete representation of `Customer` as a natural number. - -### Applying transactions - -TODO: Specification of total wallet funds. -Amounts to rewrite of the original wallet specification -by Edsko and Duncan in Agda. To be specified in a separate document. - - availableBalance : WalletState → Value - applyTx : Tx → WalletState → WalletState - -### Tracking incoming funds - -Beyond assigning an address to a customer, -the new wallet state returned by `createAddress` -also tracks this address whenever new blocks are incorporated into the wallet state. -For this purpose of tracking, we introduce an operation - - getCustomerHistory : WalletState → Customer → List TxSummary - -which returns a list of transaction summaries. For a given transaction, such a summary reports the total `Value` spend or received at a specific address. - - record ValueTransfer : Set where - field - spent : Value - received : Value - - open ValueTransfer - - TxSummary : Set - TxSummary = Slot × TxId × ValueTransfer - -Note that `Value` includes both native coins (ADA) and -user-defined assets, such as stablecoins NFTs. -Also note that the customer deposit wallet does not support -delegation and reward accounts, and the `spent` value -can only be spent from transaction outputs. - -The function `getCustomerHistory` allows users to detect incoming -transfers by observing the `received` value. - -The behavior of this function is best specified in terms of a function - -```agda - summarize : WalletState → Tx → List (Address × TxSummary) - - getAddressSummary - : Address → List (Address × TxSummary) → List TxSummary - getAddressSummary address = - map snd ∘ filter (λ x → fst x == address) -``` - -which summarizes a single transaction. Specifically, the result of `getCustomerHistory` an aggregate of all previous transaction summaries. - -```agda - field - prop_getAddressHistory-summary - : ∀ (s : WalletState) - (c : Customer) - (address : Address) - (tx : Tx) - → (c , address) ∈ listCustomers s - → getCustomerHistory (applyTx tx s) c - ≡ (getAddressSummary address (summarize s tx)) - ++ getCustomerHistory s c -``` - -Importantly, we only track an address if and only if it is a `knownCustomerAddress`. - -```agda - prop_tx-known-address - : ∀ (address : Address) - (s : WalletState) - (tx : Tx) - → (knownCustomerAddress address s ≡ True) - ⟷ (address ∈ map fst (summarize s tx)) -``` - -### Creating transactions - -Finally, we expose an operation - - createPayment - : List (Address × Value) - → PParams → WalletState → Maybe Tx - -which constructs and signs a transaction that sends given values to given addresses. -Here, `PParams` are protocol parameters needed for computation the fee to -include in the `Tx`. - -First, this function will succeed in creating a transaction if there are sufficient -funds available: - -```agda - - field - totalValue : List (Address × Value) → Value - -- totalValue = mconcat ∘ map snd - - maxFee : Value -- maximum fee of a transaction - exceeds : Value → Value → Set - _<>_ : Value → Value → Value - - prop_createPayment-success - : ∀ (s : WalletState) - (pp : PParams) - (destinations : List (Address × Value)) - → exceeds (availableBalance s) (totalValue destinations <> maxFee) - → isJust (createPayment destinations pp s) ≡ True -``` - -TODO: The above statement cannot hold as written, -but it would be highly desirable to have something in this spirit. -(This would be part of a separate specification file -related to `balanceTransaction`.) -Aside from insufficient funds, reasons for failure include: - -* Wallet UTxO is poor - * Few UTxO which are too close to minimum ADA quantity - * UTxO with too many native assets -* Destinations are poor - * `Value` does not carry minimum ADA quantity - * `Value` size too large (native assets, `Datum`, …) -* Combination of both: - * Too many UTxO with small ADA amount - that we need to cover a large `Value` payment. - Example: "Have 1 million x 1 ADA coins, want to send 1 x 1'000'000 ADA coin." - -Second, the transaction sends funds as indicated - -```agda - field - outputs : Tx → List (Address × Value) - - field - prop_createPayment-pays - : ∀ (s : WalletState) - (pp : PParams) - (destinations : List (Address × Value)) - (tx : Tx) - → createPayment destinations pp s ≡ Just tx - → isSubsetOf (outputs tx) destinations ≡ True -``` - -Third, and most importantly, the operation `createPayment` never creates a transaction -whose `received` summary for any tracked index/address pair is non-zero. -In other words, `createPayment` uses change addresses that are distinct -from any address obtained via `createAddress`. - -That said, `createPayment` is free to contribute to the `spent` summary of any address -— the deposit wallet spends funds from any address as it sees fit. - -In other words, we have - -```agda - getAddress : (Address × Value) → Address - getAddress = fst - - field - prop_createPayment-not-known - : ∀ (address : Address) - (s : WalletState) - (pp : PParams) - (destinations : List (Address × Value)) - (tx : Tx) - → knownCustomerAddress address s ≡ True - → createPayment destinations pp s ≡ Just tx - → ¬(address ∈ map getAddress (outputs tx)) -``` - -## Derived Properties - -TODO -From the properties above, one can prove various other properties. -However, this requires and induction principle on `WalletState`, -where we can be certain that other operations do not interfere -with the given ones. - -```agda -{- -prop_getAddressHistory-unknown : Set -prop_getAddressHistory-unknown - = ∀ (s : WalletState) - (addr : Address) - → knownAddress addr s ≡ False - → getAddressHistory addr s ≡ [] --} -``` - diff --git a/lib/deposit-wallet/spec/openapi/index.html b/lib/deposit-wallet/spec/openapi/index.html deleted file mode 100644 index 75d1ff89a92..00000000000 --- a/lib/deposit-wallet/spec/openapi/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Customer Deposit Wallet API - - - - - - - - - - - - - diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO.hs deleted file mode 100644 index 3bec178e0a2..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO.hs +++ /dev/null @@ -1,417 +0,0 @@ -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE Rank2Types #-} -{-# LANGUAGE RecordWildCards #-} - -module Cardano.Wallet.Deposit.IO - ( -- * Types - WalletEnv (..) - , WalletStore - , WalletBootEnv (..) - , WalletPublicIdentity (..) - , WalletInstance - - -- * Operations - - -- ** Initialization - , withWalletInit - , Word31 - , withWalletLoad - , walletPublicIdentity - - -- ** Mapping between customers and addresses - , listCustomers - , customerAddress - , addressToCustomer - , ResolveAddress - - -- ** Reading from the blockchain - , getWalletTip - , availableBalance - , getTxHistoryByCustomer - , getTxHistoryByTime - , getCustomerDeposits - , getAllDeposits - - -- ** Writing to the blockchain - - -- *** Create transactions - , createPayment - , inspectTx - - -- *** Sign transactions - , getBIP32PathsForOwnedInputs - , signTx - - -- *** Submit transactions - , submitTx - , listTxsInSubmission - - -- * Internals - , onWalletState - , networkTag - , readWalletState - , resolveCurrentEraTx - , canSign - ) where - -import Prelude - -import Cardano.Wallet.Address.BIP32 - ( BIP32Path - ) -import Cardano.Wallet.Deposit.IO.Network.Type - ( NetworkEnv (slotToUTCTime) - ) -import Cardano.Wallet.Deposit.Pure - ( Credentials - , CurrentEraResolvedTx - , Customer - , ValueTransfer - , WalletPublicIdentity (..) - , WalletState - , Word31 - ) -import Cardano.Wallet.Deposit.Pure.API.TxHistory - ( ByCustomer - , ByTime - , LookupTimeFromSlot - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( CanSign - ) -import Cardano.Wallet.Deposit.Read - ( Address - , TxId - , WithOrigin - ) -import Cardano.Wallet.Network.Checkpoints.Policy - ( defaultPolicy - ) -import Control.Tracer - ( Tracer - , contramap - , traceWith - ) -import Data.Bifunctor - ( first - ) -import Data.List.NonEmpty - ( NonEmpty - ) -import Data.Map.Strict - ( Map - ) -import Data.Time - ( UTCTime - ) - -import qualified Cardano.Wallet.Deposit.IO.Network.Type as Network -import qualified Cardano.Wallet.Deposit.Pure as Wallet -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Control.Concurrent.Async as Async -import qualified Data.DBVar as DBVar -import qualified Data.Delta as Delta - ( Replace (..) - ) -import qualified Data.Delta.Update as Delta -import qualified Data.Store as Store - -{----------------------------------------------------------------------------- - Types -------------------------------------------------------------------------------} - --- | The environment needed to initialize a wallet, before a database is --- connected. -data WalletBootEnv m = WalletBootEnv - { logger :: Tracer m WalletLog - -- ^ Logger for the wallet. - , genesisData :: Read.GenesisData - -- ^ Genesis data for the wallet. - , networkEnv :: Network.NetworkEnv m (Read.EraValue Read.Block) - -- ^ Network environment for the wallet. - } - --- | The wallet store type. -type WalletStore = Store.UpdateStore IO Wallet.DeltaWalletState - --- | The full environment needed to run a wallet. -data WalletEnv m = WalletEnv - { bootEnv :: WalletBootEnv m - -- ^ The boot environment. - , store :: WalletStore - -- ^ The store for the wallet. - } - -data WalletInstance = WalletInstance - { env :: WalletEnv IO - , walletState :: DBVar.DBVar IO Wallet.DeltaWalletState - } - -{----------------------------------------------------------------------------- - Helpers -------------------------------------------------------------------------------} - --- | Convenience to apply an 'Update' to the 'WalletState' via the 'DBLayer'. -onWalletState - :: WalletInstance - -> Delta.Update Wallet.DeltaWalletState r - -> IO r -onWalletState WalletInstance{walletState} = - Delta.onDBVar walletState - --- FIXME: Propagation of exceptions from Pure to IO. - --- | Convenience to read the 'WalletState'. --- --- Use 'onWalletState' if you want to use the result in an atomic update. -readWalletState :: WalletInstance -> IO WalletState -readWalletState WalletInstance{walletState} = - DBVar.readDBVar walletState - -{----------------------------------------------------------------------------- - Operations - Initialization -------------------------------------------------------------------------------} - --- | Initialize a new wallet in the given environment. -withWalletInit - :: Tracer IO () -- wallet tip changes - -> WalletEnv IO - -> Credentials - -> Word31 - -> (WalletInstance -> IO a) - -> IO a -withWalletInit - wtc - env@WalletEnv - { bootEnv = WalletBootEnv{genesisData} - , .. - } - credentials - customers - action = do - walletState <- - DBVar.initDBVar store - $ Wallet.fromCredentialsAndGenesis - credentials - customers - genesisData - withWalletDBVar wtc env walletState action - --- | Load an existing wallet from the given environment. -withWalletLoad - :: Tracer IO () -- wallet tip changes - -> WalletEnv IO - -> (WalletInstance -> IO a) - -> IO a -withWalletLoad wtc env@WalletEnv{..} action = do - walletState <- DBVar.loadDBVar store - withWalletDBVar wtc env walletState action - -withWalletDBVar - :: Tracer IO () -- wallet tip changes - -> WalletEnv IO - -> DBVar.DBVar IO Wallet.DeltaWalletState - -> (WalletInstance -> IO a) - -> IO a -withWalletDBVar - wtc - env@WalletEnv{bootEnv = WalletBootEnv{logger, networkEnv}} - walletState - action = do - let w = WalletInstance{env, walletState} - Async.withAsync (doChainSync w) $ \_ -> action w - where - doChainSync = Network.chainSync networkEnv trChainSync . chainFollower - trChainSync = contramap (\_ -> WalletLogDummy) logger - chainFollower w = - Network.ChainFollower - { checkpointPolicy = defaultPolicy - , readChainPoints = do - walletTip <- Wallet.getWalletTip <$> readWalletState w - pure - [ walletTip - , Read.GenesisPoint - ] - , rollForward = rollForward w wtc - , rollBackward = rollBackward w - } - -{----------------------------------------------------------------------------- - Operations -------------------------------------------------------------------------------} -listCustomers :: WalletInstance -> IO [(Customer, Address)] -listCustomers w = - Wallet.listCustomers <$> readWalletState w - -customerAddress :: Customer -> WalletInstance -> IO (Maybe Address) -customerAddress c w = Wallet.customerAddress c <$> readWalletState w - -walletPublicIdentity :: WalletInstance -> IO WalletPublicIdentity -walletPublicIdentity w = do - state <- readWalletState w - pure - $ WalletPublicIdentity - { pubXpub = Wallet.walletXPub state - , pubNextUser = Wallet.trackedCustomers state - } - -type ResolveAddress = Address -> Maybe Customer - -addressToCustomer :: WalletInstance -> IO ResolveAddress -addressToCustomer w = do - state <- readWalletState w - pure $ flip Wallet.addressToCustomer state - -{----------------------------------------------------------------------------- - Operations - Reading from the blockchain -------------------------------------------------------------------------------} -getWalletTip :: WalletInstance -> IO Read.ChainPoint -getWalletTip w = - Wallet.getWalletTip <$> readWalletState w - -availableBalance :: WalletInstance -> IO Read.Value -availableBalance w = - Wallet.availableBalance <$> readWalletState w - -getTxHistoryByCustomer :: WalletInstance -> IO ByCustomer -getTxHistoryByCustomer w = - Wallet.getTxHistoryByCustomer <$> readWalletState w - -getTxHistoryByTime :: WalletInstance -> IO ByTime -getTxHistoryByTime w = Wallet.getTxHistoryByTime <$> readWalletState w - -getCustomerDeposits - :: WalletInstance - -> Customer - -> Maybe (WithOrigin UTCTime, WithOrigin UTCTime) - -> IO (Map TxId ValueTransfer) -getCustomerDeposits w c i = - Wallet.getCustomerDeposits c i <$> readWalletState w - -getAllDeposits - :: WalletInstance - -> Maybe (WithOrigin UTCTime, WithOrigin UTCTime) - -> IO (Map Customer ValueTransfer) -getAllDeposits w i = - Wallet.getAllDeposits i <$> readWalletState w - -rollForward - :: WalletInstance - -> Tracer IO () -- wallet tip changes - -> NonEmpty (Read.EraValue Read.Block) - -> tip - -> IO () -rollForward w wtc blocks _nodeTip = do - timeFromSlot <- slotResolver w - onWalletState w - $ Delta.update - $ Delta.Replace - . Wallet.rollForwardMany - timeFromSlot - blocks - traceWith wtc () - x <- readWalletState w - x `seq` pure () - -rollBackward - :: WalletInstance -> Read.ChainPoint -> IO Read.ChainPoint -rollBackward w point = do - timeFromSlot <- slotResolver w - onWalletState w - $ Delta.updateWithResult - $ first Delta.Replace . Wallet.rollBackward timeFromSlot point - --- | Compute a slot resolver for the given slots. -slotResolver - :: WalletInstance - -> IO LookupTimeFromSlot -slotResolver w = do - slotToUTCTime - $ networkEnv - $ bootEnv - $ env w - -networkTag :: WalletInstance -> IO Read.NetworkTag -networkTag w = do - Wallet.networkTag <$> readWalletState w - -{----------------------------------------------------------------------------- - Operations - Constructing transactions -------------------------------------------------------------------------------} - -createPayment - :: [(Address, Read.Value)] - -> WalletInstance - -> IO (Either Wallet.ErrCreatePayment CurrentEraResolvedTx) -createPayment a w = do - timeTranslation <- Network.getTimeTranslation network - pparams <- - Network.currentPParams network - Wallet.createPayment pparams timeTranslation a <$> readWalletState w - where - network = networkEnv $ bootEnv $ env w - -inspectTx - :: CurrentEraResolvedTx - -> WalletInstance - -> IO Wallet.InspectTx -inspectTx tx w = flip Wallet.inspectTx tx <$> readWalletState w - -resolveCurrentEraTx - :: Write.Tx - -> WalletInstance - -> IO CurrentEraResolvedTx -resolveCurrentEraTx tx w = - Wallet.resolveCurrentEraTx tx <$> readWalletState w - -{----------------------------------------------------------------------------- - Operations - Signing transactions -------------------------------------------------------------------------------} - -canSign :: WalletInstance -> IO CanSign -canSign w = do - Wallet.canSign <$> readWalletState w - -getBIP32PathsForOwnedInputs - :: Write.Tx -> WalletInstance -> IO [BIP32Path] -getBIP32PathsForOwnedInputs a w = - Wallet.getBIP32PathsForOwnedInputs a <$> readWalletState w - -signTx - :: Write.Tx -> Wallet.Passphrase -> WalletInstance -> IO (Maybe Write.Tx) -signTx a b w = Wallet.signTx a b <$> readWalletState w - -{----------------------------------------------------------------------------- - Operations - Pending transactions -------------------------------------------------------------------------------} - -submitTx - :: Write.Tx -> WalletInstance -> IO (Either Network.ErrPostTx ()) -submitTx tx w = do - e <- Network.postTx network tx - case e of - Right _ -> do - onWalletState w - $ Delta.update - $ Delta.Replace . Wallet.addTxSubmission tx - pure $ Right () - _ -> pure e - where - network = networkEnv $ bootEnv $ env w - -listTxsInSubmission :: WalletInstance -> IO [Write.Tx] -listTxsInSubmission w = - Wallet.listTxsInSubmission <$> readWalletState w - -{----------------------------------------------------------------------------- - Logging -------------------------------------------------------------------------------} -data WalletLog - = WalletLogDummy - deriving (Show) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB.hs deleted file mode 100644 index e332dacf206..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB.hs +++ /dev/null @@ -1,17 +0,0 @@ -{-# LANGUAGE CPP #-} - -module Cardano.Wallet.Deposit.IO.DB - ( -#ifndef mingw32_HOST_OS - module Cardano.Wallet.Deposit.IO.DB.Real -#endif - ) - -where - -#ifdef mingw32_HOST_OS -import Cardano.Wallet.Deposit.IO.DB.Stub - () -#else -import Cardano.Wallet.Deposit.IO.DB.Real -#endif diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB/Real.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB/Real.hs deleted file mode 100644 index 3d4e4e85b73..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB/Real.hs +++ /dev/null @@ -1,61 +0,0 @@ -{-# LANGUAGE Rank2Types #-} - -module Cardano.Wallet.Deposit.IO.DB.Real - ( Connection - , withSqliteFile - , withSqliteInMemory - , SqlM - , runSqlM - , DBLog (..) - ) where - -import Prelude - -import Control.Tracer - ( Tracer - , traceWith - ) -import Database.Table.SQLite.Simple - ( Connection - , SqlM - , runSqlM - , withConnection - ) - -{----------------------------------------------------------------------------- - SqlContext -------------------------------------------------------------------------------} - --- | Acquire and release an SQLite 'Connection' in memory. -withSqliteInMemory - :: Tracer IO DBLog - -- ^ Logging - -> (Connection -> IO a) - -- ^ Action to run - -> IO a -withSqliteInMemory tr = withSqliteFile tr ":memory:" - --- | Acquire and release an SQLite 'Connection' from a file. -withSqliteFile - :: Tracer IO DBLog - -- ^ Logging - -> FilePath - -- ^ Database file - -> (Connection -> IO a) - -- ^ Action to run - -> IO a -withSqliteFile tr filepath action = - withConnection filepath $ \conn -> do - traceWith tr $ MsgStartConnection filepath - result <- action conn - traceWith tr $ MsgDoneConnection filepath - pure result - -{------------------------------------------------------------------------------- - Logging --------------------------------------------------------------------------------} - -data DBLog - = MsgStartConnection FilePath - | MsgDoneConnection FilePath - deriving (Show, Eq) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB/Stub.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB/Stub.hs deleted file mode 100644 index ec9a50f4454..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/DB/Stub.hs +++ /dev/null @@ -1,2 +0,0 @@ -module Cardano.Wallet.Deposit.IO.DB.Stub () -where diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/Mock.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/Mock.hs deleted file mode 100644 index ee3726965dc..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/Mock.hs +++ /dev/null @@ -1,97 +0,0 @@ -{-# LANGUAGE DuplicateRecordFields #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Mock implementation of a 'NetworkEnv'. -module Cardano.Wallet.Deposit.IO.Network.Mock - ( newNetworkEnvMock - ) where - -import Prelude - -import Cardano.Wallet.Deposit.IO.Network.Type - ( NetworkEnv (..) - ) -import Cardano.Wallet.Network - ( ChainFollower (..) - ) -import Control.Concurrent.Class.MonadSTM - ( MonadSTM - , atomically - , modifyTVar - , newTVarIO - , readTVar - , readTVarIO - , writeTVar - ) -import Control.Monad - ( forever - ) -import Control.Monad.Class.MonadTimer - ( MonadDelay - , threadDelay - ) -import Data.Foldable - ( for_ - ) -import Data.List.NonEmpty - ( NonEmpty ((:|)) - ) - -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Time as Time -import qualified Cardano.Wallet.Deposit.Write as Write - -{----------------------------------------------------------------------------- - Mock implementation of 'NetworkEnv' -------------------------------------------------------------------------------} -newNetworkEnvMock - :: (MonadDelay m, MonadSTM m) - => m (NetworkEnv m (Read.Block Read.Conway)) -newNetworkEnvMock = do - mchain <- newTVarIO [] - mtip <- newTVarIO Read.GenesisPoint - mfollowers <- newTVarIO [] - - let registerAndUpdate follower = do - _ <- rollBackward follower Read.GenesisPoint - (chain, tip) <- atomically $ do - modifyTVar mfollowers (follower :) - (,) <$> readTVar mchain <*> readTVar mtip - case reverse chain of - [] -> pure () - (b : bs) -> rollForward follower (b :| bs) tip - - let forgeBlock tx = atomically $ do - tipOld <- readTVar mtip - let txRead = Write.toConwayTx tx - blockNew = Read.mockNextBlock tipOld [txRead] - tipNew = Read.getChainPoint blockNew - writeTVar mtip tipNew - modifyTVar mchain (blockNew :) - pure (blockNew, tipNew) - - let broadcast block tip = do - followers <- readTVarIO mfollowers - for_ followers $ \follower -> - rollForward follower (block :| []) tip - - pure - NetworkEnv - { chainSync = \_ follower -> do - registerAndUpdate follower - forever $ threadDelay 1000000 - , postTx = \tx -> do - (block, tip) <- forgeBlock tx - broadcast block tip - -- brief delay to account for asynchronous chain followers - threadDelay 100 - pure $ Right () - , currentPParams = - pure $ Read.EraValue Read.mockPParamsConway - , getTimeTranslation = - pure $ Time.toTimeTranslationPure Time.mockTimeInterpreter - , slotToUTCTime = pure Time.unsafeUTCTimeOfSlot - } diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/NodeToClient.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/NodeToClient.hs deleted file mode 100644 index d6f8632980e..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/NodeToClient.hs +++ /dev/null @@ -1,86 +0,0 @@ -{-# LANGUAGE LambdaCase #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Real implementation of a 'NetworkEnv'. -module Cardano.Wallet.Deposit.IO.Network.NodeToClient - ( fromNetworkLayer - , NetworkLayer - , CardanoBlock - , StandardCrypto - ) where - -import Prelude - -import Cardano.Ledger.Api - ( StandardCrypto - ) -import Cardano.Wallet.Deposit.IO.Network.Type - ( ErrPostTx (..) - , NetworkEnv (..) - , mapBlock - ) -import Cardano.Wallet.Deposit.Time - ( toTimeTranslation - ) -import Cardano.Wallet.Network - ( NetworkLayer - , mapChainFollower - ) -import Cardano.Wallet.Primitive.Ledger.Shelley - ( CardanoBlock - ) -import Cardano.Wallet.Primitive.Slotting - ( snapshot - ) -import Cardano.Wallet.Read - ( chainPointFromChainTip - ) -import Control.Monad.Trans.Except - ( runExceptT - , withExceptT - ) -import Control.Tracer - ( nullTracer - ) - -import qualified Cardano.Read.Ledger.Block.Block as Read -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Time as Time -import qualified Cardano.Wallet.Network as NetworkLayer - -{----------------------------------------------------------------------------- - NodeToClient 'NetworkEnv' -------------------------------------------------------------------------------} - --- | Translate the old NetworkLayer to the new NetworkEnv interface -fromNetworkLayer - :: NetworkLayer.NetworkLayer IO Read.ConsensusBlock - -> NetworkEnv IO (Read.EraValue Read.Block) -fromNetworkLayer nl = mapBlock Read.fromConsensusBlock $ - NetworkEnv - { chainSync = \_tr follower -> do - -- TODO: Connect tracer - let follower' = mapChainFollower id id chainPointFromChainTip id follower - NetworkLayer.chainSync nl nullTracer follower' - return $ error "impossible: chainSync returned" - -- TODO: We can change the error type of 'NetworkLayer.postTx' it - -- doesn't need the ErrPostTxEraUnsupported case - , postTx = runExceptT . withExceptT translateErrPostTx . NetworkLayer.postTx nl - , currentPParams = - NetworkLayer.currentPParams nl - , getTimeTranslation = toTimeTranslation (NetworkLayer.timeInterpreter nl) - , slotToUTCTime = Time.slotToUTCTime <$> snapshot ti - } - - where - ti = NetworkLayer.timeInterpreter nl - - translateErrPostTx :: NetworkLayer.ErrPostTx -> ErrPostTx - translateErrPostTx = \case - NetworkLayer.ErrPostTxValidationError errorText -> ErrPostTxValidationError errorText - NetworkLayer.ErrPostTxMempoolFull -> ErrPostTxMempoolFull - NetworkLayer.ErrPostTxEraUnsupported _era -> - error "translateErrPostTx: ErrPostTxEraUnsupported should be impossible" diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/Type.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/Type.hs deleted file mode 100644 index 8e3d92cff59..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Network/Type.hs +++ /dev/null @@ -1,100 +0,0 @@ -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE NamedFieldPuns #-} - -module Cardano.Wallet.Deposit.IO.Network.Type - ( NetworkEnv (..) - , ErrPostTx (..) - , mapBlock - , ChainFollower (..) - ) where - -import Prelude - -import Cardano.Wallet.Deposit.Read - ( Slot - , WithOrigin - ) -import Cardano.Wallet.Network - ( ChainFollower (..) - , mapChainFollower - ) -import Control.Monad.Class.MonadTime - ( UTCTime - ) -import Control.Tracer - ( Tracer - ) -import Data.List.NonEmpty - ( NonEmpty - ) -import Data.Text - ( Text - ) -import Data.Void - ( Void - ) -import GHC.Generics - ( Generic - ) - -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Time as Time -import qualified Cardano.Wallet.Deposit.Write as Write - -{----------------------------------------------------------------------------- - Type -------------------------------------------------------------------------------} - -data NetworkEnv m block = NetworkEnv - { chainSync - :: Tracer m ChainFollowLog - -> ChainFollower m Read.ChainPoint Read.ChainPoint (NonEmpty block) - -> m Void - -- ^ Run the chain-sync mini-protocol (forever). - , postTx - :: Write.Tx - -> m (Either ErrPostTx ()) - -- ^ Post a transaction to the Cardano network. - , currentPParams - :: m (Read.EraValue Read.PParams) - -- ^ Current protocol paramters. - , getTimeTranslation - :: m Time.TimeTranslation - -- ^ Get the current 'TimeInterpreter' from the Cardano node. - , slotToUTCTime - :: m (Slot -> (Maybe (WithOrigin UTCTime))) - - } - -mapBlock - :: Functor m - => (block1 -> block2) - -> NetworkEnv m block1 - -> NetworkEnv m block2 -mapBlock f env@NetworkEnv{chainSync} = env - { chainSync = \tr follower -> - chainSync tr (mapChainFollower id id id (fmap f) follower) - } - -{------------------------------------------------------------------------------- - Errors --------------------------------------------------------------------------------} - --- | Error while trying to send a transaction to the network. -data ErrPostTx - = ErrPostTxValidationError Text - | ErrPostTxMempoolFull - deriving (Eq, Show, Generic) - -{------------------------------------------------------------------------------- - Logging --------------------------------------------------------------------------------} - --- | Higher level log of a chain follower. --- -- Includes computed statistics about synchronization progress. -data ChainFollowLog - = -- = MsgChainSync (ChainSyncLog BlockHeader ChainPoint) - - -- | MsgFollowStats (FollowStats Rearview) - MsgStartFollowing - deriving (Eq, Show, Generic) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Resource.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Resource.hs deleted file mode 100644 index 1f3168023b0..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Resource.hs +++ /dev/null @@ -1,228 +0,0 @@ -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} - -{-# HLINT ignore "Use void" #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Implementation of a 'Resource' (think REST) which can be initialized. -module Cardano.Wallet.Deposit.IO.Resource - ( Resource - , withResource - , ErrResourceMissing (..) - , onResource - , ErrResourceExists (..) - , putResource - , ResourceStatus (..) - , readStatus - , closeResource - ) where - -import Prelude - -import Control.Concurrent - ( forkFinally - ) -import Control.Concurrent.Class.MonadSTM - ( MonadSTM (..) - , TVar - , atomically - , readTVar - , writeTVar - ) -import Control.Monad - ( void - ) -import Control.Monad.Class.MonadThrow - ( MonadThrow (..) - , SomeException - ) - -{----------------------------------------------------------------------------- - Resource -------------------------------------------------------------------------------} - --- | Mutable resource (think REST) that holds a reference of type @a@ --- that has to be initialized with a 'with…' function. -data Resource e a = Resource - { content :: TVar IO (ResourceStatus e a) - , waitForEndOfLife :: IO (Either (Either SomeException e) ()) - -- ^ Wait until the 'Resource' is out of scope. - } - --- | Possible status of the content of a 'Resource'. -data ResourceStatus e a - = Closed - | Opening - | Open a - | FailedToOpen e - | Vanished SomeException - | Closing - deriving (Show) - -instance Functor (ResourceStatus e) where - fmap _ Closed = Closed - fmap _ Opening = Opening - fmap f (Open a) = Open (f a) - fmap _ (Vanished e) = Vanished e - fmap _ (FailedToOpen e) = FailedToOpen e - fmap _ Closing = Closing - --- | Read the status of a 'Resource'. -readStatus :: Resource e a -> STM IO (ResourceStatus e a) -readStatus resource = readTVar (content resource) - --- | Make a 'Resource' that can be initialized later. --- --- Once the 'Resource' has been initialized, --- it will also be cleaned up once the 'withResource' function has finished. --- --- If the 'Resource' vanishes because of an exception, --- the 'withResource' will /not/ be interrupted. --- You can use 'getStatus' to poll the current status. -withResource - :: (Resource e a -> IO b) - -- ^ Action to perform on the 'Resource'. - -> IO b - -- ^ Result of the action. -withResource action = do - content <- newTVarIO Closed - let waitForEndOfLife = atomically $ do - state <- readTVar content - case state of - Closing -> pure $ Right () - Vanished e -> pure $ Left $ Left e - FailedToOpen e -> pure $ Left $ Right e - _ -> retry - resource = Resource{content, waitForEndOfLife} - action resource `finally` closeResource resource - --- | Error condition for 'onResource'. -data ErrResourceMissing e - = -- | The 'Resource' has not been initialized yet. - ErrNotInitialized - | -- | The 'Resource' is currently being initialized. - ErrStillInitializing - | -- | The 'Resource' has not been initialized yet. - ErrVanished SomeException - | -- | The 'Resource' has vanished due to an unhandled exception. - ErrFailedToInitialize e - -- | The 'Resource' has failed to initialize. - | ErrClosing - -- | The 'Resource is currently being closed. - deriving (Show) - --- | Perform an action on a 'Resource' if it is initialized. -onResource - :: (a -> IO b) - -- ^ Action to perform on the initialized 'Resource'. - -> Resource e a - -- ^ The 'Resource' to act on. - -> IO (Either (ErrResourceMissing e) b) -onResource action resource = do - eContent <- readTVarIO $ content resource - case eContent of - Closed -> pure $ Left ErrNotInitialized - Opening -> pure $ Left ErrStillInitializing - Open a -> Right <$> action a - Vanished e -> pure $ Left $ ErrVanished e - FailedToOpen e -> pure $ Left $ ErrFailedToInitialize e - Closing -> pure $ Left ErrClosing - -closeResource :: Resource e a -> IO (Either (ErrResourceMissing e) ()) -closeResource resource = do - r <- atomically $ do - status <- readTVar $ content resource - case status of - Closed -> pure $ Right () - Opening -> pure $ Left ErrStillInitializing - Open _ -> do - writeTVar (content resource) Closing - pure $ Right () - Vanished e -> pure $ Left $ ErrVanished e - FailedToOpen e -> pure $ Left $ ErrFailedToInitialize e - Closing -> pure $ Left ErrClosing - case r of - Right () -> waitForClose resource - Left e' -> pure $ Left e' - -waitForClose :: Resource e a -> IO (Either (ErrResourceMissing e) ()) -waitForClose resource = do - e <- atomically $ do - status <- readTVar (content resource) - case status of - Closed -> pure $ Right () - Vanished e -> pure $ Left $ ErrVanished e - FailedToOpen e -> pure $ Left $ ErrFailedToInitialize e - _ -> retry - case e of - Right () -> pure $ Right () - Left e' -> pure $ Left e' - --- | Error condition for 'putResource'. -data ErrResourceExists e a - = -- | The resource 'a' is currently being initialized. - ErrAlreadyInitializing - | -- | The resource 'a' has already been initialized. - ErrAlreadyInitialized a - | -- | The resource 'a' has vanished. - ErrAlreadyVanished SomeException - | -- | The resource 'a' has failed to initialize. - ErrAlreadyFailedToInitialize e - | -- | The resource 'a' is currently being closed. - ErrAlreadyClosing - deriving (Show) - --- | Initialize a 'Resource' using a @with…@ function. --- This @with…@ function will be called with an argument that does --- not terminate until 'withResource' terminates. --- The function can logically fail returning a 'Left' value. --- Exceptions will be caught and stored in the 'Resource' as well -putResource - :: (forall b. (a -> IO b) -> IO (Either e b)) - -- ^ Function to initialize the resource 'a' - -> Resource e a - -- ^ The 'Resource' to initialize. - -> IO (Either (ErrResourceExists e a) ()) -putResource start resource = do - forking <- atomically $ do - ca :: ResourceStatus e a <- readTVar (content resource) - case ca of - FailedToOpen e -> pure $ Left $ ErrAlreadyFailedToInitialize e - Vanished e -> pure $ Left $ ErrAlreadyVanished e - Opening -> pure $ Left ErrAlreadyInitializing - Open a -> pure $ Left $ ErrAlreadyInitialized a - Closed -> do - writeTVar (content resource) Opening - pure $ Right forkInitialization - Closing -> pure $ Left ErrAlreadyClosing - case forking of - Left e -> pure $ Left e - Right action -> Right <$> action - where - controlInitialization = do - r <- start run - atomically $ case r of - Right (Right ()) -> do - writeTVar (content resource) Closed - Right (Left (Left e)) -> do - writeTVar (content resource) (Vanished e) - Right (Left (Right e)) -> do - writeTVar (content resource) (FailedToOpen e) - Left e -> do - writeTVar (content resource) (FailedToOpen e) - - forkInitialization = void $ forkFinally controlInitialization vanish - - run a = do - atomically $ writeTVar (content resource) (Open a) - waitForEndOfLife resource - - vanish (Left e) = do - atomically $ writeTVar (content resource) (Vanished e) - vanish (Right _) = - pure () -- waitForEndOfLife has succeeded diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Resource/Event.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Resource/Event.hs deleted file mode 100644 index 0cdb58e3008..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/IO/Resource/Event.hs +++ /dev/null @@ -1,48 +0,0 @@ -module Cardano.Wallet.Deposit.IO.Resource.Event - ( onResourceChange - ) where - -import Prelude - -import Cardano.Wallet.Deposit.IO.Resource - ( Resource - , ResourceStatus (..) - , readStatus - ) -import Control.Concurrent.Async - ( withAsync - ) -import Control.Concurrent.Class.MonadSTM - ( MonadSTM (..) - , atomically - ) -import Control.Monad - ( void - ) -import Control.Monad.Cont - ( ContT (..) - ) -import Control.Monad.Fix - ( fix - ) - --- | Run an action whenever the status of a 'Resource' changes. -onResourceChange - :: (ResourceStatus e a -> IO ()) - -> Resource e a - -> ContT x IO () -onResourceChange f resource = do - void $ ContT $ withAsync $ ($ Closed) $ fix $ \loop lastStatus -> do - status <- atomically $ do - status <- readStatus resource - case (status, lastStatus) of - (Closed, Closed) -> retry - (Opening, Opening) -> retry - (Open _a, Open _a') -> retry -- this is something to think about - (FailedToOpen _e, FailedToOpen _e') -> retry - (Vanished _e, Vanished _e') -> retry - (Closing, Closing) -> retry - _ -> pure () - pure status - f status - loop status diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Map.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Map.hs deleted file mode 100644 index 556e8354aec..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Map.hs +++ /dev/null @@ -1,305 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE QuantifiedConstraints #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE StrictData #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} - -module Cardano.Wallet.Deposit.Map - ( -- * Type - Map (..) - - -- * Keys - , W - , F - - -- * Patch management - , unPatch - , forgetPatch - - -- * Accessors - , OpenF - , open - , PatchF - , patch - , ValueF - , value - - -- * Lookup - , lookupMap - , lookupFinger - - -- * Construction - , singletonMap - , singletonFinger - - -- * Conversion - , toFinger - - -- * Modification - , onMap - , onFinger - , Peel - ) -where - -import Cardano.Wallet.Deposit.Map.Timed - ( Timed (..) - , TimedSeq - , extractInterval - , fmapTimedSeq - , singleton - ) -import Data.Kind - ( Type - ) -import Data.Map.Monoidal.Strict - ( MonoidalMap - ) -import Data.Monoid - ( Last (..) - ) -import Prelude hiding - ( lookup - ) - -import qualified Cardano.Wallet.Deposit.Map.Timed as TimedSeq -import qualified Data.Map.Monoidal.Strict as MonoidalMap - --- | Infix form of MonoidalMap type -type (^^^) = MonoidalMap - -infixr 5 ^^^ - --- | A phantom type for tuples of mappings from 'k' tupled with a spurious monoid --- 'w'. This is used to keep track of the patches applied to the map. -data W (w :: Type) (k :: Type) - --- | A phantom type for a finger tree of mappings from 'k' tupled with a spurious --- monoid 'w'. -data F (w :: Type) (k :: Type) - --- | A nested monoidal map. Every nesting can also be patched with a monoid 'w'. -data Map :: [Type] -> Type -> Type where - Value - :: v - -> Map '[] v - -- ^ A leaf node with a value. - Map - :: w - -> k ^^^ Map ks v - -> Map (W w k ': ks) v - -- ^ A node with a patch 'w' and a nested monoidal map. - Finger - :: w - -> TimedSeq k (Map ks v) - -> Map (F w k ': ks) v - -- ^ A node with a patch 'w' and a nested finger tree of maps. - -deriving instance Show v => Show (Map '[] v) - -deriving instance - ( Show w - , Show k - , Show (Map ks v) - ) - => Show (Map (W w k ': ks) v) - -deriving instance Eq v => Eq (Map '[] v) - -deriving instance - ( Eq w - , Eq k - , Eq (Map ks v) - ) - => Eq (Map (W w k ': ks) v) - -deriving instance - ( Show w - , Show k - , Show (Map ks v) - ) - => Show (Map (F w k ': ks) v) - -deriving instance - ( Eq w - , Eq k - , Eq (Map ks v) - ) - => Eq (Map (F w k ': ks) v) - -instance Functor (Map '[]) where - fmap f (Value v) = Value (f v) - -instance Functor (Map xs) => Functor (Map (W w x : xs)) where - fmap f (Map w m) = Map w $ fmap (fmap f) m - -instance - (Functor (Map xs), forall a. Monoid (Map xs a)) - => Functor (Map (F w x : xs)) - where - fmap f (Finger w m) = Finger w $ fmapTimedSeq (fmap f) m - -instance Monoid v => Monoid (Map '[] v) where - mempty = Value mempty - -instance - ( Monoid (Map ks v) - , Ord k - , Monoid w - ) - => Monoid (Map (W w k : ks) v) - where - mempty = Map mempty mempty - -instance (Monoid (Map xs v), Monoid w, Eq x) => Monoid (Map (F w x : xs) v) where - mempty = Finger mempty mempty - -instance Semigroup v => Semigroup (Map '[] v) where - Value a <> Value b = Value (a <> b) - -instance - ( Ord x - , Semigroup (Map xs v) - , Semigroup w - ) - => Semigroup (Map (W w x : xs) v) - where - Map w a <> Map w' b = Map (w <> w') (a <> b) - -instance - (Monoid w, Monoid (Map xs v), Eq x) - => Semigroup (Map (F w x : xs) v) - where - Finger wa a <> Finger wb b = Finger (wa <> wb) (a <> b) - -instance Foldable (Map '[]) where - foldMap f (Value v) = f v - -instance (Foldable (Map xs), Ord x) => Foldable (Map (F w x : xs)) where - foldMap f (Finger _ m) = foldMap (foldMap f) m - -instance (Foldable (Map xs), Ord x) => Foldable (Map (W w x : xs)) where - foldMap f (Map _ m) = foldMap (foldMap f) m - -type family UnPatchF xs where - UnPatchF (Map (W w x ': xs) v) = - Map (W () x ': xs) (w, v) - UnPatchF (Map (F w x ': xs) v) = - Map (F () x ': xs) (w, v) - --- | Push the patch down to the leaves of the map. -unPatch - :: ( y ~ Map (x : ks) v - , Functor (Map ks) - , Monoid (Map ks v) - , Monoid (Map ks (w, v)) - , w ~ PatchF x - ) - => y - -> UnPatchF y -unPatch (Map w m) = Map () $ fmap (fmap (w,)) m -unPatch (Finger w m) = Finger () $ fmapTimedSeq (fmap (w,)) m - -type family ForgetPatchF xs where - ForgetPatchF (Map (W w x ': xs) v) = - Map (W () x ': xs) v - ForgetPatchF (Map (F w x ': xs) v) = - Map (F () x ': xs) v - --- | Forget the patch of any map layer. -forgetPatch - :: (y ~ Map (x : ks) v) - => y - -> ForgetPatchF y -forgetPatch ((Map _ m)) = Map () m -forgetPatch ((Finger _ m)) = Finger () m - -type family PatchF x where - PatchF (W w x) = w - PatchF (F w x) = w - --- | Extract the patch from any map layer. -patch :: Map (x : xs) v -> PatchF x -patch (Map w _) = w -patch (Finger w _) = w - -type family ValueF x where - ValueF (Map '[] v) = v - ValueF (Map (W w x ': xs) v) = x ^^^ Map xs v - ValueF (Map (F w x ': xs) v) = TimedSeq x (Map xs v) - --- | Extract the value from any map layer. -value :: Map xs v -> ValueF (Map xs v) -value (Map _ m) = m -value (Finger _ m) = m -value (Value v) = v - -type family OpenF xs where - OpenF (Map (W w x ': xs) v) = (w, x ^^^ Map xs v) - OpenF (Map (F w x ': xs) v) = (w, TimedSeq x (Map xs v)) - --- | Open any map layer and return the patch as well. -open :: Map (x : xs) v -> OpenF (Map (x : xs) v) -open (Map w m) = (w, m) -open (Finger w m) = (w, m) - --- | Construct a map layer with a single key-value pair. -singletonMap - :: w -> k -> Map xs v -> Map (W w k ': xs) v -singletonMap w k = Map w . MonoidalMap.singleton k - --- | Construct a finger layer with a single key-value pair. -singletonFinger - :: Monoid (Map xs v) => w -> k -> Map xs v -> Map (F w k ': xs) v -singletonFinger w k m = - Finger w $ singleton $ Timed (Last (Just k)) m - -toFinger - :: (Monoid (Map ks a), Eq k) => Map (W w k : ks) a -> Map (F w k : ks) a -toFinger (Map w m) = Finger w $ TimedSeq.fromList $ do - (k, v) <- MonoidalMap.toList m - pure $ Timed (Last (Just k)) v - --- | Lookup a value in first layer of the map and return the patch as well. -lookupMap - :: (Ord k) => k -> Map (W w k : ks) a -> Maybe (w, Map ks a) -lookupMap k (Map w m) = (w,) <$> MonoidalMap.lookup k m - --- | Lookup for an interval of keys in the finger tree and return the patch as well. -lookupFinger - :: (Ord k, Monoid (Map ks a)) - => k - -> k - -> Map (F w k : ks) a - -> Maybe (w, Map ks a) -lookupFinger k1 k2 (Finger w m) = do - case extractInterval k1 k2 m of - Timed (Last Nothing) _ -> Nothing - Timed _ m' -> Just (w, m') - --- | Apply a function to the nested monoidal map keeping the patch. -onMap - :: Map (W w k : ks) a - -> (MonoidalMap k (Map ks a) -> MonoidalMap k (Map ks a)) - -> Map (W w k : ks) a -onMap (Map w m) f = Map w $ f m - --- | Apply a function to the nested finger tree keeping the patch. -onFinger - :: Map (F w k : ks) a - -> (TimedSeq k (Map ks a) -> TimedSeq k (Map ks a)) - -> Map (F w k : ks) a -onFinger (Finger w m) f = Finger w $ f m - -type family Peel x where - Peel (Map (W w k : xs) v) = Map xs v - Peel (Map (F w k : xs) v) = Map xs v - Peel (Map '[] v) = v diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Map/Timed.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Map/Timed.hs deleted file mode 100644 index d8e5efc8082..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Map/Timed.hs +++ /dev/null @@ -1,265 +0,0 @@ -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveFunctor #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE TypeFamilies #-} -{-# OPTIONS_GHC -Wno-orphans #-} - -module Cardano.Wallet.Deposit.Map.Timed - ( - -- * Timed - Timed (..) - -- * TimedSeq - , TimedSeq - -- ** Construction - , fromList - , singleton - -- ** Destruction - , toList - -- ** Query - , takeAfter - , takeUpTo - , extractInterval - , minKey - , maxKey - -- ** Modification - , dropAfter - , dropBefore - -- ** Functor - , fmapTimedSeq - ) -where - -import Prelude hiding - ( null - ) - -import Data.Bifunctor - ( Bifunctor (..) - ) -import Data.FingerTree - ( FingerTree - , Measured (..) - , ViewL (..) - , ViewR (..) - , dropUntil - , fmap' - , split - , takeUntil - , viewl - , viewr - , (<|) - ) -import Data.Function - ( (&) - ) -import Data.Monoid - ( Last (..) - ) - -import qualified Data.FingerTree as FingerTree -import qualified Data.Foldable as F - --- | A value paired with a timestamp. -data Timed t a = Timed - { time :: Last t - , monoid :: a - } - deriving (Eq, Ord, Show, Functor, Foldable) - -instance Semigroup a => Semigroup (Timed t a) where - Timed t1 a1 <> Timed t2 a2 = Timed (t1 <> t2) (a1 <> a2) - -instance Monoid a => Monoid (Timed t a) where - mempty = Timed mempty mempty - -instance Monoid a => Measured (Timed t a) (Timed t a) where - measure = id - --- | A sequence of timed values with a monoidal annotation as itself. --- These values have a semigroup instance that will collapse adjacent values --- with the same timestamp. --- It's up to the user to maintain the invariant that --- the sequence is sorted by timestamp. -newtype TimedSeq t a = TimedSeq - { unTimedSeq :: FingerTree (Timed t a) (Timed t a) - } - deriving (Eq, Show) - -fmapTimedSeq - :: (Monoid a1, Monoid a2) => (a1 -> a2) -> TimedSeq t a1 -> TimedSeq t a2 -fmapTimedSeq f = TimedSeq . fmap' (fmap f) . unTimedSeq - -singleton :: Monoid a => Timed t a -> TimedSeq t a -singleton = TimedSeq . FingerTree.singleton - -instance Monoid a => Measured (Timed t a) (TimedSeq t a) where - measure = measure . unTimedSeq - -instance Foldable (TimedSeq t) where - foldMap f = foldMap (f . monoid) . unTimedSeq - -onFingerTree - :: ( FingerTree (Timed t a) (Timed t a) - -> FingerTree (Timed t a) (Timed t a) - ) - -> TimedSeq t a - -> TimedSeq t a -onFingerTree f = TimedSeq . f . unTimedSeq - -instance (Semigroup a, Monoid a, Eq t) => Semigroup (TimedSeq t a) where - TimedSeq a <> TimedSeq b = case (viewr a, viewl b) of - (EmptyR, _) -> TimedSeq b - (_, EmptyL) -> TimedSeq a - (a' :> Timed t1 v1, Timed t2 v2 :< b') - | t1 == t2 -> TimedSeq $ a' <> (Timed t1 (v1 <> v2) <| b') - | otherwise -> TimedSeq $ a <> b - -instance (Monoid a, Eq t) => Monoid (TimedSeq t a) where - mempty = TimedSeq FingerTree.empty - --- | Construct a 'TimedSeq' from a list of 'Timed' values. -fromList :: (Monoid a, Eq t) => [Timed t a] -> TimedSeq t a -fromList = mconcat . fmap singleton - --- | Convert a 'TimedSeq' to a list of 'Timed' values. --- This is not the inverse of 'fromList' as some values may have been merged. But --- fromList . toList == id. -toList :: TimedSeq t a -> [Timed t a] -toList = F.toList . unTimedSeq - -takeAfterElement - :: (Monoid a, Ord q) - => (t -> q) - -> TimedSeq t a - -> Maybe (Timed t a, TimedSeq t a) -takeAfterElement bucket (TimedSeq tseq) = case viewl tseq of - EmptyL -> Nothing - hd :< _ -> - let - (taken, rest) = - split (\q -> (bucket <$> time q) > (bucket <$> time hd)) tseq - in - Just (measure taken, TimedSeq rest) - -takeBeforeElement - :: (Monoid a, Ord q) - => (t -> q) - -> TimedSeq t a - -> Maybe (Timed t a, TimedSeq t a) -takeBeforeElement bucket (TimedSeq tseq) = case viewr tseq of - EmptyR -> Nothing - _ :> hd -> - let - (rest, taken) = - split (\q -> (bucket <$> time q) >= (bucket <$> time hd)) tseq - in - Just (measure taken, TimedSeq rest) - -takeAfterElements - :: (Monoid a, Ord q, Ord t) - => (t -> q) - -> Maybe Int - -> TimedSeq t a - -> (TimedSeq t a, Maybe t) -takeAfterElements _dt (Just 0) (TimedSeq tseq) = - ( mempty - , case viewl tseq of - EmptyL -> Nothing - Timed (Last hd) _ :< _ -> hd - ) -takeAfterElements bucket mn tseq = - case takeAfterElement bucket tseq of - Just (v, rest) -> - first (onFingerTree (v <|)) - $ takeAfterElements bucket (subtract 1 <$> mn) rest - _ -> (mempty, Nothing) - -takeBeforeElements - :: (Monoid a, Ord q, Ord t) - => (t -> q) - -> Maybe Int - -> TimedSeq t a - -> (TimedSeq t a, Maybe t) -takeBeforeElements _dt (Just 0) (TimedSeq tseq) = - ( mempty - , case viewr tseq of - EmptyR -> Nothing - _ :> Timed (Last hd) _ -> hd - ) -takeBeforeElements bucket mn tseq = case takeBeforeElement bucket tseq of - Just (v, rest) -> - first (onFingerTree (v <|)) - $ takeBeforeElements bucket (subtract 1 <$> mn) rest - _ -> (mempty, Nothing) - --- | Extract the first n elements from a timed seq after and including --- a given start time after applying a bucketing function. --- The result is a map of the extracted elements and the next time to start from. -takeAfter - :: (Monoid a, Ord q, Ord t) - => (t -> q) - -- ^ A function to bucket the timestamps. - -> Maybe t - -- ^ The start time to extract elements from. - -> Maybe Int - -- ^ The number of elements to extract. - -> TimedSeq t a - -- ^ The timed sequence to extract elements from. - -> (TimedSeq t a, Maybe t) -takeAfter bucket mstart mcount = - takeAfterElements bucket mcount - . onFingerTree - ( dropUntil - ( \q -> mstart & maybe True (\t -> time q >= Last (Just t)) - ) - ) - --- | Extract the last n elements from a timed seq before and excluding --- a given start time after applying a bucketing function. --- The result is a map of the extracted elements and the next time to start from. -takeUpTo - :: (Monoid a, Ord q, Ord t) - => (t -> q) - -- ^ A function to bucket the timestamps. - -> Maybe t - -- ^ The start time to extract elements from. - -> Maybe Int - -- ^ The number of elements to extract. - -> TimedSeq t a - -- ^ The timed sequence to extract elements from. - -> (TimedSeq t a, Maybe t) -takeUpTo bucket mstart mcount = - takeBeforeElements bucket mcount - . onFingerTree - ( takeUntil - (\q -> mstart & maybe False (\t -> time q > Last (Just t))) - ) - --- | Try to extract the first element time from a tseq. -minKey :: Monoid a => TimedSeq t a -> Maybe t -minKey (TimedSeq tseq) = case viewl tseq of - Timed (Last (Just t)) _ :< _ -> Just t - _ -> Nothing - --- | Try to extract the last element time from a tseq. -maxKey :: Monoid a => TimedSeq t a -> Maybe t -maxKey (TimedSeq tseq) = case viewr tseq of - _ :> Timed (Last (Just t)) _ -> Just t - _ -> Nothing - --- | Extract all elements from a tseq that are within the given time interval. -extractInterval - :: (Monoid a, Ord t) => t -> t -> TimedSeq t a -> Timed t a -extractInterval t0 t1 (TimedSeq tseq) = - measure - $ takeUntil (\q -> time q > Last (Just t1)) - $ dropUntil (\q -> time q >= Last (Just t0)) tseq - --- | Drop all elements from a tseq that are after the given time. -dropAfter :: (Ord t, Monoid a) => t -> TimedSeq t a -> TimedSeq t a -dropAfter t = onFingerTree $ takeUntil (\q -> time q > Last (Just t)) - --- | Drop all elements from a tseq that are before the given time. -dropBefore :: (Ord t, Monoid a) => t -> TimedSeq t a -> TimedSeq t a -dropBefore t = onFingerTree $ dropUntil (\q -> time q >= Last (Just t)) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure.hs deleted file mode 100644 index 6de8e6a07e3..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure.hs +++ /dev/null @@ -1,138 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE ScopedTypeVariables #-} - -module Cardano.Wallet.Deposit.Pure - ( -- * Types - WalletState - , DeltaWalletState - , WalletPublicIdentity (..) - - -- * Creation - , Credentials (..) - , fromCredentialsAndGenesis - - -- * Operations - - -- ** Mapping between customers and addresses - , Customer - , listCustomers - , addressToCustomer - , deriveAddress - , knownCustomer - , knownCustomerAddress - , isCustomerAddress - , fromRawCustomer - , customerAddress - , trackedCustomers - , walletXPub - - -- ** Reading from the blockchain - , Word31 - , getWalletTip - , availableBalance - , availableUTxO - , rollForwardMany - , rollForwardOne - , rollBackward - , ValueTransfer (..) - , getTxHistoryByCustomer - , getTxHistoryByTime - , getEraSlotOfBlock - , getCustomerDeposits - , getAllDeposits - , networkTag - - -- ** Writing to the blockchain - , ErrCreatePayment (..) - , createPayment - , resolveCurrentEraTx - , CurrentEraResolvedTx - , BIP32Path (..) - , DerivationType (..) - , ResolvedTx (..) - , canSign - , CanSign (..) - , getBIP32PathsForOwnedInputs - , Passphrase - , signTx - , addTxSubmission - , listTxsInSubmission - , inspectTx - , InspectTx (..) - ) where - -import Cardano.Wallet.Address.BIP32 - ( BIP32Path (..) - , DerivationType (..) - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( CanSign (..) - , Credentials (..) - , WalletPublicIdentity (..) - , canSign - , fromCredentialsAndGenesis - ) -import Cardano.Wallet.Deposit.Pure.State.Payment - ( CurrentEraResolvedTx - , ErrCreatePayment (..) - , createPayment - , resolveCurrentEraTx - ) -import Cardano.Wallet.Deposit.Pure.State.Payment.Inspect - ( InspectTx (..) - , inspectTx - ) -import Cardano.Wallet.Deposit.Pure.State.Rolling - ( rollBackward - , rollForwardMany - , rollForwardOne - ) -import Cardano.Wallet.Deposit.Pure.State.Signing - ( Passphrase - , getBIP32PathsForOwnedInputs - , signTx - ) -import Cardano.Wallet.Deposit.Pure.State.Submissions - ( addTxSubmission - , availableBalance - , availableUTxO - , listTxsInSubmission - ) -import Cardano.Wallet.Deposit.Pure.State.TxHistory - ( getAllDeposits - , getCustomerDeposits - , getTxHistoryByCustomer - , getTxHistoryByTime - ) -import Cardano.Wallet.Deposit.Pure.State.Type - ( Customer - , DeltaWalletState - , WalletState - , addressToCustomer - , customerAddress - , deriveAddress - , fromRawCustomer - , getWalletTip - , isCustomerAddress - , knownCustomer - , knownCustomerAddress - , listCustomers - , networkTag - , trackedCustomers - , walletXPub - ) -import Cardano.Wallet.Deposit.Pure.UTxO.Tx - ( ResolvedTx (..) - ) -import Cardano.Wallet.Deposit.Pure.UTxO.ValueTransfer - ( ValueTransfer (..) - ) -import Cardano.Wallet.Deposit.Read - ( getEraSlotOfBlock - ) -import Data.Word.Odd - ( Word31 - ) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/API/Address.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/API/Address.hs deleted file mode 100644 index 70c5785db3a..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/API/Address.hs +++ /dev/null @@ -1,210 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE TypeApplications #-} -{-# OPTIONS_GHC -Wno-orphans #-} - -module Cardano.Wallet.Deposit.Pure.API.Address - ( encodeAddress - , decodeAddress - , DecodingError (..) - , NetworkTag (..) - , getNetworkTag - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.Read - ( Address - , NetworkTag (..) - ) -import Cardano.Wallet.Primitive.Ledger.Shelley - ( StandardCrypto - ) -import Cardano.Wallet.Read.Address - ( toShortByteString - ) -import Codec.Binary.Bech32 - ( DataPart - , HumanReadablePart - , dataPartFromBytes - , dataPartToBytes - , decodeLenient - ) -import Control.Arrow - ( ArrowChoice (..) - ) -import Control.Monad - ( (>=>) - ) -import Control.Monad.State.Strict - ( evalStateT - ) -import Data.ByteString - ( ByteString - ) -import Data.ByteString.Base58 - ( bitcoinAlphabet - , decodeBase58 - , encodeBase58 - ) -import Data.Text - ( Text - ) - -import qualified Cardano.Ledger.Address as SH -import qualified Cardano.Ledger.Address as SL -import qualified Cardano.Ledger.BaseTypes as SL -import qualified Codec.Binary.Bech32 as Bech32 -import qualified Codec.Binary.Bech32.TH as Bech32 -import qualified Data.ByteString.Short as B8 -import qualified Data.Text.Encoding as T - -data AddressFlavor a b - = Bootstrap - {bootstrapFlavor :: a} - | Shelley - {shelleyFlavor :: b} - deriving (Eq, Show) - -withAddressFlavor - :: (a -> c) - -> (b -> c) - -> AddressFlavor a b - -> c -withAddressFlavor f _ (Bootstrap x) = f x -withAddressFlavor _ g (Shelley x) = g x - --- | Errors that can occur when decoding an 'Address'. -data DecodingError - = InvalidBech32Encoding Bech32.DecodingError - | InvalidBase58Encoding - | InvalidHumanReadablePart HumanReadablePart - | InvalidDataPart DataPart - | AddressFlavorMismatch - | AddressDecodingError String - | AddressNetworkMismatch - deriving (Eq, Show) - -humanPart :: NetworkTag -> HumanReadablePart -humanPart = \case - MainnetTag -> [Bech32.humanReadablePart|addr|] - TestnetTag -> [Bech32.humanReadablePart|addr_test|] - -decodeBase58Address - :: ByteString - -> Either - DecodingError - ( AddressFlavor - ByteString - (ByteString, HumanReadablePart) - ) -decodeBase58Address = - fmap Bootstrap - . maybe (Left InvalidBase58Encoding) Right - . decodeBase58 bitcoinAlphabet - -decodeBech32Address - :: Text - -> Either - DecodingError - (AddressFlavor ByteString (ByteString, HumanReadablePart)) -decodeBech32Address bech32 = do - (hrp, dataPart) <- left InvalidBech32Encoding $ decodeLenient bech32 - case dataPartToBytes dataPart of - Nothing -> Left $ InvalidDataPart dataPart - Just bytes -> pure $ Shelley (bytes, hrp) - -decodeHumanAddress - :: Text - -> Either - DecodingError - (AddressFlavor ByteString (ByteString, HumanReadablePart)) -decodeHumanAddress t = - decodeBech32Address t - <> decodeBase58Address (T.encodeUtf8 t) - -newtype CatchFail a = CatchFail {runCatchFail :: Either String a} - deriving (Functor, Applicative, Monad) - -instance MonadFail CatchFail where - fail = CatchFail . Left - -ledgerAddressFlavor :: SL.Addr c -> AddressFlavor () () -ledgerAddressFlavor (SL.AddrBootstrap _) = Bootstrap () -ledgerAddressFlavor _ = Shelley () - -ledgerAddressNetworkTag :: SL.Addr c -> NetworkTag -ledgerAddressNetworkTag addr = case SL.getNetwork addr of - SL.Testnet -> TestnetTag - SL.Mainnet -> MainnetTag - --- | Get the network tag of an 'Address'. -getNetworkTag :: Address -> NetworkTag -getNetworkTag = ledgerAddressNetworkTag . SL.decompactAddr - -ledgerDecode - :: ByteString - -> Either DecodingError (SL.Addr StandardCrypto) -ledgerDecode bs = - left AddressDecodingError - $ runCatchFail - $ evalStateT - (SH.decodeAddrStateLenientT @StandardCrypto True True bs) - 0 - -inspectAddress - :: AddressFlavor ByteString (ByteString, HumanReadablePart) - -> Either DecodingError (AddressFlavor Address Address) -inspectAddress (Bootstrap a) = do - r <- ledgerDecode a - case ledgerAddressFlavor r of - Bootstrap () -> - pure (Bootstrap $ SH.compactAddr r) - _ -> Left AddressFlavorMismatch -inspectAddress (Shelley (bytes, hrp)) = do - r <- ledgerDecode bytes - case (ledgerAddressNetworkTag r, ledgerAddressFlavor r) of - (network, Shelley ()) -> - if humanPart network == hrp - then pure (Shelley $ SH.compactAddr r) - else Left AddressNetworkMismatch - _ -> Left AddressFlavorMismatch - -decodeFlavoredAddress - :: Text - -> Either DecodingError (AddressFlavor Address Address) -decodeFlavoredAddress = decodeHumanAddress >=> inspectAddress - --- | Decode an 'Address' from a 'Text' representation. -decodeAddress - :: Text - -- ^ Text to decode - -> Either DecodingError Address -decodeAddress text = withAddressFlavor id id <$> decodeFlavoredAddress text - -addFlavorToAddress :: Address -> AddressFlavor Address Address -addFlavorToAddress x - | SL.isBootstrapCompactAddr x = Bootstrap x - | otherwise = Shelley x - -encodeFlavoredAddress - :: AddressFlavor Address Address - -> Text -encodeFlavoredAddress (Shelley addr) = bech32 - where - bytes = B8.fromShort $ toShortByteString addr - bech32 = Bech32.encodeLenient hrp (dataPartFromBytes bytes) - hrp = humanPart $ getNetworkTag addr -encodeFlavoredAddress (Bootstrap addr) = - T.decodeUtf8 . encodeBase58 bitcoinAlphabet - $ B8.fromShort - $ toShortByteString addr - --- | Encode an 'Address' to a 'Text' representation. -encodeAddress - :: Address - -- ^ Address to encode - -> Text -encodeAddress = encodeFlavoredAddress . addFlavorToAddress diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/API/TxHistory.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/API/TxHistory.hs deleted file mode 100644 index c01d7159b0c..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/API/TxHistory.hs +++ /dev/null @@ -1,168 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE StrictData #-} - -module Cardano.Wallet.Deposit.Pure.API.TxHistory - ( ByCustomer - , ByTime - , DownTime - , ResolveAddress - , LookupTimeFromSlot - , TxHistory (..) - , firstJust - , transfers - , rollForward - , rollBackward - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.Map - ( F - , Map (..) - , W - , onFinger - , onMap - , singletonFinger - , singletonMap - ) -import Cardano.Wallet.Deposit.Map.Timed - ( TimedSeq - , dropBefore - ) -import Cardano.Wallet.Deposit.Pure.Address - ( Customer - ) -import Cardano.Wallet.Deposit.Pure.Balance - ( ValueTransferMap - ) -import Cardano.Wallet.Deposit.Pure.UTxO.ValueTransfer - ( ValueTransfer - ) -import Cardano.Wallet.Deposit.Read - ( Address - , WithOrigin (..) - ) -import Cardano.Wallet.Deposit.Time - ( LookupTimeFromSlot - ) -import Cardano.Wallet.Read - ( Slot - , TxId - ) -import Data.Foldable - ( Foldable (..) - ) -import Data.Maybe - ( maybeToList - ) -import Data.Monoid - ( First (..) - ) -import Data.Ord - ( Down (..) - ) -import Data.Time - ( UTCTime - ) - -import qualified Data.Map.Monoidal.Strict as MonoidalMap - -firstJust :: a -> First a -firstJust = First . Just - -transfers - :: Foldable (Map xs) => Map xs ValueTransfer -> ValueTransfer -transfers = fold - -type DownTime = Down (WithOrigin UTCTime) - -type ByCustomer = - Map - '[ W () Customer - , F (First Address) DownTime - , W (First Slot) TxId - ] - ValueTransfer - -type ByTime = - Map - '[ F () DownTime - , W (First Slot) Customer - , W (First Address) TxId - ] - ValueTransfer - -data TxHistory = TxHistory - { byCustomer :: ByCustomer - , byTime :: ByTime - } - -instance Semigroup TxHistory where - TxHistory a1 b1 <> TxHistory a2 b2 = TxHistory (a1 <> a2) (b1 <> b2) - -instance Monoid TxHistory where - mempty = TxHistory mempty mempty - -type ResolveAddress = Address -> Maybe Customer - -rollForward - :: ValueTransferMap - -> ResolveAddress - -> LookupTimeFromSlot - -> Slot - -> TxHistory - -> TxHistory -rollForward valueTransferMap resolveAddress timeFromSlot slot = - (txHistory' <>) - where - txHistory' = - blockToTxHistory valueTransferMap resolveAddress timeFromSlot slot - -blockToTxHistory - :: ValueTransferMap - -> ResolveAddress - -> LookupTimeFromSlot - -> Slot - -> TxHistory -blockToTxHistory valueTransferMap resolveAddress timeFromSlot slot = - fold $ do - time <- fmap Down $ maybeToList $ timeFromSlot slot - (address, valueTransferByTxId) <- MonoidalMap.toList valueTransferMap - (txId, valueTransfer) <- MonoidalMap.toList valueTransferByTxId - customer <- maybeToList $ resolveAddress address - let byTime = - singletonFinger () time - $ singletonMap (First $ Just slot) customer - $ singletonMap (First $ Just address) txId - $ Value valueTransfer - let byCustomer = - singletonMap () customer - $ singletonFinger (First $ Just address) time - $ singletonMap (First $ Just slot) txId - $ Value valueTransfer - pure $ TxHistory{byCustomer, byTime} - --- | Roll backward the transaction history to a given slot. This function --- relies on the TxHistory to be sorted by time both on the time and --- customer views. -rollBackward - :: LookupTimeFromSlot - -> Slot - -> TxHistory - -> TxHistory -rollBackward timeFromSlot slot TxHistory{byCustomer, byTime} = - TxHistory - { byCustomer = - onMap byCustomer - $ cleanNulls . fmap (`onFinger` takeToSlot) - , byTime = onFinger byTime takeToSlot - } - where - takeToSlot :: Monoid a => TimedSeq DownTime a -> TimedSeq DownTime a - takeToSlot x = maybe x (`forgetAfter` x) $ timeFromSlot slot - forgetAfter t = dropBefore (Down t) - cleanNulls = MonoidalMap.filter (not . null) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/Balance.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/Balance.hs deleted file mode 100644 index 35d56e49140..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/Balance.hs +++ /dev/null @@ -1,131 +0,0 @@ -{-# LANGUAGE BangPatterns #-} - --- | Wallet balance. -module Cardano.Wallet.Deposit.Pure.Balance - ( balance - , availableUTxO - , IsOurs - , applyBlock - , ValueTransferMap - ) where - -import Prelude - -import Cardano.Wallet.Deposit.Pure.UTxO.DeltaUTxO - ( DeltaUTxO - ) -import Cardano.Wallet.Deposit.Pure.UTxO.Tx - ( IsOurs - , applyTx - , valueTransferFromDeltaUTxO - ) -import Cardano.Wallet.Deposit.Pure.UTxO.UTxO - ( UTxO - , balance - , excluding - ) -import Cardano.Wallet.Deposit.Pure.UTxO.ValueTransfer - ( ValueTransfer - ) -import Cardano.Wallet.Deposit.Read - ( Address - , TxId - ) -import Cardano.Wallet.Read - ( Block - , IsEra - , getTxId - ) -import Data.Foldable - ( Foldable (..) - ) -import Data.Map.Monoidal.Strict - ( MonoidalMap - ) -import Data.Set - ( Set - ) - -import qualified Cardano.Wallet.Deposit.Pure.UTxO.DeltaUTxO as DeltaUTxO -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Cardano.Wallet.Read as Read -import qualified Data.Map.Monoidal.Strict as MonoidalMap -import qualified Data.Map.Strict as Map - -{----------------------------------------------------------------------------- - Wallet Balance -------------------------------------------------------------------------------} - --- | Available = excluding pending transactions -availableUTxO :: UTxO -> [Write.Tx] -> UTxO -availableUTxO u pending = - u `excluding` used - where - used :: Set Read.TxIn - used = foldMap getUsedTxIn pending - - -- UTxO which have been spent or committed as collateral in a pending - -- transaction are not available to use in future transactions. - getUsedTxIn :: Read.Tx Read.Conway -> Set Read.TxIn - getUsedTxIn tx = - Read.getInputs tx - <> Read.getCollateralInputs tx - -{----------------------------------------------------------------------------- - Applying Blocks -------------------------------------------------------------------------------} - --- | Get the value transfer of a 'DeltaUTxO'. -getDeltaUTxOValueTransfer - :: UTxO - -> DeltaUTxO - -> TxId - -> ValueTransferMap -getDeltaUTxOValueTransfer u du txId = fold $ do - (addr, value) <- Map.assocs $ valueTransferFromDeltaUTxO u du - pure - $ MonoidalMap.singleton addr - $ MonoidalMap.singleton - txId - value - --- | A summary of all value transfers in a block. -type ValueTransferMap = - MonoidalMap Address (MonoidalMap TxId ValueTransfer) - --- | Apply a 'Block' to the 'UTxO'. --- --- Returns both a delta and the new value. -applyBlock - :: IsEra era - => IsOurs Read.CompactAddr - -> Block era - -> UTxO - -> (DeltaUTxO, UTxO, ValueTransferMap) -applyBlock isOurs block u0 = - (DeltaUTxO.appends $ reverse dus, u1, totalValueTransfer) - where - (dus, (u1, totalValueTransfer)) = - mapAccumL' applyTx' (u0, mempty) - $ Read.getEraTransactions block - applyTx' tx (u, total) = - let - (ds, u') = applyTx isOurs tx u - value = getDeltaUTxOValueTransfer u ds (getTxId tx) - total' - | null value = total - | otherwise = total <> value - in - (ds, (u', total')) - -{----------------------------------------------------------------------------- - Helpers -------------------------------------------------------------------------------} - --- | Strict variant of 'mapAccumL'. -mapAccumL' :: (a -> s -> (o, s)) -> s -> [a] -> ([o], s) -mapAccumL' f = go [] - where - go os !s0 [] = (reverse os, s0) - go os !s0 (x : xs) = case f x s0 of - (!o, !s1) -> go (o : os) s1 xs diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Creation.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Creation.hs deleted file mode 100644 index 4ca891c21f8..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Creation.hs +++ /dev/null @@ -1,196 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE TypeSynonymInstances #-} -{-# OPTIONS_GHC -Wno-orphans #-} - -module Cardano.Wallet.Deposit.Pure.State.Creation - ( WalletPublicIdentity (..) - , fromCredentialsAndGenesis - , deriveAccountXPrv - , Credentials (..) - , credentialsFromMnemonics - , credentialsFromEncodedXPub - , accountXPubFromCredentials - , rootXPrvFromCredentials - , ErrDecodingXPub (..) - , encodedXPubFromCredentials - , canSign - , CanSign (..) - , createMnemonicFromWords - ) where - -import Prelude hiding - ( lookup - ) - -import Cardano.Address.Derivation - ( xpubFromBytes - , xpubToBytes - ) -import Cardano.Address.Style.Shelley - ( genMasterKeyFromMnemonicShelley - ) -import Cardano.Mnemonic - ( MkSomeMnemonic (..) - , MkSomeMnemonicError - , SomeMnemonic - ) -import Cardano.Wallet.Address.BIP32_Ed25519 - ( XPrv - , XPub - , deriveXPrvHard - , rawSerialiseXPrv - , toXPub - ) -import Cardano.Wallet.Deposit.Pure.State.Type - ( WalletState (..) - ) -import Data.Text - ( Text - ) -import Data.Word.Odd - ( Word31 - ) -import GHC.Generics - ( Generic - ) - -import Cardano.Crypto.Wallet - ( xPrvChangePass - ) -import qualified Cardano.Wallet.Deposit.Pure.Address as Address -import qualified Cardano.Wallet.Deposit.Pure.Submissions as Sbm -import qualified Cardano.Wallet.Deposit.Pure.UTxO.UTxOHistory as UTxOHistory -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Data.ByteString.Base16 as B16 -import qualified Data.ByteString.Char8 as B8 -import qualified Data.Text as T -import qualified Data.Text.Encoding as T - -data WalletPublicIdentity = WalletPublicIdentity - { pubXpub :: XPub - , pubNextUser :: Word31 - } - deriving (Show) - -data Credentials - = XPubCredentials !XPub - | XPrvCredentials !XPrv !XPub - deriving (Generic, Show, Eq) - -instance Show XPrv where - show = B8.unpack . B16.encode . rawSerialiseXPrv - -instance Eq XPrv where - a == b = rawSerialiseXPrv a == rawSerialiseXPrv b - --- | Get /account/ 'XPub' from credentials if available. --- --- The account public key corresponds to the account --- private key obtained from 'deriveAccountXPrv', --- /not/ the root private key. -accountXPubFromCredentials :: Credentials -> XPub -accountXPubFromCredentials (XPubCredentials xpub) = xpub -accountXPubFromCredentials (XPrvCredentials _ xpub) = xpub - --- | Derive account 'XPrv' from the root 'XPrv'. -deriveAccountXPrv :: XPrv -> XPrv -deriveAccountXPrv xprv = - ( deriveXPrvHard - ( deriveXPrvHard - ( deriveXPrvHard - xprv - 1857 -- Address derivation standard - ) - 1815 -- ADA - ) - 0 -- Account number - ) - --- | Get root 'XPrv' from credentials if available. -rootXPrvFromCredentials :: Credentials -> Maybe XPrv -rootXPrvFromCredentials (XPubCredentials _) = Nothing -rootXPrvFromCredentials (XPrvCredentials xprv _) = Just xprv - -fromCredentialsAndGenesis - :: Credentials -> Word31 -> Read.GenesisData -> WalletState -fromCredentialsAndGenesis credentials customers genesisData = - WalletState - { walletTip = Read.GenesisPoint - , addresses = - Address.fromXPubAndCount - network - (accountXPubFromCredentials credentials) - customers - , utxoHistory = UTxOHistory.fromOrigin initialUTxO - , txHistory = mempty - , submissions = Sbm.empty - , rootXSignKey = rootXPrvFromCredentials credentials - } - where - network = Read.getNetworkId genesisData - initialUTxO = mempty - --- | Simplified version of 'mkSomeMnemonic' that takes a space-separated list of --- words. Entropy and checksum are checked as well. -createMnemonicFromWords - :: Text -> Either (MkSomeMnemonicError '[15, 24]) SomeMnemonic -createMnemonicFromWords = mkSomeMnemonic . T.words - --- | Create 'Credentials' from a mnemonic sentence and a passphrase. -credentialsFromMnemonics - :: SomeMnemonic - -- ^ Mnemonics - -> Text - -- ^ Passphrase - -> Credentials -credentialsFromMnemonics mnemonics passphrase = - let - unencryptedXPrv = - genMasterKeyFromMnemonicShelley - mnemonics - (T.encodeUtf8 mempty) - encryptedXPrv = - xPrvChangePass - B8.empty - (T.encodeUtf8 passphrase) - unencryptedXPrv - in - XPrvCredentials - encryptedXPrv - $ toXPub - $ deriveAccountXPrv unencryptedXPrv - -data CanSign = CanSign | CannotSign - deriving (Eq, Show) - -canSign :: WalletState -> CanSign -canSign WalletState{rootXSignKey} = case rootXSignKey of - Nothing -> CannotSign - Just _ -> CanSign - --- | Create 'Credentials' from an extended public key failures to decode -data ErrDecodingXPub = ErrFromXPubBase16 | ErrFromXPubDecodeKey - deriving (Show, Eq) - --- | Create 'Credentials' from an extended public key encoded in base16. -credentialsFromEncodedXPub - :: Text - -> Either ErrDecodingXPub Credentials -credentialsFromEncodedXPub xpub = case B16.decode (T.encodeUtf8 xpub) of - Left _ -> Left ErrFromXPubBase16 - Right bytes -> case xpubFromBytes bytes of - Nothing -> Left ErrFromXPubDecodeKey - Just key -> Right $ XPubCredentials key - --- | Encode an extended public key to base16. -encodedXPubFromCredentials - :: Credentials - -> Text -encodedXPubFromCredentials (XPubCredentials xpub) = - T.decodeUtf8 - $ B16.encode - $ xpubToBytes xpub -encodedXPubFromCredentials (XPrvCredentials _ xpub) = - encodedXPubFromCredentials (XPubCredentials xpub) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Payment.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Payment.hs deleted file mode 100644 index 566e646729f..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Payment.hs +++ /dev/null @@ -1,344 +0,0 @@ -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE NoFieldSelectors #-} -{-# LANGUAGE ScopedTypeVariables #-} - -module Cardano.Wallet.Deposit.Pure.State.Payment - ( ErrCreatePayment (..) - , createPayment - , createPaymentTxBody - , CurrentEraResolvedTx - , resolveCurrentEraTx - , translateBalanceTxError - ) where - -import Prelude hiding - ( lookup - ) - -import Cardano.Ledger.Val - ( isAdaOnly - ) -import Cardano.Wallet.Deposit.Pure.State.Submissions - ( availableUTxO - ) -import Cardano.Wallet.Deposit.Pure.State.Type - ( WalletState (..) - ) -import Cardano.Wallet.Deposit.Pure.UTxO.Tx - ( ResolvedTx (..) - , resolveInputs - ) -import Cardano.Wallet.Deposit.Read - ( Address - ) -import Cardano.Wallet.Deposit.Write - ( Coin - , Tx - , TxBody (..) - , Value - ) -import Cardano.Wallet.Primitive.Types.Tx.Constraints - ( TxSize (..) - ) -import Cardano.Wallet.Read - ( AssetID (AdaID) - , Coin (..) - , fromEraValue - , injectCoin - , lookupAssetID - , toMaryValue - ) -import Control.Monad.Trans.Except - ( runExceptT - ) -import Data.Bifunctor - ( first - ) -import Data.Digest.CRC32 - ( crc32 - ) -import Data.Fixed - ( E6 - , Fixed - ) -import Data.Text - ( Text - ) -import Data.Text.Class.Extended - ( ToText (..) - ) -import Numeric.Natural - ( Natural - ) - -import qualified Cardano.Read.Ledger.Value as Read.L -import qualified Cardano.Wallet.Deposit.Pure.Address as Address -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Cardano.Wallet.Read.Hash as Hash -import qualified Control.Monad.Random.Strict as Random -import qualified Data.Map.Strict as Map -import qualified Data.Text as T - -data ErrCreatePayment - = ErrCreatePaymentNotRecentEra (Read.EraValue Read.Era) - | ErrNotEnoughAda { shortfall :: Value } - | ErrEmptyUTxO - - | ErrTxOutAdaInsufficient { outputIx :: Int, suggestedMinimum :: Coin } - - -- | Only possible when sending (non-ada) assets. - | ErrTxOutValueSizeExceedsLimit { outputIx :: Int } - - -- | Only possible when sending (non-ada) assets. - | ErrTxOutTokenQuantityExceedsLimit - { outputIx :: Int - , quantity :: Natural - , quantityMaxBound :: Natural - } - - -- | The final balanced tx was too big. Either because the payload was too - -- big to begin with, or because we failed to select enough inputs without - -- making it too big, e.g. due to the UTxO containing lots of dust. - -- - -- We should ideally split out 'TooManyPayments' from this error. - -- We should ideally also be able to create payments even when dust causes - -- us to need preparatory txs. - | ErrTxMaxSizeLimitExceeded{ size :: TxSize, maxSize :: TxSize } - deriving (Eq, Show) - -translateBalanceTxError :: Write.ErrBalanceTx Write.Conway -> ErrCreatePayment -translateBalanceTxError = \case - Write.ErrBalanceTxAssetsInsufficient - Write.ErrBalanceTxAssetsInsufficientError{shortfall} -> - ErrNotEnoughAda - { shortfall = fromLedgerValue shortfall - } - Write.ErrBalanceTxMaxSizeLimitExceeded{size, maxSize} -> - ErrTxMaxSizeLimitExceeded{size, maxSize} - Write.ErrBalanceTxExistingKeyWitnesses _ -> - impossible "ErrBalanceTxExistingKeyWitnesses" - Write.ErrBalanceTxExistingCollateral -> - impossible "ErrBalanceTxExistingCollateral" - Write.ErrBalanceTxExistingTotalCollateral -> - impossible "ErrBalanceTxExistingTotalCollateral" - Write.ErrBalanceTxExistingReturnCollateral -> - impossible "ErrBalanceTxExistingReturnCollateral" - Write.ErrBalanceTxInsufficientCollateral _ -> - impossible "ErrBalanceTxInsufficientCollateral" - Write.ErrBalanceTxAssignRedeemers _ -> - impossible "ErrBalanceTxAssignRedeemers" - Write.ErrBalanceTxInternalError e -> - impossible $ show e - Write.ErrBalanceTxInputResolutionConflicts _ -> - -- We are never creating partialTxs with pre-selected inputs, which - -- means this is impossible. - impossible "conflicting input resolution" - Write.ErrBalanceTxUnresolvedInputs _ -> - -- We are never creating partialTxs with pre-selected inputs, which - -- means this is impossible. - impossible "unresolved inputs" - Write.ErrBalanceTxUnresolvedRefunds _ -> - impossible "unresolved refunds" - Write.ErrBalanceTxOutputError (Write.ErrBalanceTxOutputErrorOf ix info) -> case info of - Write.ErrBalanceTxOutputAdaQuantityInsufficient{minimumExpectedCoin} -> - ErrTxOutAdaInsufficient - { outputIx = ix - , suggestedMinimum = minimumExpectedCoin - } - Write.ErrBalanceTxOutputSizeExceedsLimit{} -> - ErrTxOutValueSizeExceedsLimit - { outputIx = ix - } - Write.ErrBalanceTxOutputTokenQuantityExceedsLimit{quantity, quantityMaxBound} -> - ErrTxOutTokenQuantityExceedsLimit - { outputIx = ix - , quantity - , quantityMaxBound - } - Write.ErrBalanceTxUnableToCreateChange - Write.ErrBalanceTxUnableToCreateChangeError{shortfall} -> - ErrNotEnoughAda - { shortfall = injectCoin shortfall - } - Write.ErrBalanceTxUnableToCreateInput -> - ErrEmptyUTxO - - where - fromLedgerValue v = fromEraValue (Read.L.Value v :: Read.L.Value Write.Conway) - - impossible :: String -> a - impossible reason = error $ "impossible: translateBalanceTxError: " <> reason - -instance ToText ErrCreatePayment where - toText = \case - ErrCreatePaymentNotRecentEra era -> - "Cannot create a payment in the era: " <> showT era - ErrNotEnoughAda{shortfall} -> T.unwords - [ "Insufficient funds. Shortfall: ", prettyValue shortfall - ] - ErrEmptyUTxO -> "Wallet has no funds" - ErrTxOutAdaInsufficient{outputIx, suggestedMinimum} -> T.unwords - [ "Ada amount in output " <> showT outputIx - , "is below the required minimum." - , "Suggested minimum amount:", prettyCoin suggestedMinimum - ] - ErrTxMaxSizeLimitExceeded{size, maxSize} -> T.unlines - [ "Exceeded the maximum size limit when creating the transaction." - <> " (size: ", prettyTxSize size, " max size: ", prettyTxSize maxSize <> ")" - , "\nPotential solutions:" - , "1) Make fewer payments at the same time." - , "2) Send smaller amounts of ada in total." - , "3) Fund wallet with more ada." - , "4) Make preparatory payments to yourself to coalesce dust into" - , "larger UTxOs." - ] - ErrTxOutValueSizeExceedsLimit{outputIx} -> T.unwords - [ "The size of the value of output", showT outputIx, "is too large." - , "Try sending fewer assets or splitting them over multiple outputs." - ] - ErrTxOutTokenQuantityExceedsLimit{outputIx, quantity, quantityMaxBound} -> T.unwords - [ "The asset quantity of ", showT quantity, "in output" - , showT outputIx, ", is larger than the maximum allowed" - , "limit", showT quantityMaxBound <> "." - ] - where - showT :: Show a => a -> Text - showT = T.pack . show - - prettyTxSize :: TxSize -> Text - prettyTxSize (TxSize s) = T.pack (show s) - - prettyValue :: Value -> Text - prettyValue v - | isAdaOnly (toMaryValue v) = prettyCoin (CoinC $ lookupAssetID AdaID v) - | otherwise = T.pack (show v) - - prettyCoin :: Coin -> Text - prettyCoin c = T.pack (show c') <> "₳" - where - c' :: Fixed E6 - c' = toEnum $ fromEnum c - -type CurrentEraResolvedTx = ResolvedTx Read.Conway - -resolveCurrentEraTx :: Tx -> WalletState -> CurrentEraResolvedTx -resolveCurrentEraTx tx w = resolveInputs (availableUTxO w) tx - -createPayment - :: Read.EraValue Read.PParams - -> Write.TimeTranslation - -> [(Address, Write.Value)] - -> WalletState - -> Either ErrCreatePayment CurrentEraResolvedTx -createPayment pp tt destinations w = - createPaymentTxBody pp tt (mkPaymentTxBody w destinations) w - --- | Create a payment to a list of destinations. -createPaymentTxBody - :: Read.EraValue Read.PParams - -> Write.TimeTranslation - -> TxBody - -> WalletState - -> Either ErrCreatePayment CurrentEraResolvedTx -createPaymentTxBody - (Read.EraValue (Read.PParams pparams :: Read.PParams era)) - timeTranslation - txBody - state = - case Read.theEra :: Read.Era era of - Read.Conway -> - first translateBalanceTxError - $ flip resolveCurrentEraTx state - <$> createPaymentConway - pparams - timeTranslation - txBody - state - era' -> Left $ ErrCreatePaymentNotRecentEra (Read.EraValue era') - -mkPaymentTxBody - :: WalletState -> [(Address, Write.Value)] -> Write.TxBody -mkPaymentTxBody w destinations = - Write.TxBody - { spendInputs = mempty - , collInputs = mempty - , txouts = - Map.fromList - $ zip [(toEnum 0) ..] - $ map (uncurry Write.mkTxOut) destinations - , collRet = Nothing - , expirySlot = Just . computeExpirySlot $ walletTip w - } - --- | In the Conway era: Create a payment to a list of destinations. -createPaymentConway - :: Write.PParams Write.Conway - -> Write.TimeTranslation - -> TxBody - -> WalletState - -> Either (Write.ErrBalanceTx Write.Conway) Write.Tx -createPaymentConway pparams timeTranslation body w = - fmap (Read.Tx . fst) - . flip Random.evalRand (pilferRandomGen w) - . runExceptT - . balance - (availableUTxO w) - (addresses w) - . mkPartialTx - $ body - where - mkPartialTx :: Write.TxBody -> Write.PartialTx Write.Conway - mkPartialTx txbody = - Write.PartialTx - { tx = Read.unTx $ Write.mkTx txbody - , extraUTxO = mempty :: Write.UTxO Write.Conway - , redeemers = mempty - , stakeKeyDeposits = Write.StakeKeyDepositMap mempty - , timelockKeyWitnessCounts = Write.TimelockKeyWitnessCounts mempty - } - - balance utxo addressState = - Write.balanceTx - pparams - timeTranslation - Write.AllKeyPaymentCredentials - (Write.constructUTxOIndex $ Write.toConwayUTxO utxo) - (changeAddressGen addressState) - () - - changeAddressGen s = - Write.ChangeAddressGen - { Write.genChangeAddress = - first Read.decompactAddr . Address.newChangeAddress s - , Write.maxLengthChangeAddress = - Read.decompactAddr $ Address.mockMaxLengthChangeAddress s - } - --- | Use entropy contained in the current 'WalletState' --- to construct a pseudorandom seed. --- (NOT a viable source of cryptographic randomness.) --- --- Possible downsides of this approach: --- --- 1. security/privacy --- 2. concurrency --- 3. retries for different coin selections -pilferRandomGen :: WalletState -> Random.StdGen -pilferRandomGen = - Random.mkStdGen . fromEnum . fromChainPoint . walletTip - where - fromChainPoint (Read.GenesisPoint) = 0 - fromChainPoint (Read.BlockPoint _ headerHash) = - crc32 $ Hash.hashToBytes headerHash - --- | Compute an expiry slot from a current 'ChainPoint'. -computeExpirySlot :: Read.ChainPoint -> Read.SlotNo -computeExpirySlot Read.GenesisPoint = 0 -computeExpirySlot (Read.BlockPoint slotNo _) = - slotNo + hour - where - hour = 60 * 60 diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Payment/Inspect.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Payment/Inspect.hs deleted file mode 100644 index 9cb26bbdc46..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Payment/Inspect.hs +++ /dev/null @@ -1,128 +0,0 @@ -{-# LANGUAGE MultiWayIf #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TypeApplications #-} - -module Cardano.Wallet.Deposit.Pure.State.Payment.Inspect - ( inspectTx - , CurrentEraResolvedTx - , InspectTx (..) - , transactionBalance - ) where - -import Prelude - -import Cardano.Read.Ledger.Tx.Fee - ( Fee (..) - , getEraFee - ) -import Cardano.Read.Ledger.Tx.Output - ( Output (..) - ) -import Cardano.Read.Ledger.Tx.Outputs - ( Outputs (..) - , getEraOutputs - ) -import Cardano.Wallet.Deposit.Pure.Address - ( Customer - , isChangeAddress - ) -import Cardano.Wallet.Deposit.Pure.State.Payment - ( CurrentEraResolvedTx - ) -import Cardano.Wallet.Deposit.Pure.State.Type - ( WalletState (..) - , addressToCustomer - ) -import Cardano.Wallet.Deposit.Pure.UTxO.Tx - ( ResolvedTx (..) - ) -import Cardano.Wallet.Deposit.Read - ( Address - , TxId - ) -import Cardano.Wallet.Read - ( Coin (..) - , Conway - , TxIx - , Value (..) - , getCompactAddr - , getInputs - , getValue - , mkEraTxOut - , pattern TxIn - ) -import Control.Lens - ( Field2 (_2) - , Field3 (_3) - , to - , (^.) - ) -import Data.Foldable - ( Foldable (..) - , fold - ) -import Data.Monoid - ( Sum (..) - ) - -import qualified Data.Map.Strict as Map -import qualified Data.Set as Set - --- | Inspect the inputs and outputs of a transaction. -data InspectTx = InspectTx - { ourInputs :: [(TxId, TxIx, Coin)] - -- ^ Our inputs. - , otherInputs :: [(TxId, TxIx)] - -- ^ Other inputs, there shouldn't be any. - , change :: [(Address, Coin)] - -- ^ Change outputs. - , ourOutputs :: [(Address, Customer, Coin)] - -- ^ Our outputs. The customer is the owner of the address. There could be - -- reasons the user wants to move funds among customer addresses. - , otherOutputs :: [(Address, Coin)] - -- ^ Other outputs. This is regular money leaving the wallet. - , fee :: Coin - } - deriving (Eq, Show) - --- | Calculate the output balance of a transaction, which is the sum of the --- values of our inputs minus the sum of the values of the change outputs and --- minus the outputs to our customers. -transactionBalance :: InspectTx -> Integer -transactionBalance InspectTx{..} = getSum $ - (ourInputs ^. traverse . _3 . mkSum) - - (change ^. traverse . _2 . mkSum) - - (ourOutputs ^. traverse . _3 . mkSum) - where - mkSum = to (Sum . unCoin) - --- | Inspect a transaction where inputs have been resolved to our UTxO. -inspectTx :: WalletState -> CurrentEraResolvedTx -> InspectTx -inspectTx ws (ResolvedTx tx ourUTxO) = - let - (ourInputs, otherInputs) = fold $ do - in'@(TxIn txId txIx) <- Set.toList $ getInputs tx - case Map.lookup in' ourUTxO of - Just out -> do - let ValueC coins _ = getValue out - pure ([(txId, txIx, coins)], []) - Nothing -> [([], [(txId, txIx)])] - (change, ourOutputs, otherOutputs) = fold $ do - out <- - fmap (mkEraTxOut @Conway . Output) - $ toList - $ (\(Outputs outs) -> outs) - $ getEraOutputs tx - let addr = getCompactAddr out - ValueC coins _ = getValue out - contrib = pure (addr, coins) - if - | isChangeAddress (addresses ws) addr -> [(contrib, [], [])] - | otherwise -> - case addressToCustomer addr ws of - Just customer -> [([], [(addr, customer, coins)], [])] - Nothing -> [([], [], contrib)] - Fee fee = getEraFee tx - in - InspectTx{..} diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Rolling.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Rolling.hs deleted file mode 100644 index 540773f84a4..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Rolling.hs +++ /dev/null @@ -1,117 +0,0 @@ -module Cardano.Wallet.Deposit.Pure.State.Rolling - ( rollForwardMany - , rollForwardOne - , rollBackward - ) where - -import Prelude hiding - ( lookup - ) - -import Cardano.Wallet.Deposit.Pure.Balance - ( ValueTransferMap - ) -import Cardano.Wallet.Deposit.Pure.State.Type -import Cardano.Wallet.Deposit.Pure.UTxO.UTxOHistory - ( UTxOHistory - ) -import Cardano.Wallet.Deposit.Read - ( Address - , getEraSlotOfBlock - ) -import Cardano.Wallet.Deposit.Time - ( LookupTimeFromSlot - ) -import Data.Foldable - ( Foldable (..) - , foldl' - ) -import Data.List.NonEmpty - ( NonEmpty - ) - -import qualified Cardano.Wallet.Deposit.Pure.Address as Address -import qualified Cardano.Wallet.Deposit.Pure.API.TxHistory as TxHistory -import qualified Cardano.Wallet.Deposit.Pure.Balance as Balance -import qualified Cardano.Wallet.Deposit.Pure.RollbackWindow as Rollback -import qualified Cardano.Wallet.Deposit.Pure.Submissions as Sbm -import qualified Cardano.Wallet.Deposit.Pure.UTxO.UTxOHistory as UTxOHistory -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Data.Delta as Delta - -rollForwardMany - :: LookupTimeFromSlot - -> NonEmpty (Read.EraValue Read.Block) - -> WalletState - -> WalletState -rollForwardMany timeFromSlot blocks w = - foldl' (flip $ rollForwardOne timeFromSlot) w blocks - -rollForwardOne - :: LookupTimeFromSlot - -> Read.EraValue Read.Block - -> WalletState - -> WalletState -rollForwardOne timeFromSlot (Read.EraValue block) w = - w - { walletTip = Read.getChainPoint block - , utxoHistory = utxoHistory' - , submissions = Delta.apply (Sbm.rollForward block) (submissions w) - , txHistory = - TxHistory.rollForward - valueTransfers - (`addressToCustomer` w) - timeFromSlot - (getEraSlotOfBlock block) - (txHistory w) - } - where - (utxoHistory', valueTransfers) = - rollForwardUTxO isOurs block (utxoHistory w) - isOurs :: Address -> Bool - isOurs = Address.isOurs (addresses w) - -rollForwardUTxO - :: Read.IsEra era - => (Address -> Bool) - -> Read.Block era - -> UTxOHistory - -> (UTxOHistory, ValueTransferMap) -rollForwardUTxO isOurs block u = - (UTxOHistory.rollForward slot deltaUTxO u, valueTransfers) - where - (deltaUTxO, _, valueTransfers) = - Balance.applyBlock isOurs block (UTxOHistory.getUTxO u) - slot = Read.getEraSlotNo $ Read.getEraBHeader block - -rollBackward - :: LookupTimeFromSlot - -> Read.ChainPoint - -> WalletState - -> (WalletState, Read.ChainPoint) -rollBackward timeFromSlot targetPoint w = - ( w - { walletTip = actualPoint - , utxoHistory = - UTxOHistory.rollBackward actualSlot (utxoHistory w) - , submissions = - Delta.apply (Sbm.rollBackward actualSlot) (submissions w) - , txHistory = - TxHistory.rollBackward timeFromSlot actualSlot (txHistory w) - } - , actualPoint - ) - where - h = utxoHistory w - - targetSlot = Read.slotFromChainPoint targetPoint - actualSlot = Read.slotFromChainPoint actualPoint - - -- NOTE: We don't keep enough information about - -- the block hashes to roll back to - -- any other point than the target point (or genesis). - actualPoint = - if (targetSlot `Rollback.member` UTxOHistory.getRollbackWindow h) - then -- FIXME: Add test for rollback window of `submissions` - targetPoint - else Read.GenesisPoint diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Signing.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Signing.hs deleted file mode 100644 index 4d01c5ab989..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Signing.hs +++ /dev/null @@ -1,72 +0,0 @@ -module Cardano.Wallet.Deposit.Pure.State.Signing - ( getBIP32PathsForOwnedInputs - , signTx - , Passphrase - ) where - -import Prelude - -import Cardano.Crypto.Wallet - ( xPrvChangePass - ) -import Cardano.Wallet.Address.BIP32 - ( BIP32Path (..) - ) -import Cardano.Wallet.Address.BIP32_Ed25519 - ( deriveXPrvBIP32Path - ) -import Cardano.Wallet.Deposit.Pure.State.Submissions - ( availableUTxO - ) -import Cardano.Wallet.Deposit.Pure.State.Type - ( WalletState (..) - ) -import Data.Maybe - ( mapMaybe - ) -import Data.Set - ( Set - ) -import Data.Text - ( Text - ) - -import qualified Cardano.Wallet.Deposit.Pure.Address as Address -import qualified Cardano.Wallet.Deposit.Pure.UTxO as UTxO -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Data.ByteString as BS -import qualified Data.Text.Encoding as T - -getBIP32PathsForOwnedInputs :: Write.Tx -> WalletState -> [BIP32Path] -getBIP32PathsForOwnedInputs tx w = - getBIP32Paths w $ resolveInputAddresses inputs - where - inputs = Read.getInputs tx <> Read.getCollateralInputs tx - - resolveInputAddresses :: Set Read.TxIn -> [Read.Address] - resolveInputAddresses ins = - map (Read.address . snd) - . UTxO.toList - $ UTxO.restrictedBy (availableUTxO w) ins - -getBIP32Paths :: WalletState -> [Read.Address] -> [BIP32Path] -getBIP32Paths w = - mapMaybe $ Address.getBIP32Path (addresses w) - -type Passphrase = Text - --- | Sign the transaction if 'rootXSignKey' is 'Just'. -signTx :: Write.Tx -> Passphrase -> WalletState -> Maybe Write.Tx -signTx tx passphrase w = signTx' <$> rootXSignKey w - where - signTx' encryptedXPrv = - foldr Write.addSignature tx keys - where - unencryptedXPrv = - xPrvChangePass - (T.encodeUtf8 passphrase) - BS.empty - encryptedXPrv - keys = deriveXPrvBIP32Path unencryptedXPrv - <$> getBIP32PathsForOwnedInputs tx w diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Submissions.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Submissions.hs deleted file mode 100644 index c829a9bc457..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Submissions.hs +++ /dev/null @@ -1,55 +0,0 @@ -module Cardano.Wallet.Deposit.Pure.State.Submissions - ( -- * Txs in submission queue management - addTxSubmission - , listTxsInSubmission - - -- * Balance considering pending transactions - , availableBalance - , availableUTxO - ) where - -import Prelude hiding - ( lookup - ) - -import Cardano.Wallet.Deposit.Pure.Balance - ( balance - ) -import Cardano.Wallet.Deposit.Pure.State.Type - ( WalletState (..) - ) -import Cardano.Wallet.Deposit.Read - ( UTxO - ) -import Cardano.Wallet.Read - ( Value - ) - -import qualified Cardano.Wallet.Deposit.Pure.Balance as Balance -import qualified Cardano.Wallet.Deposit.Pure.Submissions as Sbm -import qualified Cardano.Wallet.Deposit.Pure.UTxO.UTxOHistory as UTxOHistory -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Data.Delta as Delta - -addTxSubmission :: Write.Tx -> WalletState -> WalletState -addTxSubmission tx w = - w - { submissions = Delta.apply (Sbm.add tx) (submissions w) - } - -listTxsInSubmission :: WalletState -> [Write.Tx] -listTxsInSubmission = Sbm.listInSubmission . submissions - --- | Compute the available balance from the current 'WalletState' considering --- the pending transactions in the submission queue. -availableBalance :: WalletState -> Value -availableBalance = balance . availableUTxO - --- | Compute the available UTxO from the current 'WalletState' considering --- the pending transactions in the submission queue. -availableUTxO :: WalletState -> UTxO -availableUTxO w = - Balance.availableUTxO utxo pending - where - pending = listTxsInSubmission w - utxo = UTxOHistory.getUTxO $ utxoHistory w diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/TxHistory.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/TxHistory.hs deleted file mode 100644 index 572a75bc6a5..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/TxHistory.hs +++ /dev/null @@ -1,108 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE TypeOperators #-} - -module Cardano.Wallet.Deposit.Pure.State.TxHistory - ( getTxHistoryByCustomer - , getTxHistoryByTime - , getEraSlotOfBlock - , getCustomerDeposits - , getAllDeposits - ) where - -import Prelude hiding - ( lookup - ) - -import Cardano.Wallet.Deposit.Map - ( Map - , W - , lookupMap - , value - ) -import Cardano.Wallet.Deposit.Map.Timed - ( Timed - , TimedSeq - , extractInterval - , monoid - ) -import Cardano.Wallet.Deposit.Pure.API.TxHistory - ( ByCustomer - , ByTime - , DownTime - , TxHistory (..) - ) -import Cardano.Wallet.Deposit.Pure.State.Type - ( Customer - , WalletState (..) - ) -import Cardano.Wallet.Deposit.Pure.UTxO.ValueTransfer - ( ValueTransfer (..) - ) -import Cardano.Wallet.Deposit.Read - ( TxId - , WithOrigin (..) - , getEraSlotOfBlock - ) -import Data.FingerTree - ( Measured (..) - ) -import Data.Foldable - ( Foldable (..) - , fold - ) -import Data.Map.Monoidal.Strict - ( MonoidalMap (..) - ) -import Data.Ord - ( Down (..) - ) -import Data.Time - ( UTCTime - ) - -import qualified Data.Map.Strict as Map - -getTxHistoryByCustomer :: WalletState -> ByCustomer -getTxHistoryByCustomer state = byCustomer $ txHistory state - -getTxHistoryByTime :: WalletState -> ByTime -getTxHistoryByTime state = byTime $ txHistory state - -getCustomerDeposits - :: Customer - -> Maybe (WithOrigin UTCTime, WithOrigin UTCTime) - -> WalletState - -> Map.Map TxId ValueTransfer -getCustomerDeposits c interval s = fold $ do - fmap (wonders interval . value . snd) - $ lookupMap c - $ getTxHistoryByCustomer s - -getAllDeposits - :: Maybe (WithOrigin UTCTime, WithOrigin UTCTime) - -> WalletState - -> Map.Map Customer ValueTransfer -getAllDeposits interval s = - wonders interval - $ value - $ getTxHistoryByTime s - -wonders - :: (Ord k, Monoid w, Foldable (Map xs), Monoid (Map xs ValueTransfer)) - => Maybe (WithOrigin UTCTime, WithOrigin UTCTime) - -> TimedSeq DownTime (Map (W w k : xs) ValueTransfer) - -> Map.Map k ValueTransfer -wonders interval = - getMonoidalMap - . monoid - . fmap (fmap fold . value) - . extractInterval' interval - where - extractInterval' - :: Monoid a - => Maybe (WithOrigin UTCTime, WithOrigin UTCTime) - -> TimedSeq (DownTime) a - -> Timed (DownTime) a - extractInterval' Nothing = measure - extractInterval' (Just (t1, t2)) = extractInterval (Down t1) (Down t2) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Type.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Type.hs deleted file mode 100644 index 5868d614a89..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/State/Type.hs +++ /dev/null @@ -1,127 +0,0 @@ -{-# LANGUAGE StrictData #-} -module Cardano.Wallet.Deposit.Pure.State.Type - ( -- * Types - WalletState (..) - , DeltaWalletState - , Customer - - -- * Operations - , listCustomers - , customerAddress - , addressToCustomer - , deriveAddress - , knownCustomer - , knownCustomerAddress - , isCustomerAddress - , fromRawCustomer - , trackedCustomers - , walletXPub - , getUTxO - , getWalletTip - , networkTag - ) where - -import Prelude hiding - ( lookup - ) - -import Cardano.Crypto.Wallet - ( XPrv - , XPub - ) -import Cardano.Wallet.Deposit.Pure.API.TxHistory - ( TxHistory (..) - ) -import Cardano.Wallet.Deposit.Read - ( NetworkTag - ) -import Cardano.Wallet.Deposit.Write - ( Address - ) -import Data.Word.Odd - ( Word31 - ) - -import qualified Cardano.Wallet.Deposit.Pure.Address as Address -import qualified Cardano.Wallet.Deposit.Pure.Submissions as Sbm -import qualified Cardano.Wallet.Deposit.Pure.UTxO.UTxO as UTxO -import qualified Cardano.Wallet.Deposit.Pure.UTxO.UTxOHistory as UTxOHistory -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Data.Delta as Delta -import qualified Data.List as L -import qualified Data.Map.Strict as Map - -type Customer = Address.Customer - -data WalletState = WalletState - { walletTip :: Read.ChainPoint - -- ^ The wallet includes information from all blocks until - -- and including this one. - , addresses :: Address.AddressState - -- ^ Addresses and public keys known to this wallet. - , utxoHistory :: UTxOHistory.UTxOHistory - -- ^ UTxO of this wallet, with support for rollbacks. - , txHistory :: TxHistory - -- ^ (Summarized) transaction history of this wallet. - , submissions :: Sbm.TxSubmissions - -- ^ Queue of pending transactions. - , rootXSignKey :: Maybe XPrv - -- ^ Maybe a private key for signing transactions. - -- , info :: WalletInfo - } - -type DeltaWalletState = Delta.Replace WalletState - -listCustomers :: WalletState -> [(Customer, Address)] -listCustomers = - Address.listCustomers . addresses - -customerAddress :: Customer -> WalletState -> Maybe Address -customerAddress c = L.lookup c . listCustomers - -addressToCustomer :: Address -> WalletState -> Maybe Customer -addressToCustomer address = - Map.lookup address - . Map.fromList - . fmap (\(a, c) -> (c, a)) - . listCustomers - --- depend on the public key only, not on the entire wallet state -deriveAddress :: WalletState -> (Customer -> Address) -deriveAddress w = - Address.deriveCustomerAddress - (Address.getNetworkTag as) - (Address.getXPub as) - where - as = addresses w - --- FIXME: More performant with a double index. -knownCustomer :: Customer -> WalletState -> Bool -knownCustomer c = (c `elem`) . map fst . listCustomers - -knownCustomerAddress :: Address -> WalletState -> Bool -knownCustomerAddress address = - Address.knownCustomerAddress address . addresses - -isCustomerAddress :: Address -> WalletState -> Bool -isCustomerAddress address = - flip Address.isCustomerAddress address . addresses - -fromRawCustomer :: Word31 -> Customer -fromRawCustomer = id - --- | Maximum 'Customer' that is being tracked. -trackedCustomers :: WalletState -> Customer -trackedCustomers = (+1) . Address.getMaxCustomer . addresses - -walletXPub :: WalletState -> XPub -walletXPub = Address.getXPub . addresses - -getUTxO :: WalletState -> UTxO.UTxO -getUTxO = UTxOHistory.getUTxO . utxoHistory - -getWalletTip :: WalletState -> Read.ChainPoint -getWalletTip = walletTip - -networkTag :: WalletState -> NetworkTag -networkTag = Address.getNetworkTag . addresses diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/Submissions.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/Submissions.hs deleted file mode 100644 index 8d05a68e832..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/Submissions.hs +++ /dev/null @@ -1,105 +0,0 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE StrictData #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeFamilies #-} -{-# OPTIONS_GHC -Wno-orphans #-} -module Cardano.Wallet.Deposit.Pure.Submissions - ( TxSubmissions - , TxSubmissionsStatus - , DeltaTxSubmissions1 - , DeltaTxSubmissions - - , empty - , add - , listInSubmission - , rollForward - , rollBackward - ) where - -import Prelude - -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Cardano.Wallet.Submissions.Operations as Sbm -import qualified Cardano.Wallet.Submissions.Submissions as Sbm -import qualified Cardano.Wallet.Submissions.TxStatus as Sbm -import qualified Data.Delta as Delta -import qualified Data.Map.Strict as Map - -{----------------------------------------------------------------------------- - Types -------------------------------------------------------------------------------} -type ExpirySlot = WithInfinity Read.Slot - -type TxSubmissions - = Sbm.Submissions () ExpirySlot (Read.TxId, Write.Tx) -type TxSubmissionsStatus - = Sbm.TxStatusMeta () ExpirySlot(Read.TxId, Write.Tx) -type DeltaTxSubmissions1 - = Sbm.Operation () ExpirySlot (Read.TxId, Write.Tx) -type DeltaTxSubmissions - = [DeltaTxSubmissions1] - -instance Delta.Delta DeltaTxSubmissions1 where - type Base DeltaTxSubmissions1 = TxSubmissions - apply = Sbm.applyOperations - -instance Sbm.HasTxId (Read.TxId, Write.Tx) where - type TxId (Read.TxId, Write.Tx) = Read.TxId - txId = fst - --- | Data type used for tracking transactions --- that will never become invalid. -data WithInfinity a - = Finite a - | Infinity - deriving (Eq, Show) - -infinityFromNothing :: Maybe a -> WithInfinity a -infinityFromNothing Nothing = Infinity -infinityFromNothing (Just x) = Finite x - -instance Ord a => Ord (WithInfinity a) where - compare (Finite x) (Finite y) = compare x y - compare Infinity (Finite _) = GT - compare (Finite _) Infinity = LT - compare Infinity Infinity = EQ - -instance Functor WithInfinity where - fmap f (Finite x) = Finite (f x) - fmap _ Infinity = Infinity - -{----------------------------------------------------------------------------- - Operations -------------------------------------------------------------------------------} --- | Empty collection of transaction in submission. -empty :: TxSubmissions -empty = Sbm.mkEmpty (Finite Read.Origin) - --- | Add a /new/ transaction to the local submission pool. -add :: Write.Tx -> DeltaTxSubmissions -add tx = [ Sbm.AddSubmission expiry (txId, tx) () ] - where - txId = Read.getTxId tx - expiry = - fmap Read.At - . infinityFromNothing - . Read.invalidHereafter - $ Read.getValidityInterval tx - --- | List of transactions that are in submission, in no particular order. -listInSubmission :: TxSubmissions -> [Write.Tx] -listInSubmission submissions = do - Sbm.InSubmission _ (_, tx) <- Map.elems (Sbm.transactions submissions) - pure tx - --- | Rollforward the transactions that are in submission -rollForward :: Read.IsEra era => Read.Block era -> DeltaTxSubmissions -rollForward block = [ Sbm.RollForward slot txids ] - where - slot = Finite $ Read.slotFromChainPoint $ Read.getChainPoint block - txids = map ((slot,) . Read.getTxId) $ Read.getEraTransactions block - --- | Roll backward the transactions that are in submission -rollBackward :: Read.Slot -> DeltaTxSubmissions -rollBackward slot = [ Sbm.RollBack (Finite slot) ] diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/UTxO.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/UTxO.hs deleted file mode 100644 index 494643d9ce9..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Pure/UTxO.hs +++ /dev/null @@ -1,30 +0,0 @@ -module Cardano.Wallet.Deposit.Pure.UTxO - ( UTxO - , balance - , excluding - , restrictedBy - , filterByAddress - , toList - - , DeltaUTxO - , excludingD - , receiveD - , null - ) where - -import Cardano.Wallet.Deposit.Pure.UTxO.DeltaUTxO - ( DeltaUTxO - , excludingD - , null - , receiveD - ) -import Cardano.Wallet.Deposit.Pure.UTxO.UTxO - ( UTxO - , balance - , excluding - , filterByAddress - , restrictedBy - ) -import Data.Map.Strict - ( toList - ) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Read.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Read.hs deleted file mode 100644 index 25463a01fcd..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Read.hs +++ /dev/null @@ -1,137 +0,0 @@ -{-# LANGUAGE BinaryLiterals #-} -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE NamedFieldPuns #-} - --- | Indirection module that re-exports types --- used for reading data from the blockchain, --- from all eras. --- --- TODO: Match this up with the @Read@ hierarchy. -module Cardano.Wallet.Deposit.Read - ( Read.IsEra (..) - , Read.Era (..) - , Read.EraValue (..) - , Read.Conway - , Read.getEra - - , Read.SlotNo (..) - , Read.ChainPoint (..) - , Read.Slot - , Read.WithOrigin (..) - , Read.slotFromChainPoint - - , Address - , KeyHash - , NetworkTag (..) - , mkEnterpriseAddress - , Addr - , compactAddr - , decompactAddr - - , Ix - , Read.TxIn - , Read.TxOut - , address - , Read.Value - , Read.lessOrEqual - , UTxO - - , Read.TxId - , Read.Tx (..) - , Read.utxoFromEraTx - , Read.getCollateralInputs - , Read.getInputs - , Read.getValidityInterval - , Read.getTxId - , Read.invalidHereafter - - , Read.Block - , Read.getChainPoint - , Read.getEraBHeader - , Read.getEraSlotNo - , Read.getEraTransactions - , mockNextBlock - , Read.mockRawHeaderHash - - , Read.ChainTip (..) - , Read.getChainTip - , Read.prettyChainTip - - , Read.PParams (..) - , Read.mockPParamsConway - - , Read.GenesisData - , Read.GenesisHash - , Read.mockGenesisDataMainnet - - , Read.NetworkId (Read.Mainnet, Read.Testnet) - , Read.getNetworkId - , getEraSlotOfBlock - ) where - -import Prelude - -import Cardano.Ledger.Address - ( Addr - , compactAddr - , decompactAddr - ) -import Cardano.Wallet.Address.Encoding - ( Credential (..) - , EnterpriseAddr (..) - , KeyHash - , NetworkTag (..) - , compactAddrFromEnterpriseAddr - ) -import Cardano.Wallet.Read.Block.Gen - ( mkBlockEra - ) -import Cardano.Wallet.Read.Block.Gen.BlockParameters - ( BlockParameters (..) - ) -import Data.Map - ( Map - ) - -import qualified Cardano.Wallet.Read as Read - -{----------------------------------------------------------------------------- - Type definitions - with dummies -------------------------------------------------------------------------------} - --- | Synonym for readability. --- The ledger specifications define @Addr@. --- Byron addresses are represented by @Addr_bootstrap@. -type Address = Read.CompactAddr - --- | Make an enterprise address from a given network and key hash. -mkEnterpriseAddress :: NetworkTag -> KeyHash -> Address -mkEnterpriseAddress network = - compactAddrFromEnterpriseAddr - . EnterpriseAddrC network - . KeyHashObj - -type Ix = Read.TxIx - -address :: Read.TxOut -> Address -address = Read.getCompactAddr - -type UTxO = Map Read.TxIn Read.TxOut - -{----------------------------------------------------------------------------- - Block -------------------------------------------------------------------------------} --- | Create a new block from a sequence of transaction. -mockNextBlock - :: Read.ChainPoint -> [Read.Tx Read.Conway] -> Read.Block Read.Conway -mockNextBlock old txs = - mkBlockEra BlockParameters{slotNumber,blockNumber,txs} - where - blockNumber = Read.BlockNo $ Read.unSlotNo slotNumber - slotNumber = case old of - Read.GenesisPoint -> Read.SlotNo 0 - Read.BlockPoint{slotNo = n} -> succ n - -getEraSlotOfBlock :: Read.IsEra era => Read.Block era -> Read.Slot -getEraSlotOfBlock = Read.At . Read.getEraSlotNo . Read.getEraBHeader diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL.hs deleted file mode 100644 index 6b8373f1d89..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL.hs +++ /dev/null @@ -1,422 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ScopedTypeVariables #-} - -module Cardano.Wallet.Deposit.Testing.DSL - ( Scenario (..) - , ScenarioP - , existsTx - , deposit - , deposit_ - , withdrawal - , block - , rollForward - , rollBackward - , historyByTime - , historyByCustomer - , newHistoryByTime - , availableBalance - , assert - , interpret - , InterpreterState (..) - , spend - , sign - , utxo - , wallet - , balance - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.Pure - ( Customer - , ResolvedTx (..) - , WalletState - , getTxHistoryByTime - ) -import Cardano.Wallet.Deposit.Pure.API.TxHistory - ( ByCustomer - , ByTime - , LookupTimeFromSlot - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( createMnemonicFromWords - , credentialsFromMnemonics - ) -import Cardano.Wallet.Deposit.Pure.State.Payment - ( createPaymentTxBody - ) -import Cardano.Wallet.Deposit.Read - ( Address - , ChainPoint (..) - , EraValue (..) - , UTxO - , getChainPoint - , mockNextBlock - , slotFromChainPoint - ) -import Cardano.Wallet.Deposit.Testing.DSL.ByTime - ( ByTimeM - , ByTimeMContext (..) - ) -import Cardano.Wallet.Deposit.Testing.DSL.Types - ( BlockI (..) - , TxI (..) - , UnspentI (..) - ) -import Cardano.Wallet.Deposit.Write - ( Block - , Tx - , TxBody - , addTxIn - , addTxOut - , emptyTxBody - , mkAda - , mkTx - , mkTxOut - , txOutsL - ) -import qualified Cardano.Wallet.Deposit.Write as Write -import Cardano.Wallet.Read - ( Coin (..) - , Slot - , Value (..) - , WithOrigin - , getTxId - , pattern TxIn - ) -import Control.Lens - ( At (..) - , Field1 (_1) - , Field2 (_2) - , Ixed (..) - , Lens' - , lens - , use - , uses - , zoom - , (%=) - , (.=) - , (^?) - ) -import Control.Monad - ( void - , (>=>) - ) -import Control.Monad.Operational - ( ProgramT - , ProgramViewT (..) - , singleton - , viewT - ) -import Control.Monad.Reader - ( MonadIO (..) - , runReader - ) -import Control.Monad.State - ( MonadState (..) - , MonadTrans (..) - , StateT - , evalStateT - , execStateT - , modify - ) -import Data.List - ( mapAccumL - ) -import Data.Map - ( Map - ) -import Data.Maybe - ( fromJust - ) -import Data.Text - ( Text - ) -import Data.Time - ( UTCTime - ) - -import qualified Cardano.Wallet.Deposit.Pure as Wallet -import qualified Cardano.Wallet.Deposit.Time as Time -import qualified Cardano.Wallet.Read as Read -import qualified Data.List.NonEmpty as NE -import qualified Data.Map as Map -import qualified Data.Set as Set - -data Scenario p a where - ResetWallet :: Int -> Text -> Text -> Scenario p () - ExistsTx :: Scenario p TxI - Deposit :: TxI -> Customer -> Int -> Scenario p UnspentI - Spend :: TxI -> Address -> Int -> Scenario p () - Withdrawal :: TxI -> UnspentI -> Scenario p () - CreateBlock :: [TxI] -> Scenario p (BlockI) - RollForward :: [BlockI] -> Scenario p () - RollBackward :: Maybe BlockI -> Scenario p () - HistoryByTime :: Scenario p ByTime - HistoryByCustomer :: Scenario p ByCustomer - NewHistoryByTime :: ByTimeM ByTime -> Scenario p ByTime - AvailableBalance :: Scenario p Int - Sign :: Tx -> Text -> Scenario p Tx - Balance :: TxI -> Scenario p Tx - UTxO :: UnspentI -> Scenario p UTxO - Assert :: p -> Scenario p () - -type ScenarioP p m = ProgramT (Scenario p) m - -wallet :: Int -> Text -> Text -> ScenarioP p m () -wallet customers seed passphrase = - singleton (ResetWallet customers seed passphrase) - -existsTx :: ScenarioP p m TxI -existsTx = singleton ExistsTx - -deposit :: TxI -> Customer -> Int -> ScenarioP p m UnspentI -deposit tx customer value = singleton (Deposit tx customer value) - -deposit_ :: Monad m => TxI -> Customer -> Int -> ScenarioP p m () -deposit_ tx customer value = void $ deposit tx customer value - -spend :: TxI -> Address -> Int -> ScenarioP p m () -spend tx addr value = singleton (Spend tx addr value) - -withdrawal :: TxI -> UnspentI -> ScenarioP p m () -withdrawal tx unspent = singleton (Withdrawal tx unspent) - -block :: [TxI] -> ScenarioP p m BlockI -block txs = singleton (CreateBlock txs) - -rollForward :: [BlockI] -> ScenarioP p m () -rollForward blocks = singleton (RollForward blocks) - -rollBackward :: Maybe BlockI -> ScenarioP p m () -rollBackward slot = singleton (RollBackward slot) - -historyByTime :: ScenarioP p m ByTime -historyByTime = singleton HistoryByTime - -historyByCustomer :: ScenarioP p m ByCustomer -historyByCustomer = singleton HistoryByCustomer - -newHistoryByTime :: ByTimeM ByTime -> ScenarioP p m ByTime -newHistoryByTime = singleton . NewHistoryByTime - -availableBalance :: ScenarioP p m Int -availableBalance = singleton AvailableBalance - -sign :: Tx -> Text -> ScenarioP p m Write.Tx -sign tx pass = singleton (Sign tx pass) - -balance :: TxI -> ScenarioP p m Tx -balance tx = singleton (Balance tx) - -utxo :: UnspentI -> ScenarioP p m UTxO -utxo = singleton . UTxO - -assert :: p -> ScenarioP p m () -assert = singleton . Assert - -rollForwardBlocks - :: LookupTimeFromSlot - -> [BlockI] - -> (WalletState, InterpreterState) - -> (WalletState, InterpreterState) -rollForwardBlocks timeOf blocks (w, interpreter@InterpreterState{..}) = - ( w' - , interpreter{iBlocks = newIBlocks, iBlockPoints = newIBlockPoints} - ) - where - w' = Wallet.rollForwardMany timeOf (NE.fromList blocks') w - ((newIBlocks, newIBlockPoints), blocks') = - mapAccumL - rollForwardBlock - (iBlocks, iBlockPoints) - blocks - rollForwardBlock (iBlocksCurrent, iBlockPointsCurrent) blockI = - ( - ( Map.insert blockPoint newBlock iBlocksCurrent - , Map.insert blockI blockPoint iBlockPointsCurrent - ) - , EraValue newBlock - ) - where - txs = iBlockContents Map.! blockI - newBlock = mockNextBlock startPoint txs - blockPoint = getChainPoint newBlock - startPoint = - maybe GenesisPoint fst - $ Map.lookupMax iBlocksCurrent - -rollBackwardBlock - :: LookupTimeFromSlot - -> Maybe BlockI - -> (WalletState, InterpreterState) - -> (WalletState, InterpreterState) -rollBackwardBlock timeOf Nothing (w, interpreter) = - ( fst $ Wallet.rollBackward timeOf GenesisPoint w - , interpreter{iBlocks = mempty, iBlockPoints = mempty} - ) -rollBackwardBlock timeOf (Just blockI) (w, interpreter@InterpreterState{..}) = - case Map.lookup blockI iBlockPoints of - Just keep -> - ( w' - , interpreter{iBlocks = newIBlocks, iBlockPoints = newIBlockPoints} - ) - where - w' = fst $ Wallet.rollBackward timeOf keep w - newIBlocks = Map.takeWhileAntitone (<= keep) iBlocks - newIBlockPoints = Map.filter (<= keep) iBlockPoints - Nothing -> (w, interpreter) - -data InterpreterState = InterpreterState - { iTxs :: Map TxI TxBody - , iBlockContents :: Map BlockI [Tx] - , iBlockPoints :: Map BlockI ChainPoint - , iBlocks :: Map ChainPoint Block - } - deriving (Show) - -iTxsL :: Lens' InterpreterState (Map TxI TxBody) -iTxsL = lens iTxs (\s x -> s{iTxs = x}) - -iBlockContentsL :: Lens' InterpreterState (Map BlockI [Tx]) -iBlockContentsL = lens iBlockContents (\s x -> s{iBlockContents = x}) - -iBlockPointsL :: Lens' InterpreterState (Map BlockI ChainPoint) -iBlockPointsL = lens iBlockPoints (\s x -> s{iBlockPoints = x}) - -newTxId :: Monad m => StateT InterpreterState m TxI -newTxId = zoom iTxsL $ do - txs <- get - let z = maybe 0 fst $ Map.lookupMax txs - txId = z + 1 - put $ Map.insert txId emptyTxBody txs - return txId - -newBlockId :: Monad m => StateT InterpreterState m BlockI -newBlockId = zoom iBlockContentsL $ do - blocks <- get - let z = maybe 0 fst $ Map.lookupMax blocks - blockId = z + 1 - put $ Map.insert blockId [] blocks - return blockId - -freshInterpreterState :: InterpreterState -freshInterpreterState = InterpreterState mempty mempty mempty mempty - -unsafeCustomerAddress - :: Wallet.WalletState -> Customer -> Write.Address -unsafeCustomerAddress w = fromJust . flip Wallet.customerAddress w - -interpret - :: (MonadIO m, MonadFail m) - => WalletState - -> (p -> m ()) - -> (Slot -> WithOrigin UTCTime) - -> ScenarioP - p - (StateT (WalletState, InterpreterState) m) - () - -> m () -interpret w runP slotTimes p = flip evalStateT w $ do - walletState <- get - (walletState', _) <- - lift - $ execStateT - (go p) - (walletState, freshInterpreterState) - put walletState' - where - go = viewT >=> eval - eval (ResetWallet customers seed passphrase :>>= k) = do - Right mnemonics <- pure $ createMnemonicFromWords seed - let new = - Wallet.fromCredentialsAndGenesis - (credentialsFromMnemonics mnemonics passphrase) - (fromIntegral customers) - Read.mockGenesisDataMainnet - id .= (new, freshInterpreterState) - go $ k () - eval (Return x) = return x - eval (ExistsTx :>>= k) = do - txId <- zoom _2 newTxId - go $ k txId - eval (Deposit tx customer value :>>= k) = do - customerAddresses <- uses _1 unsafeCustomerAddress - let v = mkAda $ fromIntegral value - txOut = mkTxOut (customerAddresses customer) v - Just txBody <- use (_2 . iTxsL . at tx) - let (txBody', tix) = addTxOut txOut txBody - _2 . iTxsL . ix tx .= txBody' - go $ k $ UnspentI (tx, tix) - eval (Withdrawal tx (UnspentI (tx', tix)) :>>= k) = do - Just txId <- uses (_2 . iTxsL . at tx') $ fmap (getTxId . mkTx) - _2 . iTxsL . ix tx %= \txBody -> addTxIn (TxIn txId tix) txBody - go $ k () - eval (Spend tx address value :>>= k) = do - Just txBody <- use (_2 . iTxsL . at tx) - let (txBody', _tix) = - addTxOut (mkTxOut address (mkAda $ fromIntegral value)) txBody - _2 . iTxsL . ix tx .= txBody' - go $ k () - eval (CreateBlock txs :>>= k) = do - blockId <- zoom _2 newBlockId - send <- - uses (_2 . iTxsL) - $ flip Map.restrictKeys - $ Set.fromList txs - _2 . iBlockContentsL . ix blockId .= (mkTx <$> Map.elems send) - go $ k blockId - eval (RollForward blocks :>>= k) = do - modify $ rollForwardBlocks (fmap Just slotTimes) blocks - go $ k () - eval (RollBackward blockKeep :>>= k) = do - modify $ rollBackwardBlock (fmap Just slotTimes) blockKeep - go $ k () - eval (HistoryByTime :>>= k) = do - v <- uses _1 getTxHistoryByTime - go $ k v - eval (HistoryByCustomer :>>= k) = do - v <- uses _1 Wallet.getTxHistoryByCustomer - go $ k v - eval (NewHistoryByTime m :>>= k) = do - customerAddresses <- uses _1 unsafeCustomerAddress - txIds' <- uses (_2 . iTxsL) $ (Map.!) . fmap (getTxId . mkTx) - blockSlots <- - uses (_2 . iBlockPointsL) $ (Map.!) . fmap slotFromChainPoint - go - $ k - $ runReader m - $ ByTimeMContext txIds' customerAddresses slotTimes blockSlots - eval (AvailableBalance :>>= k) = do - ValueC (CoinC v) _ <- uses _1 Wallet.availableBalance - go $ k $ fromIntegral v - eval (Sign tx pass :>>= k) = do - Just stx <- uses _1 $ Wallet.signTx tx pass - go $ k stx - eval (Balance tx :>>= k) = do - Just txBody <- use (_2 . iTxsL . at tx) - ws <- use _1 - let etx = - createPaymentTxBody - (Read.EraValue Read.mockPParamsConway) - (Time.toTimeTranslationPure Time.mockTimeInterpreter) - txBody - ws - ResolvedTx btx _ <- case etx of - Left e -> fail $ "createPaymentTxBody failed: " <> show e - Right tx' -> return tx' - go $ k btx - eval (UTxO (UnspentI (tx, tix)) :>>= k) = do - Just txBody <- use (_2 . iTxsL . at tx) - let txId = getTxId $ mkTx txBody - Just txOut <- pure $ txBody ^? txOutsL . ix tix - go $ k $ Map.singleton (TxIn txId tix) txOut - eval (Assert assertion :>>= k) = do - lift $ runP assertion - go $ k () diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL/ByTime.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL/ByTime.hs deleted file mode 100644 index f77db89bbcf..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL/ByTime.hs +++ /dev/null @@ -1,244 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} - -{-# HLINT ignore "Use let" #-} - -module Cardano.Wallet.Deposit.Testing.DSL.ByTime - ( -- * ByTime - ByTimeM - , ByTimeMContext (..) - , ByTime - - -- * At time - , atBlock - , atSlot - , newByTime - - -- * For customer - , forCustomer - - -- * In tx - , inTx - - -- * Value transfer - , deposited - , withdrawn - , byCustomerFromByTime - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.Map - ( F - , Map (..) - , W - , toFinger - ) -import Cardano.Wallet.Deposit.Map.Timed - ( Timed (..) - ) -import Cardano.Wallet.Deposit.Pure - ( Customer - , ValueTransfer (received, spent) - ) -import Cardano.Wallet.Deposit.Pure.API.TxHistory - ( ByCustomer - , ByTime - , DownTime - , firstJust - ) -import Cardano.Wallet.Deposit.Read - ( Address - ) -import Cardano.Wallet.Deposit.Testing.DSL.Types - ( BlockI - , TxI - ) -import Cardano.Wallet.Deposit.Write - ( mkAda - ) -import Cardano.Wallet.Read - ( Slot - , TxId - , WithOrigin (..) - ) -import Control.Monad.Reader - ( Reader - , asks - ) -import Control.Monad.State - ( State - , StateT - , execState - , execStateT - , modify' - ) -import Control.Monad.Trans - ( MonadTrans (..) - ) -import Data.Foldable - ( Foldable (..) - ) -import Data.Map.Monoidal.Strict - ( MonoidalMap - ) -import Data.Monoid - ( First - ) -import Data.Ord - ( Down (..) - ) -import Data.Time - ( UTCTime - ) - -import qualified Cardano.Wallet.Deposit.Map.Timed as TimedSeq -import qualified Cardano.Wallet.Deposit.Map.Timed as TimeSeq -import qualified Data.Map.Monoidal.Strict as MonoidalMap - -byCustomerFromByTime :: ByTime -> ByCustomer -byCustomerFromByTime (Finger () xs) = Map () xs' - where - xs' - :: MonoidalMap - Customer - (Map '[F (First Address) DownTime, W (First Slot) TxId] ValueTransfer) - xs' = fold $ do - Timed t (Map slot ys) <- TimeSeq.toList xs - (customer, Map addr kv) <- MonoidalMap.toList ys - pure - $ MonoidalMap.singleton customer - $ Finger addr - $ TimedSeq.singleton (Timed t $ Map slot kv) - --- ------------------------------------------------------------------------------- --- -- AtTime --- ------------------------------------------------------------------------------- - -data ByTimeMContext = ByTimeMContext - { txIdOfTxI :: TxI -> TxId - , addrOfCustomer :: Customer -> Address - , timeOfSlot :: Slot -> WithOrigin UTCTime - , slotOfBlock :: BlockI -> Slot - } - -type ByTimeM = Reader ByTimeMContext - -atBlock - :: BlockI - -> StateT - (MonoidalMap Customer (Map '[W (First Address) TxId] ValueTransfer)) - ByTimeM - () - -> StateT - ( MonoidalMap - DownTime - (Map '[W (First Slot) Customer, W (First Address) TxId] ValueTransfer) - ) - ByTimeM - () -atBlock b v = do - slotOf <- asks slotOfBlock - atSlot (slotOf b) v - -atSlot - :: Slot - -> StateT - (MonoidalMap Customer (Map '[W (First Address) TxId] ValueTransfer)) - ByTimeM - () - -> StateT - ( MonoidalMap - DownTime - (Map '[W (First Slot) Customer, W (First Address) TxId] ValueTransfer) - ) - ByTimeM - () -atSlot t v = do - timeOf <- asks timeOfSlot - txs <- lift $ newCustomers t v - modify' $ MonoidalMap.insert (Down $ timeOf t) txs - -newByTime - :: StateT - ( MonoidalMap - DownTime - (Map '[W (First Slot) Customer, W (First Address) TxId] ValueTransfer) - ) - ByTimeM - () - -> ByTimeM ByTime -newByTime v = toFinger . Map () <$> execStateT v mempty - --- ------------------------------------------------------------------------------- --- -- Customer --- ------------------------------------------------------------------------------- - -forCustomer - :: Customer - -> StateT - (MonoidalMap TxId (Map '[] ValueTransfer)) - ByTimeM - () - -> StateT - (MonoidalMap Customer (Map '[W (First Address) TxId] ValueTransfer)) - ByTimeM - () -forCustomer c v = do - addrOf <- asks addrOfCustomer - txs <- lift $ newTxIds (addrOf c) v - modify' $ MonoidalMap.insert c txs - -newCustomers - :: Slot - -> StateT - (MonoidalMap Customer (Map '[W (First Address) TxId] ValueTransfer)) - ByTimeM - () - -> ByTimeM - (Map '[W (First Slot) Customer, W (First Address) TxId] ValueTransfer) -newCustomers slot v = Map (firstJust slot) <$> execStateT v mempty - -------------------------------------------------------------------------------- --- Tx -------------------------------------------------------------------------------- - -inTx - :: TxI - -> State ValueTransfer () - -> StateT - (MonoidalMap TxId (Map '[] ValueTransfer)) - ByTimeM - () -inTx tx v = do - w <- pure $ newValueTransferP v - txIdOf <- asks txIdOfTxI - modify' $ MonoidalMap.insert (txIdOf tx) w - -newTxIds - :: Address - -> StateT - (MonoidalMap TxId (Map '[] ValueTransfer)) - ByTimeM - () - -> ByTimeM (Map '[W (First Address) TxId] ValueTransfer) -newTxIds addr v = Map (firstJust addr) <$> execStateT v mempty - -------------------------------------------------------------------------------- --- Value transfer -------------------------------------------------------------------------------- - -deposited :: Int -> State ValueTransfer () -deposited n = modify' $ \s -> s{received = mkAda $ fromIntegral n} - -withdrawn :: Int -> State ValueTransfer () -withdrawn n = modify' $ \s -> s{spent = mkAda $ fromIntegral n} - -newValueTransferP - :: State ValueTransfer () - -> Map '[] ValueTransfer -newValueTransferP v = Value $ execState v mempty diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL/Types.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL/Types.hs deleted file mode 100644 index 5ea04bbe213..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Testing/DSL/Types.hs +++ /dev/null @@ -1,25 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE ScopedTypeVariables #-} - -module Cardano.Wallet.Deposit.Testing.DSL.Types where - -import Prelude - -import Cardano.Wallet.Deposit.Read - ( Ix - ) - -newtype TxI = TxI Int - deriving (Eq, Ord, Show, Num) - -newtype UnspentI = UnspentI (TxI, Ix) - deriving (Eq, Ord, Show) - -newtype BlockI = BlockI Int - deriving (Eq, Ord, Show, Num) - -newtype TimeI = TimeI Int - deriving (Eq, Ord, Show) diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Time.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Time.hs deleted file mode 100644 index b47596e6b2b..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Time.hs +++ /dev/null @@ -1,148 +0,0 @@ -{-# LANGUAGE NumericUnderscores #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Indirection module that re-exports types --- related to computations involving Slots and wall-clock times. --- --- TODO: Absorb this into a definition of 'TimeInterpreter'. -module Cardano.Wallet.Deposit.Time - ( -- * from Primitive - TimeInterpreter - , PastHorizonException - , mockTimeInterpreter - - , slotToUTCTime - - -- * from Write - , Write.TimeTranslation - , toTimeTranslation - , toTimeTranslationPure - - -- * wishlist - , LookupTimeFromSlot - , unsafeUTCTimeOfSlot - , unsafeSlotOfUTCTime - , systemStartMainnet - , originTime - - ) where - -import Prelude - -import Cardano.Wallet.Primitive.Slotting - ( PastHorizonException - , StartTime (..) - , hoistTimeInterpreter - , interpretQuery - , mkSingleEraInterpreter - ) -import Cardano.Wallet.Primitive.Slotting.TimeTranslation - ( toTimeTranslation - , toTimeTranslationPure - ) -import Cardano.Wallet.Primitive.Types.SlottingParameters - ( ActiveSlotCoefficient (..) - , EpochLength (..) - , SlotLength (..) - , SlottingParameters (..) - ) -import Cardano.Wallet.Read - ( Slot - , SlotNo (..) - , WithOrigin (..) - ) -import Data.Functor.Identity - ( Identity (..) - ) -import Data.IntCast - ( intCastMaybe - ) -import Data.Quantity - ( Quantity (..) - ) -import Data.Time.Clock - ( UTCTime (..) - ) -import Data.Time.Clock.POSIX - ( posixSecondsToUTCTime - , utcTimeToPOSIXSeconds - ) - -import qualified Cardano.Wallet.Primitive.Slotting as Primitive -import qualified Cardano.Wallet.Read as Read -import qualified Cardano.Write.Tx as Write - -{----------------------------------------------------------------------------- - TimeInterpreter -------------------------------------------------------------------------------} -type TimeInterpreter = Primitive.TimeInterpreter (Either PastHorizonException) - -mockTimeInterpreter :: Primitive.TimeInterpreter Identity -mockTimeInterpreter = hoistTimeInterpreter (pure . runIdentity) $ - mkSingleEraInterpreter - (StartTime $ UTCTime (toEnum 0) 0) - mockSlottingParameters - -mockSlottingParameters :: SlottingParameters -mockSlottingParameters = SlottingParameters - { getSlotLength = SlotLength 1 - , getEpochLength = EpochLength 21_600 - , getActiveSlotCoefficient = ActiveSlotCoefficient 1 - , getSecurityParameter = Quantity 2_160 - } - -{----------------------------------------------------------------------------- - TimeInterpreter -------------------------------------------------------------------------------} - -type LookupTimeFromSlot = Slot -> Maybe (WithOrigin UTCTime) - --- | Look up the UTCTime corresponding to the start of the provided `Slot`. --- --- TODO: Check roundtrip properties once we need to implement the corresponding 'utcTimeToSlot'. -slotToUTCTime :: TimeInterpreter -> LookupTimeFromSlot -slotToUTCTime _ti Origin = Just Origin -slotToUTCTime ti (At s) = either (const Nothing) (Just . At) . interpretQuery ti . Primitive.slotToUTCTime =<< convertSlotNo s - where - convertSlotNo :: SlotNo -> Maybe Primitive.SlotNo - convertSlotNo (SlotNo n) = Primitive.SlotNo <$> intCastMaybe n - --- TODO: Rename to mainnetUTCTimeOfSlot --- TODO: Move to tests? -unsafeUTCTimeOfSlot :: Slot -> Maybe (WithOrigin UTCTime) -unsafeUTCTimeOfSlot Origin = Just Origin -unsafeUTCTimeOfSlot (At (SlotNo n)) = - Just . At - $ posixSecondsToUTCTime - $ fromIntegral pt - where - pts = fromIntegral n - byronSlots - pt = - if pts >= 0 - then shelleyTime + pts - else shelleyTime + pts * 20 - -unsafeSlotOfUTCTime :: UTCTime -> Read.Slot -unsafeSlotOfUTCTime t - | origin = Origin - | byron = At $ SlotNo $ fromIntegral $ (pt - originTime) `div` 20 - | otherwise = At $ SlotNo $ fromIntegral $ pt - shelleyTime + byronSlots - where - pt = floor $ utcTimeToPOSIXSeconds t - origin = pt < originTime - byron = pt < shelleyTime - -byronSlots :: Integer -byronSlots = 4_924_800 - -shelleyTime :: Integer -shelleyTime = 1_596_491_091 - -originTime :: Integer -originTime = shelleyTime - byronSlots * 20 - -systemStartMainnet :: UTCTime -systemStartMainnet = posixSecondsToUTCTime $ fromIntegral originTime diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Write.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Write.hs deleted file mode 100644 index 9d9784b3bf3..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Write.hs +++ /dev/null @@ -1,222 +0,0 @@ -{-# LANGUAGE DataKinds #-} - --- | Indirection module that re-exports types --- used for writing transactions to the blockchain, --- in the most recent and the next future eras. --- --- TODO: Match this up with the @Write@ hierarchy. -module Cardano.Wallet.Deposit.Write - ( -- * Basic types - Address - , Value - , TxId - , Tx - , Block - , mkTx - , TxBody (..) - , TxIn - , TxOut - , Coin - - -- * Transaction balancing - , Write.IsRecentEra - , Write.Conway - , L.PParams - , Write.UTxOAssumptions (..) - , Write.ChangeAddressGen (..) - , Write.StakeKeyDepositLookup (..) - , Write.TimelockKeyWitnessCounts (..) - , Write.UTxOIndex - , Write.constructUTxOIndex - , Write.UTxO - , toConwayUTxO - , Write.PartialTx (..) - , Write.ErrBalanceTx (..) - , Write.ErrBalanceTxAssetsInsufficientError (..) - , Write.ErrBalanceTxInsufficientCollateralError (..) - , Write.ErrBalanceTxInternalError (..) - , Write.ErrBalanceTxOutputError (..) - , Write.ErrBalanceTxOutputErrorInfo (..) - , Write.ErrBalanceTxUnableToCreateChangeError (..) - , Write.ErrAssignRedeemers (..) - , Write.balanceTx - - -- * Signing - , addSignature - - -- ** Time interpreter - , Write.TimeTranslation - - -- * Helper functions - , mkAda - , mkTxOut - , txOutsL - , toConwayTx - , addTxIn - , addTxOut - , emptyTxBody - , UTxO.resolvedTx - , UTxO.resolvedInputs - ) where - -import Prelude - -import Cardano.Ledger.Coin - ( Coin - ) -import Cardano.Read.Ledger.Tx.Output - ( Output (..) - ) -import Cardano.Wallet.Address.BIP32_Ed25519 - ( XPrv - , sign - , toXPub - ) -import Cardano.Wallet.Deposit.Read - ( Address - , Ix - , SlotNo (..) - , TxId - , TxIn - , TxOut - , Value - ) -import Cardano.Wallet.Deposit.Write.Keys - ( signedDSIGNfromXSignature - , vkeyFromXPub - ) -import Cardano.Wallet.Read.Tx - ( toConwayOutput - ) -import Control.Lens - ( Lens' - , lens - , (%~) - , (&) - , (.~) - ) -import Data.Map - ( Map - ) -import Data.Maybe.Strict - ( StrictMaybe (..) - , maybeToStrictMaybe - ) -import Data.Sequence.Strict - ( StrictSeq - , fromList - ) -import Data.Set - ( Set - ) - -import qualified Cardano.Ledger.Api as L -import qualified Cardano.Ledger.Api.Tx.In as L -import qualified Cardano.Ledger.Slot as L -import qualified Cardano.Wallet.Deposit.Pure.UTxO.Tx as UTxO -import qualified Cardano.Wallet.Read as Read -import qualified Cardano.Wallet.Read.Hash as Hash -import qualified Cardano.Write.Eras as Write -import qualified Cardano.Write.Tx as Write -import qualified Data.Map.Strict as Map -import qualified Data.Set as Set - -{----------------------------------------------------------------------------- - Types -------------------------------------------------------------------------------} -type Tx = Read.Tx Read.Conway - -type Block = Read.Block Read.Conway - -{----------------------------------------------------------------------------- - Signing -------------------------------------------------------------------------------} --- | Add a signature to the transaction using the private key -addSignature :: XPrv -> Tx -> Tx -addSignature xprv tx@(Read.Tx ledgerTx) = - Read.Tx - (ledgerTx & (L.witsTxL . L.addrTxWitsL) %~ Set.insert witnessVKey) - where - txHash = Read.hashFromTxId $ Read.getTxId tx - xpub = toXPub xprv - xsign = sign xprv (Hash.hashToBytes txHash) - witnessVKey = - L.WitVKey (vkeyFromXPub xpub) (signedDSIGNfromXSignature xsign) - -{----------------------------------------------------------------------------- - Convenience TxBody -------------------------------------------------------------------------------} - -data TxBody = TxBody - { spendInputs :: Set TxIn - , collInputs :: Set TxIn - , txouts :: Map Ix TxOut - , collRet :: Maybe TxOut - , expirySlot :: Maybe SlotNo - } - deriving (Show) - -txOutsL :: Lens' TxBody (Map Ix TxOut) -txOutsL = lens txouts (\s a -> s{txouts = a}) - -nextIx :: TxBody -> Ix -nextIx = maybe minBound (succ . fst) . Map.lookupMax . txouts - -addTxOut :: TxOut -> TxBody -> (TxBody, Ix) -addTxOut txout txbody = (txBody', txIx) - where - txBody' = txbody & txOutsL .~ Map.insert txIx txout (txouts txbody) - txIx = nextIx txbody - -addTxIn :: TxIn -> TxBody -> TxBody -addTxIn txin txbody = txbody{spendInputs = Set.insert txin (spendInputs txbody)} - -emptyTxBody :: TxBody -emptyTxBody = TxBody mempty mempty mempty Nothing Nothing - --- | Inject a number of ADA, i.e. a million lovelace. -mkAda :: Integer -> Value -mkAda = Read.injectCoin . Read.CoinC . (* 1000000) - -mkTxOut :: Address -> Value -> TxOut -mkTxOut = Read.mkBasicTxOut - -toConwayTx :: Tx -> Read.Tx Read.Conway -toConwayTx = id - -mkTx :: TxBody -> Tx -mkTx txbody = Read.Tx $ L.mkBasicTx txBody - where - txBody :: L.TxBody L.Conway - txBody = - L.mkBasicTxBody - & L.inputsTxBodyL .~ Set.map toLedgerTxIn (spendInputs txbody) - & L.collateralInputsTxBodyL - .~ Set.map toLedgerTxIn (collInputs txbody) - & L.outputsTxBodyL .~ toLedgerTxOuts (txouts txbody) - & L.collateralReturnTxBodyL - .~ toLedgerMaybeTxOut (collRet txbody) - & L.vldtTxBodyL - .~ L.ValidityInterval - SNothing - (toLedgerSlotNo <$> maybeToStrictMaybe (expirySlot txbody)) - -toLedgerSlotNo :: SlotNo -> L.SlotNo -toLedgerSlotNo (SlotNo n) = L.SlotNo (fromInteger $ fromIntegral n) - -toLedgerTxIn :: TxIn -> L.TxIn L.StandardCrypto -toLedgerTxIn = id - -toLedgerTxOuts :: Map Ix TxOut -> StrictSeq (L.TxOut L.Conway) -toLedgerTxOuts = fromList . map (toConwayTxOut . snd) . Map.toAscList - -toLedgerMaybeTxOut :: Maybe TxOut -> StrictMaybe (L.TxOut L.Conway) -toLedgerMaybeTxOut = fmap toConwayTxOut . maybeToStrictMaybe - -toConwayTxOut :: TxOut -> L.TxOut L.Conway -toConwayTxOut txout = - case toConwayOutput txout of - Output o -> o - -toConwayUTxO :: Map TxIn TxOut -> Write.UTxO L.Conway -toConwayUTxO = Write.UTxO . Map.map toConwayTxOut diff --git a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Write/Keys.hs b/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Write/Keys.hs deleted file mode 100644 index 2fa7c1c413e..00000000000 --- a/lib/deposit-wallet/src/Cardano/Wallet/Deposit/Write/Keys.hs +++ /dev/null @@ -1,80 +0,0 @@ -{-# LANGUAGE DataKinds #-} - --- | Module for converting key types from --- @Cardano.Ledger@ with key types from @Cardano.Crypto.Wallet@. --- --- TODO: Match this up with the @Write@ hierarchy. -module Cardano.Wallet.Deposit.Write.Keys - ( enterpriseAddressFromVKey - , vkeyFromXPub - , signedDSIGNfromXSignature - ) where - -import Prelude - -import Cardano.Crypto.Wallet - ( xpubPublicKey - ) -import Cardano.Ledger.Keys - ( SignedDSIGN - , VKey (..) - ) -import Cardano.Wallet.Address.BIP32_Ed25519 - ( XPub - , XSignature - , rawSerialiseXSignature - ) -import Cardano.Wallet.Deposit.Read - ( Address - ) -import Data.Maybe - ( fromMaybe - ) - -import qualified Cardano.Crypto.DSIGN as DSIGN -import qualified Cardano.Ledger.Address as L -import qualified Cardano.Ledger.Api as L -import qualified Cardano.Ledger.BaseTypes as L -import qualified Cardano.Ledger.Credential as L -import qualified Cardano.Ledger.Hashes as L -import qualified Cardano.Ledger.Keys as L - -{----------------------------------------------------------------------------- - Key conversion -------------------------------------------------------------------------------} --- | Create an enterprise address from a ledger 'VKey'. -enterpriseAddressFromVKey - :: L.Network - -> VKey 'L.Witness L.StandardCrypto - -> Address -enterpriseAddressFromVKey network = - mkEnterpriseAddress - . L.coerceKeyRole - . L.hashKey - where - mkEnterpriseAddress h = - L.compactAddr - $ L.Addr network (L.KeyHashObj h) L.StakeRefNull - --- | Convert 'XPub' to a ledger verification key. -vkeyFromXPub :: XPub -> VKey 'L.Witness L.StandardCrypto -vkeyFromXPub = - VKey - . fromMaybe impossible - . DSIGN.rawDeserialiseVerKeyDSIGN - . xpubPublicKey - where - impossible = error "impossible: Cannot convert XPub to VKey" - --- | Convert 'XSignature' to a ledger signature. -signedDSIGNfromXSignature - :: XSignature - -> SignedDSIGN L.StandardCrypto - (L.Hash L.StandardCrypto L.EraIndependentTxBody) -signedDSIGNfromXSignature = - DSIGN.SignedDSIGN - . fromMaybe impossible - . DSIGN.rawDeserialiseSigDSIGN - . rawSerialiseXSignature - where - impossible = error "impossible: Cannot convert XSignature to SignedDSIGN" diff --git a/lib/deposit-wallet/test/scenario/Test/Scenario/Blockchain.hs b/lib/deposit-wallet/test/scenario/Test/Scenario/Blockchain.hs deleted file mode 100644 index 821f04bbec4..00000000000 --- a/lib/deposit-wallet/test/scenario/Test/Scenario/Blockchain.hs +++ /dev/null @@ -1,144 +0,0 @@ -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE RecordWildCards #-} - -{-| -Copyright: © 2024 Cardano Foundation -License: Apache-2.0 - -Mock implementation of a blockchain for the purpose of testing. - -TODO: -* Make the blockchain more real. --} -module Test.Scenario.Blockchain - ( assert - - , ScenarioEnv - , withScenarioEnvMock - , withWalletEnvMock - - , Faucet - , ada - , payFromFaucet - - , signTx - , submitTx - ) where - -import Prelude - -import Cardano.Crypto.Wallet - ( XPrv - ) -import Cardano.Wallet.Deposit.IO.Network.Mock - ( newNetworkEnvMock - ) -import Cardano.Wallet.Deposit.IO.Network.Type - ( NetworkEnv (..) - , mapBlock - ) -import Cardano.Wallet.Deposit.Pure - ( BIP32Path - ) -import Control.Concurrent - ( threadDelay - ) -import Control.Tracer - ( nullTracer - ) -import Data.Store - ( newStore - ) -import GHC.Stack - ( HasCallStack - ) - -import qualified Cardano.Wallet.Deposit.IO as Wallet -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Data.Map.Strict as Map - -{----------------------------------------------------------------------------- - Logic -------------------------------------------------------------------------------} - -assert :: HasCallStack => Bool -> IO () -assert True = pure () -assert False = error "Assertion failed!" - -{----------------------------------------------------------------------------- - Environment -------------------------------------------------------------------------------} --- | Environment for scenarios. -data ScenarioEnv = ScenarioEnv - { genesisData :: Read.GenesisData - , networkEnv :: NetworkEnv IO (Read.EraValue Read.Block) - , faucet :: Faucet - } - --- | Acquire and release a mock environment for a blockchain -withScenarioEnvMock :: (ScenarioEnv -> IO a) -> IO a -withScenarioEnvMock action = do - networkEnv <- mapBlock Read.EraValue <$> newNetworkEnvMock - action - $ ScenarioEnv - { genesisData = Read.mockGenesisDataMainnet - , networkEnv - , faucet = Faucet{xprv = error "TODO: Faucet xprv"} - } - --- | Acquire and release a mock environment for a wallet. -withWalletEnvMock - :: ScenarioEnv - -> (Wallet.WalletEnv IO -> IO a) - -> IO a -withWalletEnvMock ScenarioEnv{..} action = do - database <- newStore - let walletEnv = Wallet.WalletEnv - Wallet.WalletBootEnv - { Wallet.logger = nullTracer - , Wallet.genesisData = genesisData - , Wallet.networkEnv = networkEnv - } - database - action walletEnv - -{----------------------------------------------------------------------------- - Faucet -------------------------------------------------------------------------------} -newtype Faucet = Faucet - { xprv :: XPrv - } - -ada :: Integer -> Write.Value -ada = Write.mkAda - -payFromFaucet :: ScenarioEnv -> [(Write.Address, Write.Value)] -> IO () -payFromFaucet env destinations = - submitTx env tx - where - toTxOut (addr, value) = Write.mkTxOut addr value - txBody = Write.TxBody - { Write.spendInputs = mempty - , Write.collInputs = mempty - , Write.txouts = - Map.fromList $ zip [toEnum 0..] $ map toTxOut destinations - , Write.collRet = Nothing - , Write.expirySlot = Nothing - } - tx = signTx (xprv (faucet env)) [] $ Write.mkTx txBody - -{----------------------------------------------------------------------------- - Transaction submission -------------------------------------------------------------------------------} - -signTx :: XPrv -> [BIP32Path] -> Write.Tx -> Write.Tx -signTx _ _ = id - -submitTx :: ScenarioEnv -> Write.Tx -> IO () -submitTx env tx = do - _ <- postTx (networkEnv env) tx - - -- Wait a short while to give the tx time to make it on-chain. - threadDelay 500_000 diff --git a/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Exchanges.lhs b/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Exchanges.lhs deleted file mode 120000 index daac8bef83a..00000000000 --- a/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Exchanges.lhs +++ /dev/null @@ -1 +0,0 @@ -Exchanges.lhs.md \ No newline at end of file diff --git a/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Exchanges.lhs.md b/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Exchanges.lhs.md deleted file mode 100644 index 1d22927004d..00000000000 --- a/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Exchanges.lhs.md +++ /dev/null @@ -1,229 +0,0 @@ -# Use case: Centralized Exchange - -This document describes how a centralized exchange (CEX) can use the Deposit Wallet to - -1. Assign an address to a customer ID. -2. Track deposits at this address. -3. Track deposits at all addresses. -4. Create payments to a different wallet. - -# Scenarios, Haskell - -In this section, we describe the scenarios using Haskell. - -```haskell -module Test.Scenario.Wallet.Deposit.Exchanges - ( scenarioRestore - , scenarioStart - , scenarioCreateAddressList - , scenarioTrackDepositOne - , scenarioTrackDepositAll - , scenarioCreatePayment - ) where - -import Prelude - -import Cardano.Crypto.Wallet - ( XPrv - , XPub - ) -import Cardano.Wallet.Deposit.IO - ( WalletEnv - , WalletInstance - ) -import Cardano.Wallet.Deposit.Pure - ( Customer - , ValueTransfer (..) - , Credentials (..) - , ResolvedTx (..) - ) -import Cardano.Wallet.Deposit.Read - ( Address - , Value - , TxId - , lessOrEqual - ) -import Control.Tracer - ( nullTracer - ) -import Test.Scenario.Blockchain - ( ScenarioEnv - , ada - , assert - , payFromFaucet - , signTx - , submitTx - ) - -import qualified Cardano.Wallet.Deposit.IO as Wallet -import qualified Data.Map as Map -``` - -We use a function `depositFundsAt` to make a deposit at a given address. - -```haskell -depositFundsAt :: ScenarioEnv -> Address -> Value -> IO () -depositFundsAt env address value = payFromFaucet env [(address, value)] -``` - -We ignore the mapping from TxId when retrieving the customer history -```haskell -getCustomerDeposits :: Customer -> WalletInstance -> IO [(TxId, ValueTransfer)] -getCustomerDeposits customer w = - Map.toList <$> Wallet.getCustomerDeposits w customer Nothing -``` - -## 0. Start a Wallet - -A `WalletInstance` denotes a mutable wallet that is actively synchronizing to the blockchain, continuously writes its state to a database file, and responds to queries. - -In order to create a fresh wallet, or in order to restore a wallet from its public key all the way from genesis, use the function `withWalletInit`. In addition to the public key, this function expects a number which equals the numerically largest customer ID previously handled with this wallet. - -```haskell -scenarioRestore - :: XPub -> WalletEnv IO -> IO () -scenarioRestore xpub env = do - let knownCustomerCount = 127 - Wallet.withWalletInit nullTracer env (XPubCredentials xpub) knownCustomerCount $ \w -> do - value <- Wallet.availableBalance w - assert $ value == ada 0 -``` - -In order to load the wallet state from a database file and resume operation from that state use the function `withWalletLoad`. - -```haskell -scenarioStart - :: WalletEnv IO -> IO () -scenarioStart env = - Wallet.withWalletLoad nullTracer env $ \w -> do - value <- Wallet.availableBalance w - assert $ value == ada 0 -``` - -## 1. Assign an address to a customer ID - -A `Customer` is represented by a numeric customer ID. -Given such a customer ID, the function `customerAddress` will create an address and add the association between the customer and this address to the wallet state. - -(The mapping from customer ID to address is deterministic and based on the [BIP-32][] address derivation scheme.) - - [bip-32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki - - -The function `listCustomers` returns the associations between customers and addresses recorded in the current wallet state. - -```haskell -scenarioCreateAddressList - :: WalletInstance -> IO () -scenarioCreateAddressList w = do - let customer = 31 - Just address <- Wallet.customerAddress customer w - customers <- Wallet.listCustomers w - assert $ (customer, address) `elem` customers -``` - -## 2. Track deposits at this address - -As soon as an association between customer and address has been added to the wallet state using `customerAddress`, the wallet will track deposits sent to this address. - -The function `getCustomerDeposits` returns a summary for each transaction that is related to this customer. For every summary, the `received` field records the total deposit made by the customer at this address in this transaction. - -(The `spent` field has informative purpose only, and records whether the wallet has moved any funds out of this address.) - -The following scenario illustrates how `getCustomerDeposits` records deposits: - -```haskell -scenarioTrackDepositOne - :: ScenarioEnv -> WalletInstance -> IO () -scenarioTrackDepositOne env w = do - Just address <- Wallet.customerAddress customer w - - -- no deposits - txsummaries0 <- getCustomerDeposits customer w - assert $ null txsummaries0 - - -- first deposit - depositFundsAt env address coin - txsummaries1 <- getCustomerDeposits customer w - assert $ map (received . snd) txsummaries1 == [coin] - - -- second deposit - depositFundsAt env address coin - txsummaries2 <- getCustomerDeposits customer w - assert $ map (received . snd) txsummaries2 == [coin, coin] - where - customer = 7 :: Customer - coin = ada 12 -``` - -## 3. Track deposits at all addresses - -A centralized exchange typically wants to monitor all transactions in a recent time window for activity in order to synchronize customer deposits on the blockchain ledger with the exchange ledger recording customer balances. - -This is a task for the `getCustomerHistories` function — it returns a mapping from customers to `TxSummaries` that record the entire activity within the given time interval. - -The time interval is specified by a `from` and a `to` point on the blockchain. We note that the `from` argument is exclusive while the `to` argument is inclusive. -We use the type `ChainPoint` to specify points on the blockchain — this type uses both a slot number and a block header to uniquely identify a block. We do this in order to allow atomic operations — in the event that the `to` or `from` point are no longer part of the consensus chain, the `getCustomerHistories` functions throws an exception. - -The wallet is synchronized to a particular point on the blockchain — use `getWalletTip` to query it. - -```haskell -scenarioTrackDepositAll - :: ScenarioEnv -> WalletInstance -> IO () -scenarioTrackDepositAll env w = do - Just address1 <- Wallet.customerAddress customer1 w - Just address2 <- Wallet.customerAddress customer2 w - - depositFundsAt env address1 coin - depositFundsAt env address2 coin - depositFundsAt env address1 (coin <> coin) - - history <- Wallet.getAllDeposits w Nothing - assert $ - Map.map received history - == - Map.fromList - [ (customer1, coin <> coin <> coin) - , (customer2, coin) - ] - where - customer1, customer2 :: Customer - customer1 = 1 - customer2 = 2 - coin = ada 3 -``` - -## 4. Create payments to a different wallet - -The `createPayment` function allows you to move funds from one wallet to other addresses, e.g. in order to process customer withdrawals. If the wallet has sufficient funds, this function creates a transaction body which sends the given values to the given addresses. - -The transaction body needs to be signed. Given a transaction body, the function `getBIP32PathsForOwnedInputs` will provide you with all [BIP-32][] address derivation paths of all inputs that are owned by the wallet, and which therefore require a signature. - -```haskell -scenarioCreatePayment - :: XPrv -> ScenarioEnv -> Address -> WalletInstance -> IO () -scenarioCreatePayment xprv env destination w = do - -- deposit some funds at customer address - Just address1 <- Wallet.customerAddress customer w - depositFundsAt env address1 (coin <> coin) - value1 <- Wallet.availableBalance w - assert $ value1 == (coin <> coin) - - -- createPayment - Right (ResolvedTx txUnsigned _) <- Wallet.createPayment [(destination, coin)] w - paths <- Wallet.getBIP32PathsForOwnedInputs txUnsigned w - let tx = signTx xprv paths txUnsigned - submitTx env tx - - -- funds have been moved out of the wallet - value2 <- Wallet.availableBalance w - assert $ (value2 <> coin) `lessOrEqual` value1 - - -- but the original deposit amount is still recorded - txsummaries <- getCustomerDeposits customer w - assert $ value1 `elem` map (received . snd) txsummaries - where - customer :: Customer - customer = 17 - coin = ada 5 -``` diff --git a/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Run.hs b/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Run.hs deleted file mode 100644 index ea0e8193178..00000000000 --- a/lib/deposit-wallet/test/scenario/Test/Scenario/Wallet/Deposit/Run.hs +++ /dev/null @@ -1,118 +0,0 @@ --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Execute usage scenarios for the deposit wallet. -module Test.Scenario.Wallet.Deposit.Run - ( main - ) where - -import Prelude - -import Cardano.Crypto.Wallet - ( XPrv - , XPub - , generate - , toXPub - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( Credentials (..) - ) -import Control.Tracer - ( nullTracer - ) -import Test.Hspec - ( SpecWith - , describe - , it - ) -import Test.Hspec.Extra - ( aroundAll - , hspecMain - ) -import Test.Scenario.Blockchain - ( ScenarioEnv - , ada - , assert - , payFromFaucet - , withScenarioEnvMock - , withWalletEnvMock - ) - -import qualified Cardano.Wallet.Deposit.IO as Wallet -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Data.ByteString.Char8 as B8 -import qualified Data.ByteString.Short as SBS -import qualified Test.Scenario.Wallet.Deposit.Exchanges as Exchanges - -main :: IO () -main = - hspecMain - $ aroundAll withScenarioEnvMock scenarios - -scenarios :: SpecWith ScenarioEnv -scenarios = do - describe "Scenarios for centralized exchanges" $ do - it "0. Restore a wallet" $ \env -> - withWalletEnvMock env - $ Exchanges.scenarioRestore xpub - - it "0. Start a wallet" $ \env -> - withWalletEnvMock env $ \w -> do - Exchanges.scenarioRestore xpub w - Exchanges.scenarioStart w - - it "1. Assign an address to a customer ID" $ \env -> do - withWalletEnvMock env $ \walletEnv -> - Wallet.withWalletInit - nullTracer - walletEnv - (XPubCredentials $ freshXPub 1) - 32 - Exchanges.scenarioCreateAddressList - - it "4. Create payments to a different wallet" $ \env -> do - withWalletEnvMock env $ \walletEnv -> - Wallet.withWalletInit nullTracer - walletEnv (XPubCredentials xpub) 32 - $ Exchanges.scenarioCreatePayment xprv env mockAddress - - describe "Temporary tests" $ do - it "Wallet receives funds that are sent to customer address" $ \env -> do - withWalletEnvMock env $ \walletEnv -> - Wallet.withWalletInit - nullTracer - walletEnv - (XPubCredentials $ freshXPub 0) - 8 - $ testBalance env - -xpub :: XPub -xpub = toXPub xprv - -xprv :: XPrv -xprv = generate (B8.pack "random seed for a testing xpub lala") B8.empty - -freshXPub :: Integer -> XPub -freshXPub i = - toXPub - $ generate - (B8.pack $ "random seed for a testing xpub lala" <> show i) - B8.empty - -mockAddress :: Read.Address -mockAddress = - Read.mkEnterpriseAddress - Read.MainnetTag - (SBS.pack $ replicate 32 0) - -testBalance - :: ScenarioEnv -> Wallet.WalletInstance -> IO () -testBalance env w = do - Just address <- Wallet.customerAddress customer w - payFromFaucet env [(address, coin)] - value <- Wallet.availableBalance w - assert $ coin == value - where - customer = 7 - coin = ada 12 diff --git a/lib/deposit-wallet/test/scenario/test-suite-scenario.hs b/lib/deposit-wallet/test/scenario/test-suite-scenario.hs deleted file mode 100644 index 9c2e55cbedf..00000000000 --- a/lib/deposit-wallet/test/scenario/test-suite-scenario.hs +++ /dev/null @@ -1,10 +0,0 @@ -module Main where - -import Prelude - -import qualified Test.Scenario.Wallet.Deposit.Run - ( main - ) - -main :: IO () -main = Test.Scenario.Wallet.Deposit.Run.main diff --git a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/HTTP/JSON/JSONSpec.hs b/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/HTTP/JSON/JSONSpec.hs deleted file mode 100644 index 56371cb0951..00000000000 --- a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/HTTP/JSON/JSONSpec.hs +++ /dev/null @@ -1,186 +0,0 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE TypeApplications #-} - -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module Cardano.Wallet.Deposit.HTTP.JSON.JSONSpec - ( spec - ) where - -import Prelude - -import Cardano.Wallet.Deposit.HTTP.Types.JSON - ( Address - , ApiT (..) - , ChainPoint (..) - , Customer - , CustomerList - ) -import Cardano.Wallet.Deposit.HTTP.Types.OpenAPI - ( addressSchema - , chainPointSchema - , customerListSchema - , customerSchema - , depositDefinitions - ) -import Cardano.Wallet.Deposit.Pure - ( Word31 - , fromRawCustomer - ) -import Cardano.Wallet.Deposit.Read - ( NetworkTag (MainnetTag, TestnetTag) - , mkEnterpriseAddress - ) -import Data.Aeson - ( FromJSON (..) - , ToJSON (..) - , Value - , decode - , encode - ) -import Data.Aeson.Encode.Pretty - ( encodePretty - ) -import Data.OpenApi - ( Definitions - , Schema - , validateJSON - ) -import Data.Word - ( Word64 - ) -import Test.Hspec - ( Expectation - , Spec - , describe - , it - , shouldBe - ) -import Test.QuickCheck - ( Arbitrary (..) - , Gen - , Property - , Testable - , arbitrarySizedBoundedIntegral - , chooseInt - , counterexample - , elements - , forAll - , frequency - , property - , shrinkIntegral - , vectorOf - , (===) - ) - -import qualified Cardano.Wallet.Read as Read -import qualified Data.ByteString.Lazy.Char8 as BL -import qualified Data.ByteString.Short as SBS -import qualified Data.List as L - -spec :: Spec -spec = do - describe "JSON serialization & deserialization" $ do - it "ApiT Address" $ property $ - prop_jsonRoundtrip @(ApiT Address) - it "ApiT Customer" $ property $ - prop_jsonRoundtrip @(ApiT Customer) - it "ApiT CustomerList" $ property $ - prop_jsonRoundtrip @(ApiT CustomerList) - it "ApiT ChainPoint" $ property $ - prop_jsonRoundtrip @(ApiT ChainPoint) - describe "schema checks" $ do - it "ApiT Address" - $ jsonMatchesSchema genApiTAddress depositDefinitions addressSchema - it "ApiT Customer" - $ jsonMatchesSchema genApiTCustomer depositDefinitions customerSchema - it "ApiT CustomerList" - $ jsonMatchesSchema genApiTCustomerList depositDefinitions customerListSchema - it "ApiT ChainPoint" - $ jsonMatchesSchema genApiTChainPoint depositDefinitions chainPointSchema - -jsonMatchesSchema - :: (ToJSON a, Show a) - => Gen a - -> Definitions Schema - -> Schema - -> Property -jsonMatchesSchema gen defs schema = - forAll gen - $ counterExampleJSON "validate" - $ validateInstance defs schema - where - validate :: Definitions Schema -> Schema -> Value -> Expectation - validate defs' sch' x = validateJSON defs' sch' x `shouldBe` [] - - validateInstance :: ToJSON a => Definitions Schema -> Schema -> a -> Expectation - validateInstance defs' sch' = validate defs' sch' . toJSON - - counterExampleJSON - :: (Testable prop, ToJSON a) - => String - -> (a -> prop) - -> a - -> Property - counterExampleJSON t f x = - counterexample - ("Failed to " <> t <> ":\n" <> BL.unpack (encodePretty $ toJSON x)) - $ f x - -prop_jsonRoundtrip :: (Eq a, Show a, FromJSON a, ToJSON a) => a -> Property -prop_jsonRoundtrip val = - decode (encode val) === Just val - -genAddress :: Gen Address -genAddress = do - network <- elements [MainnetTag, TestnetTag] - keyhashCred <- SBS.pack <$> vectorOf 28 arbitrary - pure $ mkEnterpriseAddress network keyhashCred - -genApiTAddress :: Gen (ApiT Address) -genApiTAddress = ApiT <$> genAddress - -genApiTCustomer :: Gen (ApiT Customer) -genApiTCustomer = - ApiT . fromRawCustomer <$> arbitrary - -genApiTCustomerList :: Gen (ApiT CustomerList) -genApiTCustomerList = do - listLen <- chooseInt (0, 100) - let genPair = (,) <$> (unApiT <$> arbitrary) <*> (unApiT <$> arbitrary) - vectors <- vectorOf listLen genPair - let uniqueCustomer = L.nubBy (\a b -> fst a == fst b) - let uniqueAddr = L.nubBy (\a b -> snd a == snd b) - pure $ ApiT $ uniqueAddr $ uniqueCustomer vectors - -genApiTChainPoint :: Gen (ApiT ChainPoint) -genApiTChainPoint = ApiT <$> genChainPoint - -genChainPoint :: Gen Read.ChainPoint -genChainPoint = frequency - [ ( 1, pure Read.GenesisPoint) - , (40, Read.BlockPoint <$> genReadSlotNo <*> genHeaderHash) - ] - where - genReadSlotNo = Read.SlotNo . fromIntegral <$> (arbitrary :: Gen Word64) - genHeaderHash = elements mockHashes - -mockHashes :: [Read.RawHeaderHash] -mockHashes = map Read.mockRawHeaderHash [0..2] - -instance Arbitrary (ApiT Address) where - arbitrary = genApiTAddress - -instance Arbitrary (ApiT Customer) where - arbitrary = genApiTCustomer - -instance Arbitrary (ApiT CustomerList) where - arbitrary = genApiTCustomerList - -instance Arbitrary (ApiT ChainPoint) where - arbitrary = genApiTChainPoint - -instance Arbitrary Word31 where - arbitrary = arbitrarySizedBoundedIntegral - shrink = shrinkIntegral diff --git a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/HTTP/OpenAPISpec.hs b/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/HTTP/OpenAPISpec.hs deleted file mode 100644 index bb9d4c0e73e..00000000000 --- a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/HTTP/OpenAPISpec.hs +++ /dev/null @@ -1,55 +0,0 @@ -module Cardano.Wallet.Deposit.HTTP.OpenAPISpec - ( spec - ) where - -import Prelude - -import Cardano.Wallet.Deposit.HTTP.Types.OpenAPI - ( generateOpenapi3 - ) -import Paths_cardano_deposit_wallet - ( getDataDir - , getDataFileName - ) -import System.Directory - ( doesDirectoryExist - , doesFileExist - ) -import Test.Hspec - ( Spec - , describe - , it - , shouldReturn - ) -import Test.Hspec.Golden - ( Golden (..) - ) - -import qualified Data.ByteString.Lazy.Char8 as BL - -spec :: Spec -spec = do - describe "data dir" $ do - it "should exist" $ do - f <- getDataDir - doesDirectoryExist f `shouldReturn` True - describe "swagger.yaml" $ do - it "should be generated" $ do - f <- getDataFileName "data/swagger.json" - doesFileExist f `shouldReturn` True - it "contains the actual schema" $ do - f <- getDataFileName "data/swagger.json" - let output' = generateOpenapi3 - pure $ swaggerGolden f $ BL.unpack output' - -swaggerGolden :: FilePath -> String -> Golden String -swaggerGolden goldenPath output_ = - Golden - { output = output_ - , encodePretty = show - , writeToFile = writeFile - , readFromFile = readFile - , goldenFile = goldenPath - , actualFile = Nothing - , failFirstTime = False - } diff --git a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Map/TimedSpec.hs b/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Map/TimedSpec.hs deleted file mode 100644 index dac6c96651c..00000000000 --- a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Map/TimedSpec.hs +++ /dev/null @@ -1,429 +0,0 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE OverloadedLists #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE TypeApplications #-} - -module Cardano.Wallet.Deposit.Map.TimedSpec where - -import Prelude - -import Cardano.Wallet.Deposit.Map.Timed - ( Timed (..) - , TimedSeq - , dropAfter - , dropBefore - , fromList - , maxKey - , minKey - , takeAfter - , takeUpTo - , toList - ) -import Data.List - ( sort - , unfoldr - ) -import Data.Monoid - ( Last (..) - , Sum (..) - ) -import Data.Time - ( UTCTime (..) - , defaultTimeLocale - , parseTimeOrError - , pattern YearMonthDay - ) -import Test.Hspec - ( Spec - , describe - , it - , shouldBe - , shouldNotBe - ) - -type UTimed = Timed UTCTime (Sum Int) - -type UTimedSeq = TimedSeq UTCTime (Sum Int) - -t :: String -> UTCTime -t = - parseTimeOrError False defaultTimeLocale "%Y-%m-%d %H:%M:%S" - -mkTimed :: String -> Int -> UTimed -mkTimed s i = Timed (Last $ Just $ t s) (Sum i) - -t0 :: UTimed -t0 = mkTimed "2021-01-01 00:00:00" 1 - -t1 :: UTimed -t1 = mkTimed "2021-01-02 00:00:00" 2 - -t2 :: UTimed -t2 = mkTimed "2021-02-01 00:00:00" 3 - -t3 :: UTimed -t3 = mkTimed "2021-02-02 00:00:00" 4 - -t4 :: UTimed -t4 = mkTimed "2022-02-03 00:00:00" 5 - -t5 :: UTimed -t5 = mkTimed "2022-02-03 12:00:00" 6 - -t6 :: UTimed -t6 = mkTimed "2022-03-03 12:00:00" 7 - -t7 :: UTimed -t7 = mkTimed "2022-03-05 12:00:00" 8 - -ts :: [UTimed] -ts = sort [t0, t1, t2, t3, t4, t5, t6, t7] - -result - :: [UTimed] - -> Maybe UTimed - -> (UTimedSeq, Maybe UTCTime) -result included next = (fromList included, nextTime) - where - nextTime = do - Timed x _ <- next - getLast x - -results :: [[UTimed]] -> [UTimedSeq] -results = fmap fromList - -byYear :: UTCTime -> Integer -byYear (UTCTime (YearMonthDay y _ _) _) = y - -byMonth :: UTCTime -> (Integer, Int) -byMonth (UTCTime (YearMonthDay y m _) _) = (y, m) - -byDay :: UTCTime -> (Integer, Int, Int) -byDay (UTCTime (YearMonthDay y m d) _) = (y, m, d) - -scroll - :: (TimedSeq t a -> Maybe t) - -> ( (t -> q) - -> Maybe t - -> Maybe Int - -> TimedSeq t a - -> (TimedSeq t a, Maybe t) - ) - -> (t -> q) - -> Int - -> TimedSeq t a - -> [TimedSeq t a] -scroll boot extract bucket count pager = unfoldr f $ boot pager - where - f Nothing = Nothing - f (Just start) = case extract bucket (Just start) (Just count) pager of - (m, Just next) -> Just (m, Just next) - (m, Nothing) -> Just (m, Nothing) - -spec :: Spec -spec = do - describe "takeAfter" $ do - it "can extract without start" $ do - takeAfter - byDay - Nothing - (Just 1) - (fromList ts) - `shouldBe` result [t0] (Just t1) - it "can extract without count" $ do - takeAfter - byDay - (Just $ t "2021-01-01 00:00:00") - Nothing - (fromList ts) - `shouldBe` result [t0, t1, t2, t3, t4 <> t5, t6, t7] Nothing - it "can extract 1 day" $ do - takeAfter - byDay - (Just $ t "2021-01-01 00:00:00") - (Just 1) - (fromList ts) - `shouldBe` result [t0] (Just t1) - it "can extract 2 days" $ do - takeAfter - byDay - (Just $ t "2021-01-01 00:00:00") - (Just 2) - (fromList ts) - `shouldBe` result [t0, t1] (Just t2) - it "can extract 5 days" $ do - takeAfter - byDay - (Just $ t "2021-01-01 00:00:00") - (Just 5) - (fromList ts) - `shouldBe` result [t0, t1, t2, t3, t4 <> t5] (Just t6) - it "can extract 1 month" $ do - takeAfter - byMonth - (Just $ t "2021-01-01 00:00:00") - (Just 1) - (fromList ts) - `shouldBe` result [t0 <> t1] (Just t2) - - it "can extract 2 months" $ do - takeAfter - byMonth - (Just $ t "2021-01-01 00:00:00") - (Just 2) - (fromList ts) - `shouldBe` result [t0 <> t1, t2 <> t3] (Just t4) - - it "can extract 3 months" $ do - takeAfter - byMonth - (Just $ t "2021-01-01 00:00:00") - (Just 3) - (fromList ts) - `shouldBe` result [t0 <> t1, t2 <> t3, t4 <> t5] (Just t6) - - it "can extract 1 year" $ do - takeAfter - byYear - (Just $ t "2021-01-01 00:00:00") - (Just 1) - (fromList ts) - `shouldBe` result [t0 <> t1 <> t2 <> t3] (Just t4) - - it "can extract 2 years" $ do - takeAfter - byYear - (Just $ t "2021-01-01 00:00:00") - (Just 2) - (fromList ts) - `shouldBe` result [t0 <> t1 <> t2 <> t3, t4 <> t5 <> t6 <> t7] Nothing - - it "can extract 3 years" $ do - takeAfter - byYear - (Just $ t "2021-01-01 00:00:00") - (Just 3) - (fromList ts) - `shouldBe` result [t0 <> t1 <> t2 <> t3, t4 <> t5 <> t6 <> t7] Nothing - - it "can extract 1 day after t0" $ do - takeAfter - byDay - (Just $ t "2021-01-01 00:00:01") - (Just 1) - (fromList ts) - `shouldBe` result [t1] (Just t2) - - it "can extract 1 month after t0" $ do - takeAfter - byMonth - (Just $ t "2021-01-01 00:00:01") - (Just 1) - (fromList ts) - `shouldBe` result [t1] (Just t2) - - it "can extract 1 year after t0" $ do - takeAfter - byYear - (Just $ t "2021-01-01 00:00:01") - (Just 1) - (fromList ts) - `shouldBe` result [t1 <> t2 <> t3] (Just t4) - describe "takeBefore" $ do - it "can extract without start" $ do - takeUpTo - byDay - Nothing - (Just 1) - (fromList ts) - `shouldBe` result [t7] (Just t6) - it "can extract without count" $ do - takeUpTo - byDay - (Just $ t "2022-03-05 12:00:00") - Nothing - (fromList ts) - `shouldBe` result [t7, t6, t4 <> t5, t3, t2, t1, t0] Nothing - it "can extract 1 day" $ do - takeUpTo - byDay - (Just $ t "2022-03-05 12:00:00") - (Just 1) - (fromList ts) - `shouldBe` result [t7] (Just t6) - it "can extract 2 days" $ do - takeUpTo - byDay - (Just $ t "2022-03-05 12:00:00") - (Just 2) - (fromList ts) - `shouldBe` result [t7, t6] (Just t5) - it "can extract 3 days" $ do - takeUpTo - byDay - (Just $ t "2022-03-05 12:00:00") - (Just 3) - (fromList ts) - `shouldBe` result [t7, t6, t4 <> t5] (Just t3) - it "can extract 1 month" $ do - takeUpTo - byMonth - (Just $ t "2022-03-05 12:00:00") - (Just 1) - (fromList ts) - `shouldBe` result [t6 <> t7] (Just t5) - it "can extract 2 months" $ do - takeUpTo - byMonth - (Just $ t "2022-03-05 12:00:00") - (Just 2) - (fromList ts) - `shouldBe` result [t6 <> t7, t4 <> t5] (Just t3) - it "can extract 2 years" $ do - takeUpTo - byYear - (Just $ t "2022-03-05 12:00:00") - (Just 2) - (fromList ts) - `shouldBe` result - [t4 <> t5 <> t6 <> t7, t0 <> t1 <> t2 <> t3] - Nothing - it "can extract 1 day before t7" $ do - takeUpTo - byDay - (Just $ t "2022-03-05 11:59:59") - (Just 1) - (fromList ts) - `shouldBe` result [t6] (Just t5) - it "can extract 1 month before t6" $ do - takeUpTo - byMonth - (Just $ t "2022-03-03 11:59:59") - (Just 1) - (fromList ts) - `shouldBe` result [t4 <> t5] (Just t3) - it "can extract 1 year before t4" $ do - takeUpTo - byYear - (Just $ t "2022-01-02 23:59:59") - (Just 1) - (fromList ts) - `shouldBe` result [t0 <> t1 <> t2 <> t3] Nothing - - describe "TimedSeq scroll" $ do - it "can consume scrolling forward by 1 day" $ do - scroll minKey takeAfter byDay 1 (fromList ts) - `shouldBe` results - [ [t0] - , [t1] - , [t2] - , [t3] - , [t4 <> t5] - , [t6] - , [t7] - ] - it "can consume scrolling backward by 1 day" $ do - scroll maxKey takeUpTo byDay 1 (fromList ts) - `shouldBe` results - [ [t7] - , [t6] - , [t4 <> t5] - , [t3] - , [t2] - , [t1] - , [t0] - ] - it "can consume scrolling forward by 1 month" $ do - scroll minKey takeAfter byMonth 1 (fromList ts) - `shouldBe` results - [ [t0 <> t1] - , [t2 <> t3] - , [t4 <> t5] - , [t6 <> t7] - ] - it "can consume scrolling backward by 1 month" $ do - scroll maxKey takeUpTo byMonth 1 (fromList ts) - `shouldBe` results - [ [t6 <> t7] - , [t4 <> t5] - , [t2 <> t3] - , [t0 <> t1] - ] - it "can consume scrolling forward by 1 year" $ do - scroll minKey takeAfter byYear 1 (fromList ts) - `shouldBe` results - [ [t0 <> t1 <> t2 <> t3] - , [t4 <> t5 <> t6 <> t7] - ] - it "can consume scrolling backward by 1 year" $ do - scroll maxKey takeUpTo byYear 1 (fromList ts) - `shouldBe` results - [ [t4 <> t5 <> t6 <> t7] - , [t0 <> t1 <> t2 <> t3] - ] - - describe "dropAfter function" $ do - it "works on empty" $ do - dropAfter @UTCTime @() (t "2021-01-01 00:00:00") (fromList []) - `shouldBe` fromList [] - it "drop a single" $ do - dropAfter (t "2021-01-01 00:00:00") (fromList [t0]) - `shouldBe` fromList [t0] - it "take one and drop the second, early cut" $ do - dropAfter (t "2021-01-01 00:00:00") (fromList [t0, t1]) - `shouldBe` fromList [t0] - it "take one and drop the second, late cut" $ do - dropAfter (t "2021-01-01 23:59:59") (fromList [t0, t1]) - `shouldBe` fromList [t0] - it "can take all" $ do - dropAfter (t "2021-01-02 00:00:00") (fromList [t0, t1]) - `shouldBe` fromList [t0, t1] - - describe "dropBefore function" $ do - it "works on empty" $ do - dropBefore @UTCTime @() (t "2021-01-01 00:00:00") (fromList []) - `shouldBe` fromList [] - it "drop a single" $ do - dropBefore (t "2021-01-01 00:00:01") (fromList [t0]) - `shouldBe` fromList [] - it "take second and drop the first, early cut" $ do - dropBefore (t "2021-01-01 00:00:01") (fromList [t0, t1]) - `shouldBe` fromList [t1] - it "take the second and drop the first, late cut" $ do - dropBefore (t "2021-01-02 00:00:00") (fromList [t0, t1]) - `shouldBe` fromList [t1] - it "can take all" $ do - dropBefore (t "2021-01-01 00:00:00") (fromList [t0, t1]) - `shouldBe` fromList [t0, t1] - - describe "TimedSeq semigroup" $ do - it "can append two sequences of distinct times" $ do - fromList [t0, t1] <> fromList [t2, t3] - `shouldBe` fromList [t0, t1, t2, t3] - it "can append two sequences of overlapping edges in time" $ do - fromList [t0, t1] <> fromList [t1, t2] - `shouldBe` fromList - [ t0 - , Timed (time t1) (monoid t1 <> monoid t1) - , t2 - ] - it "is used in fromList" $ do - fromList [t0, t1, t1, t2] - `shouldBe` fromList - [ t0 - , Timed (time t1) (monoid t1 <> monoid t1) - , t2 - ] - describe "fromList" $ do - it "is the inverse of toList for different ts" $ do - fromList (toList (fromList ts)) `shouldBe` fromList ts - it "is the inverse of toList for overlapping ts" $ do - let ts' = [t0, t1, t1, t2] - fromList (toList (fromList ts')) - `shouldBe` fromList ts' - - describe "toList" $ do - it "is not the inverse of fromList for overlapping ts" $ do - let ts' = [t0, t1, t1, t2] - toList (fromList ts') `shouldNotBe` ts' diff --git a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Pure/API/AddressSpec.hs b/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Pure/API/AddressSpec.hs deleted file mode 100644 index e4e57fee8b4..00000000000 --- a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Pure/API/AddressSpec.hs +++ /dev/null @@ -1,143 +0,0 @@ -{-# LANGUAGE QuasiQuotes #-} -module Cardano.Wallet.Deposit.Pure.API.AddressSpec - ( spec - ) -where - -import Prelude - -import Cardano.Wallet.Deposit.Pure.API.Address - ( DecodingError (..) - , decodeAddress - , encodeAddress - ) -import Cardano.Wallet.Read.Address - ( isBootstrapCompactAddr - , toShortByteString - ) -import Control.Monad - ( forM_ - ) -import Data.ByteString.Base58 - ( bitcoinAlphabet - , decodeBase58 - , encodeBase58 - ) -import Data.Either - ( isLeft - , isRight - ) -import Data.Function - ( (&) - ) -import Data.Maybe - ( isJust - ) -import Data.Text - ( Text - ) -import Test.Cardano.Ledger.Core.Arbitrary - () -import Test.Hspec - ( Spec - , describe - , it - , shouldBe - ) -import Test.QuickCheck - ( Arbitrary (..) - , Gen - , checkCoverage - , counterexample - , cover - , elements - , forAll - , label - , oneof - , property - , (===) - ) - -import qualified Codec.Binary.Bech32 as Bech32 -import qualified Codec.Binary.Bech32.TH as Bech32 -import qualified Data.ByteString.Short as SBS -import qualified Data.Text as T -import qualified Data.Text.Encoding as T - -spec :: Spec -spec = do - describe "address codec" $ do - it "rountrips correctly on random addresses" $ forAll arbitrary $ \x -> - decodeAddress (encodeAddress x) - === Right x - - it "decodeAddress text = Right addr ==> encodeAddress addr == text" - $ checkCoverage $ forAll genArbitrarilyEncodedAddress $ \text -> do - let getErrorLabel e = case e of - InvalidBech32Encoding _e -> "invalid bech32 encoding" - InvalidBase58Encoding -> "invalid base58 encoding" - InvalidHumanReadablePart _hrp -> "invalid hrp" - InvalidDataPart _ -> "invalid data part" - AddressFlavorMismatch -> "flavor mismatch" - AddressDecodingError _ -> "decoding error" - AddressNetworkMismatch -> "network mismatch" - - let res = decodeAddress text - case res of - Right addr -> label "success" $ encodeAddress addr === text - Left e -> label (getErrorLabel e) $ property True - & cover 0.2 (isLeft res) "failure" - & cover 0.2 (isRight res) "success" - - it "isBootstrapAddr decides whether bech32 or base58 encoding is used" - $ forAll arbitrary $ \addr -> - let - isBase58 = isJust . decodeBase58 bitcoinAlphabet . T.encodeUtf8 - isBech32 = isRight . Bech32.decodeLenient - - encodedAddr = encodeAddress addr - in - if isBootstrapCompactAddr addr - then property $ isBase58 encodedAddr - else property $ isBech32 encodedAddr - & counterexample (T.unpack encodedAddr) - - it "roundtrips correctly on some addresses from online examples" - $ do - let testCases = - [ "addr1z92l7rnra7sxjn5qv5fzc4fwsrrm29mgkleqj9a0y46j5lyjz4gwd3njhyqwntdkcm8rrgapudajydteywgtuvl6etjs9nqzg5" - , "addr_test1wppg9l6relcpls4u667twqyggkrpfrs5cdge9hhl9cv2upchtch0h" - , "37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3RFhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbva8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na" - ] - forM_ testCases $ \addr -> - encodeAddress <$> decodeAddress addr - `shouldBe` Right addr - - it "fails to decode addresses where the network tag doesn't match the bech32 hrp" $ do - let secretlyMainnetAddr = "addr_test1z92l7rnra7sxjn5qv5fzc4fwsrrm29mgkleqj9a0y46j5lyjz4gwd3njhyqwntdkcm8rrgapudajydteywgtuvl6etjshn59kk" - decodeAddress secretlyMainnetAddr - `shouldBe` Left AddressNetworkMismatch - --- | Generate 'Text' heavily biased towards values of incorrectly encoded --- addresses -genArbitrarilyEncodedAddress :: Gen Text -genArbitrarilyEncodedAddress = oneof - [ encodeAddrBech32 <$> genAddrHrp <*> arbitrary - , encodeAddrBase58 <$> arbitrary - ] - where - encodeAddrBech32 hrp addr = Bech32.encodeLenient hrp dataPart - where - bytes = SBS.fromShort $ toShortByteString addr - dataPart = Bech32.dataPartFromBytes bytes - - genAddrHrp = elements - [ [Bech32.humanReadablePart|addr|] - , [Bech32.humanReadablePart|addr_test|] - , [Bech32.humanReadablePart|notaddr|] - ] - - encodeAddrBase58 = T.decodeUtf8 - . encodeBase58 bitcoinAlphabet - . SBS.fromShort - . toShortByteString diff --git a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Pure/API/TransactionSpec.hs b/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Pure/API/TransactionSpec.hs deleted file mode 100644 index 66a08b50583..00000000000 --- a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Pure/API/TransactionSpec.hs +++ /dev/null @@ -1,239 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} - -module Cardano.Wallet.Deposit.Pure.API.TransactionSpec - ( spec - ) -where - -import Prelude - -import Cardano.Ledger.Api - ( ppMaxTxSizeL - , ppMaxValSizeL - ) -import Cardano.Ledger.BaseTypes - ( EpochSize (..) - ) -import qualified Cardano.Ledger.BaseTypes as Ledger -import qualified Cardano.Ledger.Core as Ledger -import qualified Cardano.Ledger.Shelley.API.Mempool as Ledger -import qualified Cardano.Ledger.Shelley.LedgerState as Ledger -import qualified Cardano.Ledger.Shelley.Rules as Ledger -import qualified Cardano.Slotting.EpochInfo as Slotting -import Cardano.Slotting.Time - ( SlotLength - , SystemStart (..) - , mkSlotLength - ) -import qualified Cardano.Wallet.Deposit.Pure.Address as Address -import Cardano.Wallet.Deposit.Pure.API.Address - ( encodeAddress - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( accountXPubFromCredentials - , createMnemonicFromWords - , credentialsFromMnemonics - ) -import Cardano.Wallet.Deposit.PureSpec - ( testOnWallet - ) -import Cardano.Wallet.Deposit.Read - ( Address - , Conway - , NetworkTag (..) - , UTxO - , mkEnterpriseAddress - ) -import Cardano.Wallet.Deposit.Testing.DSL - ( assert - , balance - , block - , deposit - , existsTx - , rollForward - , sign - , spend - , utxo - , wallet - ) -import Cardano.Wallet.Deposit.Write - ( Tx - ) -import qualified Cardano.Wallet.Deposit.Write as Write -import Cardano.Wallet.Read - ( NetworkId (..) - ) -import qualified Cardano.Wallet.Read as Read -import Control.Lens - ( (&) - , (.~) - ) -import qualified Data.ByteString.Short as SBS -import Data.Default - ( Default (..) - ) -import Data.Maybe - ( fromMaybe - ) -import Data.Text - ( Text - ) -import qualified Data.Text.Lazy as TL -import Data.Time.Clock.POSIX - ( posixSecondsToUTCTime - ) -import Test.Cardano.Ledger.Core.Arbitrary - () -import Test.Hspec - ( Spec - , describe - , it - , shouldBe - ) -import Text.Pretty.Simple - ( pShow - ) - -address :: Address -address = mockAddress - -mockAddress :: Address -mockAddress = - mkEnterpriseAddress - MainnetTag - (SBS.toShort "12345678901234567890123456789012") - -defaultPParams :: Ledger.PParams Conway -defaultPParams = - def - & ppMaxTxSizeL .~ 16_384 - & ppMaxValSizeL .~ 1_000_000_000 - --- | Create a new ledger env from given protocol parameters. -newLedgerEnv :: Ledger.PParams Conway -> Ledger.LedgerEnv Conway -newLedgerEnv protocolParams = - Ledger.LedgerEnv - { Ledger.ledgerSlotNo = 0 - , -- NOTE: This can probably stay at 0 forever. This is used internally by the - -- node's mempool to keep track of transaction seen from peers. Transactions - -- in Hydra do not go through the node's mempool and follow a different - -- consensus path so this will remain unused. - Ledger.ledgerIx = minBound - , -- NOTE: This keeps track of the ledger's treasury and reserve which are - -- both unused in Hydra. There might be room for interesting features in the - -- future with these two but for now, we'll consider them empty. - Ledger.ledgerAccount = Ledger.AccountState mempty mempty - , Ledger.ledgerPp = protocolParams - , Ledger.ledgerMempool = False - } - -defaultLedgerEnv :: Ledger.LedgerEnv Conway -defaultLedgerEnv = newLedgerEnv defaultPParams - -defaultGlobals :: Ledger.Globals -defaultGlobals = - Ledger.Globals - { Ledger.epochInfo = Slotting.fixedEpochInfo epochSize slotLength - , Ledger.slotsPerKESPeriod = 20 - , Ledger.stabilityWindow = 33 - , Ledger.randomnessStabilisationWindow = 33 - , Ledger.securityParameter = 10 - , Ledger.maxKESEvo = 10 - , Ledger.quorum = 5 - , Ledger.maxLovelaceSupply = 45 * 1000 * 1000 * 1000 * 1000 * 1000 - , Ledger.activeSlotCoeff = - Ledger.mkActiveSlotCoeff . unsafeBoundRational $ 0.9 - , Ledger.networkId = Ledger.Mainnet - , Ledger.systemStart = SystemStart $ posixSecondsToUTCTime 0 - } - where - unsafeBoundRational r = - fromMaybe (error $ "Could not convert from Rational: " <> show r) - $ Ledger.boundRational r - -epochSize :: EpochSize -epochSize = EpochSize 100 - -slotLength :: SlotLength -slotLength = mkSlotLength 1 - -applyTx - :: UTxO - -> Write.Tx - -> Either - (Ledger.ApplyTxError Conway) - () -applyTx utxos (Read.Tx tx) = - case Ledger.applyTx defaultGlobals defaultLedgerEnv memPoolState tx of - Left err -> Left err - Right _ -> Right () - where - memPoolState = - Ledger.LedgerState - { Ledger.lsUTxOState = - def{Ledger.utxosUtxo = Write.toConwayUTxO utxos} - , Ledger.lsCertState = def - } -newtype Ledger = Ledger - { validate :: Tx -> Either (Ledger.ApplyTxError Conway) () - } - -ledgerFrom :: UTxO -> Ledger -ledgerFrom = Ledger . applyTx - -accepts :: Ledger -> Tx -> IO () -accepts l t = case validate l t of - Left err -> - error - $ TL.unpack - $ "Transaction was not accepted by the ledger: \n" - <> pShow defaultPParams - <> "\n" - <> pShow t - <> "\n" - <> pShow err - Right _ -> pure () - -mnemonics :: Text -mnemonics = "vital minimum victory start lunch find city peanut shiver soft hedgehog artwork mushroom loud found" - -spec :: Spec -spec = do - describe "balanced transaction" $ do - it "has correct witness for one tx-in" - $ testOnWallet - $ do - wallet 17 mnemonics "passphrase" - tx1 <- existsTx - u1 <- deposit tx1 1 100 - b1 <- block [tx1] - rollForward [b1] - spending <- existsTx - spend spending address 10 - balanced <- balance spending - utxos <- utxo u1 - signedTx <- sign balanced "passphrase" - assert $ ledgerFrom utxos `accepts` signedTx - - -- cat root1.prv - -- | cardano-address key child 1857H/1815H/0H/0/0 \ - -- | cardano-address key public --with-chain-code \ - -- | cardano-address address payment --network-tag mainnet - describe "generated address match golden cases" $ do - it "with empty passphrase in mainnet" $ do - let - Right seed = createMnemonicFromWords mnemonics - address0 = "addr1v8th5554xvd2us9hwh72p3yt9rg7uw9v7tk49t3yw3wrcgc3drxft" - creds = credentialsFromMnemonics seed mempty - xpub = accountXPubFromCredentials creds - addr = - encodeAddress - $ snd - $ head - $ Address.listCustomers - $ Address.fromXPubAndCount Mainnet xpub 1 - - addr `shouldBe` address0 diff --git a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/PureSpec.hs b/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/PureSpec.hs deleted file mode 100644 index efb3844c038..00000000000 --- a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/PureSpec.hs +++ /dev/null @@ -1,346 +0,0 @@ -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE NumericUnderscores #-} -{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Property tests for the deposit wallet. -module Cardano.Wallet.Deposit.PureSpec - ( spec - , testOnWallet - ) where - -import Prelude - -import Cardano.Mnemonic - ( SomeMnemonic - ) -import Cardano.Wallet.Deposit.Pure - ( Credentials - ) -import Cardano.Wallet.Deposit.Pure.API.TxHistory - ( LookupTimeFromSlot - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( createMnemonicFromWords - , credentialsFromMnemonics - ) -import Cardano.Wallet.Deposit.Testing.DSL - ( InterpreterState (..) - , ScenarioP - , assert - , availableBalance - , block - , deposit - , deposit_ - , existsTx - , historyByCustomer - , historyByTime - , interpret - , newHistoryByTime - , rollBackward - , rollForward - , withdrawal - ) -import Cardano.Wallet.Deposit.Testing.DSL.ByTime - ( atBlock - , byCustomerFromByTime - , deposited - , forCustomer - , inTx - , newByTime - , withdrawn - ) -import Cardano.Wallet.Deposit.Time - ( unsafeUTCTimeOfSlot - ) -import Control.Monad.Trans.State - ( StateT - ) -import Data.Maybe - ( fromJust - ) -import Data.Time - ( UTCTime - ) -import Test.Hspec - ( Spec - , describe - , it - , shouldBe - ) -import Test.QuickCheck - ( Property - , (.&&.) - , (=/=) - , (===) - ) - -import qualified Cardano.Wallet.Deposit.Pure as Wallet -import qualified Cardano.Wallet.Deposit.Pure.UTxO as UTxO -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Cardano.Wallet.Deposit.Write as Write -import qualified Data.Map.Strict as Map -import qualified Data.Set as Set - -timeFromSlot :: LookupTimeFromSlot -timeFromSlot = unsafeUTCTimeOfSlot - -unsafeTimeForSlot :: Read.Slot -> Read.WithOrigin UTCTime -unsafeTimeForSlot = fromJust . timeFromSlot - -testOnWallet - :: ScenarioP - (IO ()) - (StateT (Wallet.WalletState, InterpreterState) IO) - () - -> IO () -testOnWallet = - interpret - emptyWalletWith17Addresses - id - unsafeTimeForSlot - -spec :: Spec -spec = do - describe "UTxO availableBalance" $ do - it - "rollForward twice" - prop_availableBalance_rollForward_twice - it - "rollBackward . rollForward" - prop_availableBalance_rollForward_rollBackward - describe "history by time" $ do - it "is empty after initialization" - $ testOnWallet - $ do - ht0 <- historyByTime - assert $ ht0 `shouldBe` mempty - hc0 <- historyByCustomer - assert $ hc0 `shouldBe` mempty - it "reports a tx after a rollforward" - $ testOnWallet - $ do - tx1 <- existsTx - deposit_ tx1 1 100 - b1 <- block [tx1] - rollForward [b1] - h1 <- historyByTime - h1' <- newHistoryByTime $ newByTime $ do - atBlock b1 $ do - forCustomer 1 $ do - inTx tx1 $ deposited 100 - assert $ h1 `shouldBe` h1' - hc1 <- historyByCustomer - assert $ hc1 `shouldBe` byCustomerFromByTime h1' - balance <- availableBalance - assert $ balance `shouldBe` 100_000_000 - it "reports multiple blocks after a rollforward" - $ testOnWallet - $ do - tx1 <- existsTx - deposit_ tx1 1 100 - b1 <- block [tx1] - tx2 <- existsTx - deposit_ tx2 1 200 - b2 <- block [tx2] - rollForward [b1, b2] - h1 <- historyByTime - h1' <- newHistoryByTime $ newByTime $ do - atBlock b1 $ do - forCustomer 1 $ do - inTx tx1 $ deposited 100 - atBlock b2 $ do - forCustomer 1 $ do - inTx tx2 $ deposited 200 - assert $ h1 `shouldBe` h1' - hc1 <- historyByCustomer - assert $ hc1 `shouldBe` byCustomerFromByTime h1' - balance <- availableBalance - assert $ balance `shouldBe` 300_000_000 - it "reports withdrawals in separate blocks from deposits" - $ testOnWallet - $ do - tx1 <- existsTx - w1 <- deposit tx1 1 100 - b1 <- block [tx1] - tx2 <- existsTx - withdrawal tx2 w1 - b2 <- block [tx2] - rollForward [b1, b2] - h1 <- historyByTime - h1' <- newHistoryByTime $ newByTime $ do - atBlock b1 $ do - forCustomer 1 $ do - inTx tx1 $ deposited 100 - atBlock b2 $ do - forCustomer 1 $ do - inTx tx2 $ withdrawn 100 - assert $ h1 `shouldBe` h1' - hc1 <- historyByCustomer - assert $ hc1 `shouldBe` byCustomerFromByTime h1' - balance <- availableBalance - assert $ balance `shouldBe` 0 - it "reports withdrawals in the same block as deposits" - $ testOnWallet - $ do - tx1 <- existsTx - w1 <- deposit tx1 1 100 - tx2 <- existsTx - withdrawal tx2 w1 - b1 <- block [tx1, tx2] - rollForward [b1] - h1 <- historyByTime - h1' <- newHistoryByTime $ newByTime $ do - atBlock b1 $ do - forCustomer 1 $ do - inTx tx1 $ deposited 100 - inTx tx2 $ withdrawn 100 - assert $ h1 `shouldBe` h1' - hc1 <- historyByCustomer - assert $ hc1 `shouldBe` byCustomerFromByTime h1' - balance <- availableBalance - assert $ balance `shouldBe` 0 - - it "is empty after a full rollback" - $ testOnWallet - $ do - tx1 <- existsTx - deposit_ tx1 1 100 - b1 <- block [tx1] - rollForward [b1] - rollBackward Nothing - h1 <- historyByTime - assert $ h1 `shouldBe` mempty - hc1 <- historyByCustomer - assert $ hc1 `shouldBe` mempty - balance <- availableBalance - assert $ balance `shouldBe` 0 - it "contains the blocks not rolled back after a partial rollback" - $ testOnWallet - $ do - tx1 <- existsTx - deposit_ tx1 1 100 - b1 <- block [tx1] - tx2 <- existsTx - deposit_ tx2 1 200 - b2 <- block [tx2] - rollForward [b1, b2] - rollBackward $ Just b1 - h1 <- historyByTime - h1' <- newHistoryByTime $ newByTime $ do - atBlock b1 $ do - forCustomer 1 $ do - inTx tx1 $ deposited 100 - assert $ h1 `shouldBe` h1' - hc1 <- historyByCustomer - assert $ hc1 `shouldBe` byCustomerFromByTime h1' - balance <- availableBalance - assert $ balance `shouldBe` 100_000_000 - -{----------------------------------------------------------------------------- - Properties -------------------------------------------------------------------------------} -prop_availableBalance_rollForward_twice :: Property -prop_availableBalance_rollForward_twice = - Wallet.availableBalance w2 === Write.mkAda 3 - where - w0 = emptyWalletWith17Addresses - Just addr1 = Wallet.customerAddress 1 w0 - Just addr2 = Wallet.customerAddress 2 w0 - - tx1 = payFromFaucet [(addr1, Write.mkAda 1)] - block1 = Read.mockNextBlock Read.GenesisPoint [tx1] - chainPoint1 = Read.getChainPoint block1 - w1 = Wallet.rollForwardOne timeFromSlot (Read.EraValue block1) w0 - - tx2 = payFromFaucet [(addr2, Write.mkAda 2)] - block2 = Read.mockNextBlock chainPoint1 [tx2] - w2 = Wallet.rollForwardOne timeFromSlot (Read.EraValue block2) w1 - -prop_availableBalance_rollForward_rollBackward :: Property -prop_availableBalance_rollForward_rollBackward = - Wallet.availableBalance - (fst $ Wallet.rollBackward timeFromSlot chainPoint0 w3) - === Wallet.availableBalance w0 - .&&. Wallet.availableBalance - (fst $ Wallet.rollBackward timeFromSlot chainPoint1 w3) - === Wallet.availableBalance w1 - .&&. Wallet.availableBalance - (fst $ Wallet.rollBackward timeFromSlot chainPoint2 w3) - === Wallet.availableBalance w2 - .&&. Wallet.availableBalance w3 - =/= Wallet.availableBalance w2 - .&&. Wallet.availableBalance w3 - `Read.lessOrEqual` Wallet.availableBalance w2 - where - w0 = emptyWalletWith17Addresses - Just addr1 = Wallet.customerAddress 1 w0 - Just addr2 = Wallet.customerAddress 2 w0 - chainPoint0 = Read.GenesisPoint - - tx1 = payFromFaucet [(addr1, Write.mkAda 1)] - block1 = Read.mockNextBlock chainPoint0 [tx1] - w1 = Wallet.rollForwardOne timeFromSlot (Read.EraValue block1) w0 - chainPoint1 = Read.getChainPoint block1 - - tx2 = payFromFaucet [(addr2, Write.mkAda 2)] - block2 = Read.mockNextBlock chainPoint1 [tx2] - chainPoint2 = Read.getChainPoint block2 - w2 = Wallet.rollForwardOne timeFromSlot (Read.EraValue block2) w1 - - tx3 = spendOneTxOut (Wallet.availableUTxO w2) - block3 = Read.mockNextBlock chainPoint2 [tx3] - w3 = Wallet.rollForwardOne timeFromSlot (Read.EraValue block3) w2 - -emptyWalletWith17Addresses :: Wallet.WalletState -emptyWalletWith17Addresses = - Wallet.fromCredentialsAndGenesis testCredentials 17 testGenesis - -seed :: SomeMnemonic -seed = case createMnemonicFromWords - "vital minimum victory start lunch find city peanut shiver soft hedgehog artwork mushroom loud found" - of - Right seed' -> seed' - Left e -> error $ show e - -testCredentials :: Credentials -testCredentials = - credentialsFromMnemonics seed mempty - -{----------------------------------------------------------------------------- - Test blockchain -------------------------------------------------------------------------------} - -testGenesis :: Read.GenesisData -testGenesis = Read.mockGenesisDataMainnet - -spendOneTxOut :: UTxO.UTxO -> Write.Tx -spendOneTxOut utxo = - Write.mkTx txBody - where - txBody = - Write.TxBody - { Write.spendInputs = Set.singleton . fst . head $ Map.toList utxo - , Write.collInputs = mempty - , Write.txouts = Map.empty - , Write.collRet = Nothing - , Write.expirySlot = Nothing - } - -payFromFaucet :: [(Write.Address, Write.Value)] -> Write.Tx -payFromFaucet destinations = - Write.mkTx txBody - where - toTxOut (addr, value) = Write.mkTxOut addr value - txBody = - Write.TxBody - { Write.spendInputs = mempty - , Write.collInputs = mempty - , Write.txouts = - Map.fromList $ zip [toEnum 0 ..] $ map toTxOut destinations - , Write.collRet = Nothing - , Write.expirySlot = Nothing - } diff --git a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/RESTSpec.hs b/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/RESTSpec.hs deleted file mode 100644 index 76d394b15d3..00000000000 --- a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/RESTSpec.hs +++ /dev/null @@ -1,250 +0,0 @@ -{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} - -module Cardano.Wallet.Deposit.RESTSpec - ( spec - ) -where - -import Prelude - -import Cardano.Crypto.Wallet - ( sign - , verify - , xPrvChangePass - ) -import Cardano.Mnemonic - ( SomeMnemonic - ) -import Cardano.Wallet.Deposit.IO - ( WalletBootEnv (WalletBootEnv) - ) -import Cardano.Wallet.Deposit.IO.Resource - ( ErrResourceMissing (..) - , withResource - ) -import Cardano.Wallet.Deposit.Pure.State.Creation - ( Credentials - , accountXPubFromCredentials - , createMnemonicFromWords - , credentialsFromMnemonics - , deriveAccountXPrv - , rootXPrvFromCredentials - ) -import Cardano.Wallet.Deposit.REST - ( ErrCreatingDatabase (..) - , ErrDatabase (..) - , ErrLoadingDatabase (..) - , ErrWalletResource (..) - , WalletResourceM - , availableBalance - , initWallet - , loadWallet - , runWalletResourceM - , walletExists - ) -import Codec.Serialise - ( deserialise - , serialise - ) -import Control.Concurrent - ( threadDelay - ) -import Control.Monad.IO.Class - ( MonadIO (..) - ) -import Control.Monad.Trans.Cont - ( cont - , evalCont - ) -import Control.Tracer - ( nullTracer - ) -import Data.ByteString - ( ByteString - ) -import Data.Maybe - ( fromJust - ) -import Data.Text - ( Text - ) -import System.IO.Temp - ( withSystemTempDirectory - ) -import Test.Hspec - ( Spec - , describe - , it - , shouldBe - ) -import Test.QuickCheck - ( Gen - , arbitrary - , elements - , forAll - , listOf - , (===) - ) - -import qualified Cardano.Wallet.Deposit.Read as Read -import qualified Data.ByteString.Char8 as B8 -import qualified Data.Text as T -import qualified Data.Text.Encoding as T - -fakeBootEnv :: WalletBootEnv IO -fakeBootEnv = WalletBootEnv nullTracer Read.mockGenesisDataMainnet undefined - -seed :: SomeMnemonic -Right seed = - createMnemonicFromWords - "vital minimum victory start lunch find city peanut shiver soft hedgehog artwork mushroom loud found" - -credentials :: Credentials -credentials = credentialsFromMnemonics seed mempty - -letItInitialize :: WalletResourceM () -letItInitialize = liftIO $ threadDelay 100000 - -onSuccess :: (Show e, MonadFail m) => Either e a -> (a -> m b) -> m b -onSuccess (Left e) _ = fail $ show e -onSuccess (Right a) f = f a - -matchEmptyValue :: Show e => Either e Read.Value -> IO () -matchEmptyValue x = onSuccess x $ \v -> v `shouldBe` mempty - -withWallet :: WalletResourceM a -> IO (Either ErrWalletResource a) -withWallet f = withResource $ runWalletResourceM f - -withInitializedWallet - :: FilePath - -> WalletResourceM a - -> IO (Either ErrWalletResource a) -withInitializedWallet dir f = withWallet $ do - initWallet nullTracer nullTracer fakeBootEnv dir credentials 0 - letItInitialize - f - -withLoadedWallet - :: FilePath - -> WalletResourceM a - -> IO (Either ErrWalletResource a) -withLoadedWallet dir f = withWallet $ do - loadWallet nullTracer fakeBootEnv dir - letItInitialize - f - -doNothing :: WalletResourceM () -doNothing = pure () - -inADirectory :: (FilePath -> IO a) -> IO a -inADirectory = withSystemTempDirectory "deposit-rest" - -byteStringGen :: Gen ByteString -byteStringGen = B8.pack <$> listOf arbitrary - -textGen :: Gen Text -textGen = T.pack <$> listOf arbitrary - -words15 :: [Text] -words15 = - [ "soap retire song hat major steak stuff daughter half scorpion please brisk decade hill song" - , "sure cannon broom caution artist legend boring reveal scene rubber weapon chest page clog fine" - , "fruit garden saddle upper huge educate fabric ocean bamboo verb iron apple have deposit trap" - ] - -credentialsGen :: Gen (Credentials, Text) -credentialsGen = do - mnemonics' <- elements words15 - case createMnemonicFromWords mnemonics' of - Left e -> error $ "Invalid mnemonics: " <> show e - Right seed' -> do - passphrase' <- textGen - pure (credentialsFromMnemonics seed' passphrase', passphrase') - -spec :: Spec -spec = do - describe "XPub" $ do - it "can be serialised and deserialised" $ do - forAll credentialsGen $ \(credentials', _) -> - deserialise (serialise $ accountXPubFromCredentials credentials') - === accountXPubFromCredentials credentials' - describe "XPrv" $ do - it "can be serialised and deserialised" $ do - forAll credentialsGen $ \(credentials', _) -> - deserialise (serialise $ rootXPrvFromCredentials credentials') - === rootXPrvFromCredentials credentials' - describe "Credentials" $ do - it "can be serialised and deserialised" $ do - forAll credentialsGen $ \(credentials', _) -> - deserialise (serialise credentials') === credentials' - describe "Credentials with mnemonics" $ do - it "can sign and verify a message" $ evalCont $ do - (credentials', passphrase') <- cont $ forAll credentialsGen - message <- cont $ forAll byteStringGen - let - decryptXPrv = - xPrvChangePass (T.encodeUtf8 passphrase') B8.empty - xprv = - deriveAccountXPrv - $ decryptXPrv - $ fromJust - $ rootXPrvFromCredentials credentials' - sig = sign B8.empty xprv message - pure - $ verify (accountXPubFromCredentials credentials') message sig - === True - - describe "REST Deposit interface" $ do - it "can initialize a wallet" - $ inADirectory - $ \dir -> do - val <- withInitializedWallet dir availableBalance - matchEmptyValue val - it "can load an existing wallet" - $ inADirectory - $ \dir -> do - val <- withInitializedWallet dir availableBalance - onSuccess val $ \_ -> do - val' <- withLoadedWallet dir availableBalance - matchEmptyValue val' - it "cannot re-initialize a wallet" - $ inADirectory - $ \dir -> do - val <- withInitializedWallet dir doNothing - onSuccess val $ \_ -> do - val' <- withInitializedWallet dir availableBalance - case val' of - Left - ( ErrNoWallet - ( ErrFailedToInitialize - ( ErrCreatingDatabase - (ErrDatabaseAlreadyExists fp) - ) - ) - ) - | dir == fp -> pure () - Left e -> fail $ show e - Right _ -> fail "Should have failed the query on re-init" - it "cannot load a non-existing wallet" - $ inADirectory - $ \dir -> do - val <- withLoadedWallet dir availableBalance - case val of - Left - ( ErrNoWallet - ( ErrFailedToInitialize - ( ErrLoadingDatabase - (ErrDatabaseNotFound fp) - ) - ) - ) - | dir == fp -> pure () - Left e -> fail $ show e - Right _ -> fail "Should have failed the query on load" - it "can check if a wallet is present on disk" - $ inADirectory - $ \dir -> do - r <- withInitializedWallet dir doNothing - onSuccess r $ \_ -> do - presence <- walletExists dir - presence `shouldBe` True diff --git a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Write/KeysSpec.hs b/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Write/KeysSpec.hs deleted file mode 100644 index 872cbe6c9ed..00000000000 --- a/lib/deposit-wallet/test/unit/Cardano/Wallet/Deposit/Write/KeysSpec.hs +++ /dev/null @@ -1,114 +0,0 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE PackageImports #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} -{-# OPTIONS_GHC -Wno-orphans #-} - --- | --- Copyright: © 2024 Cardano Foundation --- License: Apache-2.0 --- --- Property tests for the deposit wallet. -module Cardano.Wallet.Deposit.Write.KeysSpec - ( spec - ) where - -import Prelude - -import Cardano.Crypto.Wallet - ( generate - ) -import Cardano.Wallet.Address.BIP32_Ed25519 - ( XPrv - , XPub - , sign - , toXPub - ) -import "customer-deposit-wallet-pure" Cardano.Wallet.Address.Encoding - ( EnterpriseAddr (..) - , NetworkTag (..) - , compactAddrFromEnterpriseAddr - , credentialFromXPub - ) -import Cardano.Wallet.Deposit.Write.Keys - ( enterpriseAddressFromVKey - , signedDSIGNfromXSignature - , vkeyFromXPub - ) -import Test.Hspec - ( Spec - , describe - , it - ) -import Test.QuickCheck - ( Arbitrary (..) - , Blind (..) - , Property - , elements - , property - , vectorOf - , withMaxSuccess - , (===) - ) - -import qualified Cardano.Crypto.Hash.Blake2b as Hash -import qualified Cardano.Crypto.Hash.Class as Hash -import qualified Cardano.Ledger.BaseTypes as L -import qualified Cardano.Ledger.Hashes as L -import qualified Cardano.Ledger.Keys as L -import qualified Cardano.Wallet.Read as Read -import qualified Data.ByteString as BS - -{----------------------------------------------------------------------------- - Spec -------------------------------------------------------------------------------} -spec :: Spec -spec = do - describe "commutes with ledger" $ do - it "address" $ lessCryptography $ property $ - \xpub networkTag -> - let network = toLedgerNetwork networkTag - in enterpriseAddressFromVKey network (vkeyFromXPub xpub) - === enterpriseAddressFromXPub networkTag xpub - - it "verify" $ lessCryptography $ property $ - \(Blind xprv) hash -> - let xpub = toXPub xprv - xsig = sign xprv (Hash.hashToBytes hash) - in - True === - L.verifySignedDSIGN - (vkeyFromXPub xpub) - hash - (signedDSIGNfromXSignature xsig) - -lessCryptography :: Property -> Property -lessCryptography = withMaxSuccess 20 - -{----------------------------------------------------------------------------- - Helper functions -------------------------------------------------------------------------------} -enterpriseAddressFromXPub :: NetworkTag -> XPub -> Read.CompactAddr -enterpriseAddressFromXPub networkTag = - compactAddrFromEnterpriseAddr - . EnterpriseAddrC networkTag - . credentialFromXPub - -toLedgerNetwork :: NetworkTag -> L.Network -toLedgerNetwork MainnetTag = L.Mainnet -toLedgerNetwork TestnetTag = L.Testnet - -instance Arbitrary NetworkTag where - arbitrary = elements [MainnetTag, TestnetTag] - -instance Arbitrary XPrv where - arbitrary = generate . BS.pack <$> vectorOf 100 arbitrary <*> pure BS.empty - -instance Arbitrary XPub where - arbitrary = toXPub <$> arbitrary - -instance Arbitrary (Hash.Hash Hash.Blake2b_256 L.EraIndependentTxBody) where - arbitrary = do - bytes <- BS.pack <$> vectorOf (32) arbitrary - let Just hash = Hash.hashFromBytes bytes - pure hash diff --git a/lib/deposit-wallet/test/unit/Spec.hs b/lib/deposit-wallet/test/unit/Spec.hs deleted file mode 100644 index 5416ef6a866..00000000000 --- a/lib/deposit-wallet/test/unit/Spec.hs +++ /dev/null @@ -1 +0,0 @@ -{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-} diff --git a/lib/deposit-wallet/test/unit/test-suite-unit.hs b/lib/deposit-wallet/test/unit/test-suite-unit.hs deleted file mode 100644 index 66edcab2e95..00000000000 --- a/lib/deposit-wallet/test/unit/test-suite-unit.hs +++ /dev/null @@ -1,15 +0,0 @@ -module Main where - -import Prelude - -import Main.Utf8 - ( withUtf8 - ) -import Test.Hspec.Extra - ( hspecMain - ) - -import qualified Spec - -main :: IO () -main = withUtf8 $ hspecMain Spec.spec diff --git a/nix/project-package-list.nix b/nix/project-package-list.nix index fd1ec47c8d5..ceff1329d92 100644 --- a/nix/project-package-list.nix +++ b/nix/project-package-list.nix @@ -1 +1 @@ -[ "address-derivation-discovery" "cardano-api-extra" "cardano-balance-tx" "cardano-coin-selection" "cardano-numeric" "cardano-wallet" "cardano-wallet-api" "cardano-wallet-application-extras" "cardano-wallet-benchmarks" "cardano-wallet-blackbox-benchmarks" "cardano-wallet-buildkite" "cardano-wallet-exe" "cardano-wallet-integration" "cardano-wallet-launcher" "cardano-wallet-network-layer" "cardano-wallet-primitive" "cardano-wallet-secrets" "cardano-wallet-test-utils" "cardano-wallet-ui" "cardano-wallet-unit" "crypto-primitives" "cardano-deposit-wallet" "delta-chain" "delta-store" "delta-table" "delta-types" "faucet" "iohk-monitoring-extra" "local-cluster" "std-gen-seed" "temporary-extra" "text-class" "wai-middleware-logging" ] +[ "address-derivation-discovery" "cardano-api-extra" "cardano-balance-tx" "cardano-coin-selection" "cardano-numeric" "cardano-wallet" "cardano-wallet-api" "cardano-wallet-application-extras" "cardano-wallet-benchmarks" "cardano-wallet-blackbox-benchmarks" "cardano-wallet-buildkite" "cardano-wallet-exe" "cardano-wallet-integration" "cardano-wallet-launcher" "cardano-wallet-network-layer" "cardano-wallet-primitive" "cardano-wallet-secrets" "cardano-wallet-test-utils" "cardano-wallet-ui" "cardano-wallet-unit" "crypto-primitives" "delta-chain" "delta-store" "delta-table" "delta-types" "faucet" "iohk-monitoring-extra" "local-cluster" "std-gen-seed" "temporary-extra" "text-class" "wai-middleware-logging" ]