Fix assets placement for binary #91
26 changed files with 9031 additions and 1249 deletions
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,4 +1,4 @@
|
|||
[submodule "zcash-haskell"]
|
||||
path = zcash-haskell
|
||||
url = https://git.vergara.tech/Vergara_Tech/zcash-haskell.git
|
||||
branch = milestone2
|
||||
branch = master
|
||||
|
|
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.7.1.0-beta]
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed workaround to obtain block time
|
||||
|
||||
## [0.7.0.0-beta]
|
||||
|
||||
### Added
|
||||
|
||||
- RPC module
|
||||
- OpenRPC specification
|
||||
- `listwallets` RPC method
|
||||
- `listaccounts` RPC method
|
||||
- `listaddresses` RPC method
|
||||
- `listreceived` RPC method
|
||||
- `getbalance` RPC method
|
||||
- `getnewwallet` RPC method
|
||||
- `getnewaccount` RPC method
|
||||
- `getnewaddress` RPC method
|
||||
- `getoperationstatus` RPC method
|
||||
- `sendmany` RPC method
|
||||
- Function `prepareTxV2` implementing `PrivacyPolicy`
|
||||
- Support for TEX addresses
|
||||
- Functionality to shield transparent balance
|
||||
- Functionality to de-shield shielded notes
|
||||
- Native commitment trees
|
||||
- Batch append to trees in O(log n)
|
||||
|
||||
### Changed
|
||||
|
||||
- Detection of changes in database schema for automatic re-scan
|
||||
- Block tracking for chain re-org detection
|
||||
- Refactored `ZcashPool`
|
||||
- Preventing write operations to occur during wallet sync
|
||||
|
||||
|
||||
## [0.6.0.0-beta]
|
||||
|
||||
### Added
|
||||
|
|
17
app/Main.hs
17
app/Main.hs
|
@ -19,8 +19,8 @@ import System.IO
|
|||
import Text.Read (readMaybe)
|
||||
import ZcashHaskell.Types
|
||||
import Zenith.CLI
|
||||
import Zenith.Core (clearSync, testSync)
|
||||
import Zenith.GUI (runZenithGUI)
|
||||
import Zenith.Scanner (clearSync, rescanZebra)
|
||||
import Zenith.Types (Config(..), ZcashAddress(..), ZcashPool(..), ZcashTx(..))
|
||||
import Zenith.Utils
|
||||
import Zenith.Zcashd
|
||||
|
@ -204,12 +204,15 @@ main :: IO ()
|
|||
main = do
|
||||
config <- load ["$(HOME)/Zenith/zenith.cfg"]
|
||||
args <- getArgs
|
||||
dbFilePath <- require config "dbFilePath"
|
||||
{-nodeUser <- require config "nodeUser"-}
|
||||
{-nodePwd <- require config "nodePwd"-}
|
||||
dbFileName <- require config "dbFileName"
|
||||
nodeUser <- require config "nodeUser"
|
||||
nodePwd <- require config "nodePwd"
|
||||
zebraPort <- require config "zebraPort"
|
||||
zebraHost <- require config "zebraHost"
|
||||
let myConfig = Config dbFilePath zebraHost zebraPort
|
||||
nodePort <- require config "nodePort"
|
||||
dbFP <- getZenithPath
|
||||
let dbFilePath = T.pack $ dbFP ++ dbFileName
|
||||
let myConfig = Config dbFilePath zebraHost zebraPort nodeUser nodePwd nodePort
|
||||
if not (null args)
|
||||
then do
|
||||
case head args
|
||||
|
@ -226,7 +229,8 @@ main = do
|
|||
of
|
||||
"gui" -> runZenithGUI myConfig
|
||||
"tui" -> runZenithTUI myConfig
|
||||
"rescan" -> clearSync myConfig
|
||||
"rescan" -> rescanZebra zebraHost zebraPort dbFilePath
|
||||
"resync" -> clearSync myConfig
|
||||
_ -> printUsage
|
||||
else printUsage
|
||||
|
||||
|
@ -236,4 +240,5 @@ printUsage = do
|
|||
putStrLn "Available commands:"
|
||||
{-putStrLn "legacy\tLegacy CLI for zcashd"-}
|
||||
putStrLn "tui\tTUI for zebrad"
|
||||
putStrLn "gui\tGUI for zebrad"
|
||||
putStrLn "rescan\tRescan the existing wallet(s)"
|
||||
|
|
91
app/Server.hs
Normal file
91
app/Server.hs
Normal file
|
@ -0,0 +1,91 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Server where
|
||||
|
||||
import Control.Concurrent (forkIO, threadDelay)
|
||||
import Control.Exception (throwIO, throwTo, try)
|
||||
import Control.Monad (forever, when)
|
||||
import Control.Monad.Logger (runNoLoggingT)
|
||||
import Data.Configurator
|
||||
import qualified Data.Text as T
|
||||
import Network.Wai.Handler.Warp (run)
|
||||
import Servant
|
||||
import System.Exit
|
||||
import System.Posix.Signals
|
||||
import ZcashHaskell.Types (ZebraGetBlockChainInfo(..), ZebraGetInfo(..))
|
||||
import Zenith.Core (checkBlockChain, checkZebra)
|
||||
import Zenith.DB (getWallets, initDb, initPool)
|
||||
import Zenith.RPC
|
||||
( State(..)
|
||||
, ZenithRPC(..)
|
||||
, authenticate
|
||||
, scanZebra
|
||||
, zenithServer
|
||||
)
|
||||
import Zenith.Scanner (rescanZebra)
|
||||
import Zenith.Types (Config(..))
|
||||
import Zenith.Utils (getZenithPath)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
config <- load ["$(HOME)/Zenith/zenith.cfg"]
|
||||
dbFileName <- require config "dbFileName"
|
||||
nodeUser <- require config "nodeUser"
|
||||
nodePwd <- require config "nodePwd"
|
||||
zebraPort <- require config "zebraPort"
|
||||
zebraHost <- require config "zebraHost"
|
||||
nodePort <- require config "nodePort"
|
||||
dbFP <- getZenithPath
|
||||
let dbFilePath = T.pack $ dbFP ++ dbFileName
|
||||
let myConfig = Config dbFilePath zebraHost zebraPort nodeUser nodePwd nodePort
|
||||
let ctx = authenticate myConfig :. EmptyContext
|
||||
w <- try $ checkZebra zebraHost zebraPort :: IO (Either IOError ZebraGetInfo)
|
||||
case w of
|
||||
Right zebra -> do
|
||||
bc <-
|
||||
try $ checkBlockChain zebraHost zebraPort :: IO
|
||||
(Either IOError ZebraGetBlockChainInfo)
|
||||
case bc of
|
||||
Left e1 -> throwIO e1
|
||||
Right chainInfo -> do
|
||||
x <- initDb dbFilePath
|
||||
case x of
|
||||
Left e2 -> throwIO $ userError e2
|
||||
Right x' -> do
|
||||
when x' $ rescanZebra zebraHost zebraPort dbFilePath
|
||||
pool <- runNoLoggingT $ initPool dbFilePath
|
||||
walList <- getWallets pool $ zgb_net chainInfo
|
||||
if not (null walList)
|
||||
then do
|
||||
scanThread <-
|
||||
forkIO $
|
||||
forever $ do
|
||||
_ <-
|
||||
scanZebra
|
||||
dbFilePath
|
||||
zebraHost
|
||||
zebraPort
|
||||
(zgb_net chainInfo)
|
||||
threadDelay 90000000
|
||||
putStrLn "Zenith RPC Server 0.8.0.0-beta"
|
||||
putStrLn "------------------------------"
|
||||
putStrLn $
|
||||
"Connected to " ++
|
||||
show (zgb_net chainInfo) ++
|
||||
" Zebra " ++
|
||||
T.unpack (zgi_build zebra) ++ " on port " ++ show zebraPort
|
||||
let myState =
|
||||
State
|
||||
(zgb_net chainInfo)
|
||||
zebraHost
|
||||
zebraPort
|
||||
dbFilePath
|
||||
(zgi_build zebra)
|
||||
(zgb_blocks chainInfo)
|
||||
run nodePort $
|
||||
serveWithContext
|
||||
(Proxy :: Proxy ZenithRPC)
|
||||
ctx
|
||||
(zenithServer myState)
|
||||
else putStrLn
|
||||
"No wallets available. Please start Zenith interactively to create a wallet"
|
|
@ -4,7 +4,7 @@ module ZenScan where
|
|||
|
||||
import Control.Monad.Logger (runNoLoggingT)
|
||||
import Data.Configurator
|
||||
import Zenith.Scanner (scanZebra)
|
||||
import Zenith.Scanner (rescanZebra)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
|
|
|
@ -9,19 +9,18 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.OneTuple ==0.4.2,
|
||||
any.OpenGLRaw ==3.3.4.1,
|
||||
OpenGLRaw -osandroid +usegles2 +useglxgetprocaddress +usenativewindowslibraries,
|
||||
any.QuickCheck ==2.14.3,
|
||||
any.QuickCheck ==2.15.0.1,
|
||||
QuickCheck -old-random +templatehaskell,
|
||||
any.RSA ==2.4.1,
|
||||
any.SHA ==1.6.4.4,
|
||||
SHA -exe,
|
||||
any.StateVar ==1.2.2,
|
||||
any.X11 ==1.10.3,
|
||||
X11 -pedantic,
|
||||
any.X11 ==1.9.2,
|
||||
any.adjunctions ==4.4.2,
|
||||
any.aeson ==2.2.3.0,
|
||||
aeson +ordered-keymap,
|
||||
any.alex ==3.5.1.0,
|
||||
any.ansi-terminal ==1.1.1,
|
||||
any.ansi-terminal ==1.1.2,
|
||||
ansi-terminal -example,
|
||||
any.ansi-terminal-types ==1.1,
|
||||
any.appar ==0.1.8,
|
||||
|
@ -39,11 +38,11 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
attoparsec -developer,
|
||||
any.attoparsec-aeson ==2.2.2.0,
|
||||
any.authenticate-oauth ==1.7,
|
||||
any.auto-update ==0.2.1,
|
||||
any.auto-update ==0.2.4,
|
||||
any.base ==4.18.2.1,
|
||||
any.base-compat ==0.14.0,
|
||||
any.base-compat-batteries ==0.14.0,
|
||||
any.base-orphans ==0.9.2,
|
||||
any.base-compat ==0.14.1,
|
||||
any.base-compat-batteries ==0.14.1,
|
||||
any.base-orphans ==0.9.3,
|
||||
any.base16 ==1.0,
|
||||
any.base16-bytestring ==1.0.2.0,
|
||||
any.base58-bytestring ==0.1.0,
|
||||
|
@ -59,18 +58,19 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.blaze-builder ==0.4.2.3,
|
||||
any.blaze-html ==0.9.2.0,
|
||||
any.blaze-markup ==0.8.3.0,
|
||||
any.boring ==0.2.2,
|
||||
boring +tagged,
|
||||
any.borsh ==0.3.0,
|
||||
any.brick ==2.4,
|
||||
any.brick ==2.6,
|
||||
brick -demos,
|
||||
any.bsb-http-chunked ==0.0.0.4,
|
||||
any.byteorder ==1.0.4,
|
||||
any.bytes ==0.17.3,
|
||||
any.bytes ==0.17.4,
|
||||
any.bytestring ==0.11.5.3,
|
||||
any.bytestring-builder ==0.10.8.2.0,
|
||||
bytestring-builder +bytestring_has_builder,
|
||||
any.bytestring-to-vector ==0.3.0.1,
|
||||
any.c2hs ==0.28.8,
|
||||
c2hs +base3 -regression,
|
||||
any.cabal-doctest ==1.0.10,
|
||||
any.cabal-doctest ==1.0.11,
|
||||
any.call-stack ==0.4.0,
|
||||
any.case-insensitive ==1.2.1.0,
|
||||
any.cborg ==0.2.10.0,
|
||||
|
@ -81,15 +81,16 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.clock ==0.8.4,
|
||||
clock -llvm,
|
||||
any.colour ==2.3.6,
|
||||
any.comonad ==5.0.8,
|
||||
any.comonad ==5.0.9,
|
||||
comonad +containers +distributive +indexed-traversable,
|
||||
any.concurrent-output ==1.10.21,
|
||||
any.conduit ==1.3.5,
|
||||
any.conduit ==1.3.6,
|
||||
any.conduit-extra ==1.3.6,
|
||||
any.config-ini ==0.2.7.0,
|
||||
config-ini -enable-doctests,
|
||||
any.configurator ==0.3.0.0,
|
||||
configurator -developer,
|
||||
any.constraints ==0.14.2,
|
||||
any.containers ==0.6.7,
|
||||
any.contravariant ==1.5.5,
|
||||
contravariant +semigroups +statevar +tagged,
|
||||
|
@ -97,22 +98,22 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.crypto-api ==0.13.3,
|
||||
crypto-api -all_cpolys,
|
||||
any.crypto-pubkey-types ==0.4.3,
|
||||
any.crypton ==1.0.0,
|
||||
any.cryptohash-md5 ==0.11.101.0,
|
||||
any.cryptohash-sha1 ==0.11.101.0,
|
||||
any.crypton ==1.0.1,
|
||||
crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes,
|
||||
any.crypton-connection ==0.4.1,
|
||||
any.crypton-connection ==0.4.3,
|
||||
any.crypton-x509 ==1.7.7,
|
||||
any.crypton-x509-store ==1.6.9,
|
||||
any.crypton-x509-system ==1.6.7,
|
||||
any.crypton-x509-validation ==1.6.12,
|
||||
any.crypton-x509-validation ==1.6.13,
|
||||
any.cryptonite ==0.30,
|
||||
cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes,
|
||||
any.data-clist ==0.2,
|
||||
any.data-default ==0.7.1.1,
|
||||
any.data-default-class ==0.1.2.0,
|
||||
any.data-default-instances-containers ==0.0.1,
|
||||
any.data-default-instances-dlist ==0.0.1,
|
||||
any.data-default-instances-old-locale ==0.0.1,
|
||||
any.data-default ==0.8.0.0,
|
||||
any.data-default-class ==0.2.0.0,
|
||||
any.data-fix ==0.3.4,
|
||||
any.dec ==0.0.6,
|
||||
any.deepseq ==1.4.8.1,
|
||||
any.directory ==1.3.8.4,
|
||||
any.distributive ==0.6.2.1,
|
||||
|
@ -124,11 +125,12 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.easy-file ==0.2.5,
|
||||
any.entropy ==0.4.1.10,
|
||||
entropy -donotgetentropy,
|
||||
any.envy ==2.1.3.0,
|
||||
any.esqueleto ==3.5.11.2,
|
||||
any.envy ==2.1.4.0,
|
||||
any.esqueleto ==3.5.13.1,
|
||||
any.exceptions ==0.10.7,
|
||||
any.extra ==1.7.16,
|
||||
any.fast-logger ==3.2.3,
|
||||
any.extra ==1.8,
|
||||
any.fast-logger ==3.2.5,
|
||||
any.file-embed ==0.0.16.0,
|
||||
any.filepath ==1.4.300.1,
|
||||
any.fixed ==0.3,
|
||||
any.foreign-rust ==0.1.0,
|
||||
|
@ -136,8 +138,6 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.formatting ==7.2.0,
|
||||
formatting -no-double-conversion,
|
||||
any.free ==5.2,
|
||||
any.generic-deriving ==1.14.5,
|
||||
generic-deriving +base-4-9,
|
||||
any.generically ==0.1.1,
|
||||
any.generics-sop ==0.5.1.4,
|
||||
any.ghc ==9.6.5,
|
||||
|
@ -147,48 +147,54 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.ghc-heap ==9.6.5,
|
||||
any.ghc-prim ==0.10.0,
|
||||
any.ghci ==9.6.5,
|
||||
any.half ==0.3.1,
|
||||
any.happy ==1.20.1.1,
|
||||
any.half ==0.3.2,
|
||||
any.happy ==2.1.3,
|
||||
any.happy-lib ==2.1.3,
|
||||
any.hashable ==1.4.7.0,
|
||||
hashable -arch-native +integer-gmp -random-initial-seed,
|
||||
any.haskell-lexer ==1.1.1,
|
||||
any.haskell-lexer ==1.1.2,
|
||||
any.haskoin-core ==1.1.0,
|
||||
any.hexstring ==0.12.1.0,
|
||||
any.hourglass ==0.2.12,
|
||||
any.hpc ==0.6.2.0,
|
||||
any.hsc2hs ==0.68.10,
|
||||
hsc2hs -in-ghc-tree,
|
||||
any.hspec ==2.11.9,
|
||||
any.hspec-core ==2.11.9,
|
||||
any.hspec-discover ==2.11.9,
|
||||
any.hspec ==2.11.10,
|
||||
any.hspec-core ==2.11.10,
|
||||
any.hspec-discover ==2.11.10,
|
||||
any.hspec-expectations ==0.8.4,
|
||||
any.http-api-data ==0.6.1,
|
||||
http-api-data -use-text-show,
|
||||
any.http-client ==0.7.17,
|
||||
http-client +network-uri,
|
||||
any.http-client-tls ==0.3.6.3,
|
||||
any.http-conduit ==2.3.8.3,
|
||||
any.http-client-tls ==0.3.6.4,
|
||||
any.http-conduit ==2.3.9.1,
|
||||
http-conduit +aeson,
|
||||
any.http-date ==0.0.11,
|
||||
any.http-media ==0.8.1.1,
|
||||
any.http-semantics ==0.3.0,
|
||||
any.http-types ==0.12.4,
|
||||
any.http2 ==5.3.9,
|
||||
http2 -devel -h2spec,
|
||||
any.indexed-traversable ==0.1.4,
|
||||
any.indexed-traversable-instances ==0.1.2,
|
||||
any.integer-conversion ==0.1.1,
|
||||
any.integer-gmp ==1.1,
|
||||
any.integer-logarithms ==1.0.3.1,
|
||||
integer-logarithms -check-bounds +integer-gmp,
|
||||
any.invariant ==0.6.3,
|
||||
any.iproute ==1.7.12,
|
||||
any.invariant ==0.6.4,
|
||||
any.iproute ==1.7.15,
|
||||
any.kan-extensions ==5.2.6,
|
||||
any.language-c ==0.9.3,
|
||||
language-c -allwarnings +iecfpextension +usebytestrings,
|
||||
any.language-c ==0.10.0,
|
||||
language-c +iecfpextension +usebytestrings,
|
||||
any.lens ==5.3.2,
|
||||
lens -benchmark-uniplate -dump-splices +inlining -j +test-hunit +test-properties +test-templates +trustworthy,
|
||||
any.lens-aeson ==1.2.3,
|
||||
any.lift-type ==0.1.1.1,
|
||||
any.lift-type ==0.1.2.0,
|
||||
any.lifted-base ==0.2.3.12,
|
||||
any.linear ==1.22,
|
||||
linear -herbie +template-haskell,
|
||||
any.megaparsec ==9.6.1,
|
||||
any.megaparsec ==9.7.0,
|
||||
megaparsec -dev,
|
||||
any.memory ==0.18.0,
|
||||
memory +support_bytestring +support_deepseq,
|
||||
|
@ -196,47 +202,57 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.microlens-mtl ==0.2.0.3,
|
||||
any.microlens-th ==0.4.3.15,
|
||||
any.mime-types ==0.1.2.0,
|
||||
any.mmorph ==1.2.0,
|
||||
any.monad-control ==1.0.3.1,
|
||||
any.monad-logger ==0.3.40,
|
||||
monad-logger +template_haskell,
|
||||
any.monad-loops ==0.4.3,
|
||||
monad-loops +base4,
|
||||
any.mono-traversable ==1.0.17.0,
|
||||
any.mono-traversable ==1.0.21.0,
|
||||
any.monomer ==1.6.0.1,
|
||||
monomer -examples,
|
||||
any.mtl ==2.3.1,
|
||||
any.murmur3 ==1.0.5,
|
||||
any.nanovg ==0.8.1.0,
|
||||
nanovg -examples -gl2 -gles3 -stb_truetype,
|
||||
any.network ==3.2.1.0,
|
||||
any.network ==3.2.7.0,
|
||||
network -devel,
|
||||
any.network-byte-order ==0.1.7,
|
||||
any.network-control ==0.1.3,
|
||||
any.network-info ==0.2.1,
|
||||
any.network-uri ==2.6.4.2,
|
||||
any.old-locale ==1.0.0.7,
|
||||
any.old-time ==1.1.0.4,
|
||||
any.os-string ==2.0.6,
|
||||
any.optparse-applicative ==0.18.1.0,
|
||||
optparse-applicative +process,
|
||||
any.os-string ==2.0.7,
|
||||
any.parallel ==3.2.2.0,
|
||||
any.parsec ==3.1.16.1,
|
||||
any.parser-combinators ==1.3.0,
|
||||
parser-combinators -dev,
|
||||
any.path-pieces ==0.2.1,
|
||||
any.pem ==0.2.4,
|
||||
any.persistent ==2.14.6.1,
|
||||
any.persistent ==2.14.6.3,
|
||||
any.persistent-sqlite ==2.13.3.0,
|
||||
persistent-sqlite -build-sanity-exe +full-text-search +have-usleep +json1 -systemlib +uri-filenames -use-pkgconfig -use-stat3 +use-stat4,
|
||||
any.persistent-template ==2.12.0.0,
|
||||
any.pretty ==1.1.3.6,
|
||||
any.prettyprinter ==1.7.1,
|
||||
prettyprinter -buildreadme +text,
|
||||
any.prettyprinter-ansi-terminal ==1.1.3,
|
||||
any.primitive ==0.9.0.0,
|
||||
any.process ==1.6.19.0,
|
||||
any.profunctors ==5.6.2,
|
||||
any.psqueues ==0.2.8.0,
|
||||
any.pureMD5 ==2.1.4,
|
||||
pureMD5 -test,
|
||||
any.qrcode-core ==0.9.9,
|
||||
any.qrcode-juicypixels ==0.8.5,
|
||||
any.qrcode-core ==0.9.10,
|
||||
any.qrcode-juicypixels ==0.8.6,
|
||||
any.quickcheck-io ==0.2.0,
|
||||
any.quickcheck-transformer ==0.3.1.2,
|
||||
any.random ==1.2.1.2,
|
||||
any.reflection ==2.1.8,
|
||||
any.recv ==0.1.0,
|
||||
any.reflection ==2.1.9,
|
||||
reflection -slow +template-haskell,
|
||||
any.regex-base ==0.94.0.2,
|
||||
any.regex-compat ==0.95.2.1,
|
||||
|
@ -251,7 +267,7 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
scientific -integer-simple,
|
||||
any.sdl2 ==2.5.5.0,
|
||||
sdl2 -examples -no-linear -opengl-example +pkgconfig +recent-ish,
|
||||
any.secp256k1-haskell ==1.2.0,
|
||||
any.secp256k1-haskell ==1.4.2,
|
||||
any.semialign ==1.3.1,
|
||||
semialign +semigroupoids,
|
||||
any.semigroupoids ==6.0.1,
|
||||
|
@ -260,8 +276,15 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
semigroups +binary +bytestring -bytestring-builder +containers +deepseq +hashable +tagged +template-haskell +text +transformers +unordered-containers,
|
||||
any.serialise ==0.2.6.1,
|
||||
serialise +newtime15,
|
||||
any.silently ==1.2.5.3,
|
||||
any.servant ==0.20.2,
|
||||
any.servant-server ==0.20.2,
|
||||
any.silently ==1.2.5.4,
|
||||
any.simple-sendfile ==0.2.32,
|
||||
simple-sendfile +allow-bsd -fallback,
|
||||
any.singleton-bool ==0.1.8,
|
||||
any.socks ==0.6.1,
|
||||
any.some ==1.0.6,
|
||||
some +newtype-unsafe,
|
||||
any.sop-core ==0.5.0.2,
|
||||
any.sort ==1.0.0.0,
|
||||
any.split ==0.2.5,
|
||||
|
@ -274,8 +297,10 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.strict ==0.5.1,
|
||||
any.string-conversions ==0.4.0.1,
|
||||
any.system-cxx-std-lib ==1.0,
|
||||
any.tagged ==0.8.8,
|
||||
any.tagged ==0.8.9,
|
||||
tagged +deepseq +transformers,
|
||||
any.tasty ==1.5.2,
|
||||
tasty +unix,
|
||||
any.template-haskell ==2.20.0.0,
|
||||
any.terminal-size ==0.3.4,
|
||||
any.terminfo ==0.4.1.6,
|
||||
|
@ -283,41 +308,43 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.text-iso8601 ==0.1.1,
|
||||
any.text-short ==0.1.6,
|
||||
text-short -asserts,
|
||||
any.text-show ==3.10.5,
|
||||
text-show +base-4-9 +integer-gmp +new-functor-classes +template-haskell-2-11,
|
||||
any.text-show ==3.11,
|
||||
text-show +integer-gmp,
|
||||
any.text-zipper ==0.13,
|
||||
any.tf-random ==0.5,
|
||||
any.th-abstraction ==0.7.0.0,
|
||||
any.th-compat ==0.1.5,
|
||||
any.th-lift ==0.8.4,
|
||||
any.th-abstraction ==0.7.1.0,
|
||||
any.th-compat ==0.1.6,
|
||||
any.th-lift ==0.8.6,
|
||||
any.th-lift-instances ==0.1.20,
|
||||
any.these ==1.2.1,
|
||||
any.time ==1.12.2,
|
||||
any.time-compat ==1.9.7,
|
||||
any.time-locale-compat ==0.1.1.5,
|
||||
time-locale-compat -old-locale,
|
||||
any.tls ==2.1.0,
|
||||
any.time-manager ==0.2.1,
|
||||
any.tls ==2.1.5,
|
||||
tls -devel,
|
||||
any.transformers ==0.6.1.0,
|
||||
any.transformers-base ==0.4.6,
|
||||
transformers-base +orphaninstances,
|
||||
any.transformers-compat ==0.7.2,
|
||||
transformers-compat -five +five-three -four +generic-deriving +mtl -three -two,
|
||||
any.typed-process ==0.2.11.1,
|
||||
any.typed-process ==0.2.12.0,
|
||||
any.unix ==2.8.4.0,
|
||||
any.unix-compat ==0.7.2,
|
||||
any.unix-time ==0.4.15,
|
||||
any.unix-compat ==0.7.3,
|
||||
any.unix-time ==0.4.16,
|
||||
any.unliftio ==0.2.25.0,
|
||||
any.unliftio-core ==0.2.1.0,
|
||||
any.unordered-containers ==0.2.20,
|
||||
unordered-containers -debug,
|
||||
any.utf8-string ==1.0.2,
|
||||
any.uuid ==1.3.16,
|
||||
any.uuid-types ==1.0.6,
|
||||
any.vault ==0.3.1.5,
|
||||
vault +useghc,
|
||||
any.vector ==0.13.1.0,
|
||||
any.vector ==0.13.2.0,
|
||||
vector +boundschecks -internalchecks -unsafechecks -wall,
|
||||
any.vector-algorithms ==0.9.0.2,
|
||||
any.vector-algorithms ==0.9.0.3,
|
||||
vector-algorithms +bench +boundschecks -internalchecks -llvm +properties -unsafechecks,
|
||||
any.vector-stream ==0.1.0.1,
|
||||
any.void ==0.7.3,
|
||||
|
@ -326,11 +353,20 @@ constraints: any.Cabal ==3.10.3.0,
|
|||
any.vty-crossplatform ==0.4.0.0,
|
||||
vty-crossplatform -demos,
|
||||
any.vty-unix ==0.2.0.0,
|
||||
any.wai ==3.2.4,
|
||||
any.wai-app-static ==3.1.9,
|
||||
wai-app-static +crypton -print,
|
||||
any.wai-extra ==3.1.17,
|
||||
wai-extra -build-example,
|
||||
any.wai-logger ==2.5.0,
|
||||
any.warp ==3.4.7,
|
||||
warp +allow-sendfilefd -network-bytestring -warp-debug +x509,
|
||||
any.wide-word ==0.1.6.0,
|
||||
any.witherable ==0.5,
|
||||
any.word-wrap ==0.5,
|
||||
any.word8 ==0.1.3,
|
||||
any.wreq ==0.5.4.3,
|
||||
wreq -aws -developer +doctest -httpbin,
|
||||
any.zlib ==0.7.1.0,
|
||||
zlib -bundled-c-zlib +non-blocking-ffi +pkg-config
|
||||
index-state: hackage.haskell.org 2024-07-10T18:40:26Z
|
||||
index-state: hackage.haskell.org 2024-12-14T09:52:48Z
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -2,6 +2,7 @@
|
|||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
|
||||
module Zenith.CLI where
|
||||
|
||||
|
@ -10,16 +11,15 @@ import qualified Brick.BChan as BC
|
|||
import qualified Brick.Focus as F
|
||||
import Brick.Forms
|
||||
( Form(..)
|
||||
, FormFieldState
|
||||
, (@@=)
|
||||
, allFieldsValid
|
||||
, editShowableField
|
||||
, editShowableFieldWithValidate
|
||||
, editTextField
|
||||
, focusedFormInputAttr
|
||||
, handleFormEvent
|
||||
, invalidFormInputAttr
|
||||
, newForm
|
||||
, radioField
|
||||
, renderForm
|
||||
, setFieldValid
|
||||
, updateFormState
|
||||
|
@ -42,7 +42,6 @@ import Brick.Widgets.Core
|
|||
, joinBorders
|
||||
, padAll
|
||||
, padBottom
|
||||
, padLeft
|
||||
, padTop
|
||||
, setAvailableSize
|
||||
, str
|
||||
|
@ -63,16 +62,24 @@ import qualified Brick.Widgets.Edit as E
|
|||
import qualified Brick.Widgets.List as L
|
||||
import qualified Brick.Widgets.ProgressBar as P
|
||||
import Control.Concurrent (forkIO, threadDelay)
|
||||
import Control.Exception (catch, throw, throwIO, try)
|
||||
import Control.Monad (forever, void)
|
||||
import Control.Exception (throw, throwIO, try)
|
||||
import Control.Monad (forM_, forever, unless, void, when)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Control.Monad.Logger (LoggingT, runFileLoggingT, runNoLoggingT)
|
||||
import Control.Monad.Logger
|
||||
( LoggingT
|
||||
, NoLoggingT
|
||||
, logDebugN
|
||||
, runNoLoggingT
|
||||
, runStderrLoggingT
|
||||
)
|
||||
import Data.Aeson
|
||||
import Data.HexString (HexString(..), toText)
|
||||
import Data.Maybe
|
||||
import Data.Scientific (Scientific, scientific)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as E
|
||||
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
|
||||
import qualified Data.UUID as U
|
||||
import qualified Data.Vector as Vec
|
||||
import Database.Persist
|
||||
import Database.Persist.Sqlite
|
||||
|
@ -83,10 +90,13 @@ import Lens.Micro.Mtl
|
|||
import Lens.Micro.TH
|
||||
import System.Hclip
|
||||
import Text.Printf
|
||||
import Text.Wrap (FillScope(..), FillStrategy(..), WrapSettings(..), wrapText)
|
||||
import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed)
|
||||
import ZcashHaskell.Orchard (getSaplingFromUA, isValidUnifiedAddress)
|
||||
import ZcashHaskell.Sapling (decodeSaplingAddress, isValidShieldedAddress)
|
||||
import Text.Wrap (FillScope(..), FillStrategy(..), WrapSettings(..))
|
||||
import ZcashHaskell.Keys (generateWalletSeedPhrase)
|
||||
import ZcashHaskell.Orchard
|
||||
( getSaplingFromUA
|
||||
, isValidUnifiedAddress
|
||||
, parseAddress
|
||||
)
|
||||
import ZcashHaskell.Transparent
|
||||
( decodeTransparentAddress
|
||||
, encodeTransparentReceiver
|
||||
|
@ -95,19 +105,27 @@ import ZcashHaskell.Types
|
|||
import ZcashHaskell.Utils (getBlockTime, makeZebraCall)
|
||||
import Zenith.Core
|
||||
import Zenith.DB
|
||||
import Zenith.Scanner (processTx, updateConfs)
|
||||
import Zenith.Scanner (checkIntegrity, processTx, rescanZebra, updateConfs)
|
||||
import Zenith.Types
|
||||
( Config(..)
|
||||
, HexStringDB(..)
|
||||
, PhraseDB(..)
|
||||
, PrivacyPolicy(..)
|
||||
, ProposedNote(..)
|
||||
, ShieldDeshieldOp(..)
|
||||
, UnifiedAddressDB(..)
|
||||
, ValidAddressAPI(..)
|
||||
, ZcashNetDB(..)
|
||||
, ZenithStatus(..)
|
||||
, ZenithUuid(..)
|
||||
)
|
||||
import Zenith.Utils
|
||||
( displayTaz
|
||||
, displayZec
|
||||
, getChainTip
|
||||
, isRecipientValid
|
||||
, isRecipientValidGUI
|
||||
, jsonNumber
|
||||
, parseAddress
|
||||
, showAddress
|
||||
, validBarValue
|
||||
)
|
||||
|
@ -126,6 +144,14 @@ data Name
|
|||
| ABList
|
||||
| DescripField
|
||||
| AddressField
|
||||
| PrivacyNoneField
|
||||
| PrivacyLowField
|
||||
| PrivacyMediumField
|
||||
| PrivacyFullField
|
||||
| ShieldField
|
||||
| DeshieldField
|
||||
| TotalTranspField
|
||||
| TotalShieldedField
|
||||
deriving (Eq, Show, Ord)
|
||||
|
||||
data DialogInput = DialogInput
|
||||
|
@ -136,8 +162,9 @@ makeLenses ''DialogInput
|
|||
|
||||
data SendInput = SendInput
|
||||
{ _sendTo :: !T.Text
|
||||
, _sendAmt :: !Float
|
||||
, _sendAmt :: !Scientific
|
||||
, _sendMemo :: !T.Text
|
||||
, _policyField :: !PrivacyPolicy
|
||||
} deriving (Show)
|
||||
|
||||
makeLenses ''SendInput
|
||||
|
@ -149,6 +176,12 @@ data AdrBookEntry = AdrBookEntry
|
|||
|
||||
makeLenses ''AdrBookEntry
|
||||
|
||||
newtype ShDshEntry = ShDshEntry
|
||||
{ _shAmt :: Scientific
|
||||
} deriving (Show)
|
||||
|
||||
makeLenses ''ShDshEntry
|
||||
|
||||
data DialogType
|
||||
= WName
|
||||
| AName
|
||||
|
@ -161,6 +194,8 @@ data DialogType
|
|||
| AdrBookForm
|
||||
| AdrBookUpdForm
|
||||
| AdrBookDelForm
|
||||
| DeshieldForm
|
||||
| ShieldForm
|
||||
|
||||
data DisplayType
|
||||
= AddrDisplay
|
||||
|
@ -178,6 +213,9 @@ data Tick
|
|||
| TickMsg !String
|
||||
| TickTx !HexString
|
||||
|
||||
data DropDownItem =
|
||||
DropdownItem String
|
||||
|
||||
data State = State
|
||||
{ _network :: !ZcashNet
|
||||
, _wallets :: !(L.List Name (Entity ZcashWallet))
|
||||
|
@ -206,6 +244,9 @@ data State = State
|
|||
, _abCurAdrs :: !T.Text -- used for address book CRUD operations
|
||||
, _sentTx :: !(Maybe HexString)
|
||||
, _unconfBalance :: !Integer
|
||||
, _deshieldForm :: !(Form ShDshEntry () Name)
|
||||
, _tBalance :: !Integer
|
||||
, _sBalance :: !Integer
|
||||
}
|
||||
|
||||
makeLenses ''State
|
||||
|
@ -222,11 +263,11 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s]
|
|||
(" Zenith - " <>
|
||||
show (st ^. network) <>
|
||||
" - " <>
|
||||
(T.unpack
|
||||
T.unpack
|
||||
(maybe
|
||||
"(None)"
|
||||
(\(_, w) -> zcashWalletName $ entityVal w)
|
||||
(L.listSelectedElement (st ^. wallets)))) ++
|
||||
(L.listSelectedElement (st ^. wallets))) ++
|
||||
" "))
|
||||
(C.hCenter
|
||||
(str
|
||||
|
@ -253,17 +294,24 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s]
|
|||
(C.hCenter
|
||||
(str ("Last block seen: " ++ show (st ^. syncBlock) ++ "\n")) <=>
|
||||
listTxBox " Transactions " (st ^. network) (st ^. transactions))) <=>
|
||||
C.hCenter
|
||||
(vBox
|
||||
[ C.hCenter
|
||||
(hBox
|
||||
[ capCommand "W" "allets"
|
||||
, capCommand "A" "ccounts"
|
||||
, capCommand "V" "iew address"
|
||||
, capCommand "S" "end Tx"
|
||||
, capCommand2 "Address " "B" "ook"
|
||||
, capCommand3 "" "S" "end Tx"
|
||||
])
|
||||
, C.hCenter
|
||||
(hBox
|
||||
[ capCommand2 "Address " "B" "ook"
|
||||
, capCommand2 "s" "H" "ield"
|
||||
, capCommand "D" "e-shield"
|
||||
, capCommand "Q" "uit"
|
||||
, capCommand "?" " Help"
|
||||
, str $ show (st ^. timer)
|
||||
])
|
||||
])
|
||||
listBox :: Show e => String -> L.List Name e -> Widget Name
|
||||
listBox titleLabel l =
|
||||
C.vCenter $
|
||||
|
@ -329,7 +377,7 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s]
|
|||
else emptyWidget
|
||||
where
|
||||
keyList =
|
||||
map (C.hCenter . str) ["?", "Esc", "w", "a", "v", "s", "b", "q"]
|
||||
map (C.hCenter . str) ["?", "Esc", "w", "a", "v", "s", "b", "d", "q"]
|
||||
actionList =
|
||||
map
|
||||
(hLimit 40 . str)
|
||||
|
@ -340,6 +388,7 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s]
|
|||
, "View address"
|
||||
, "Send Tx"
|
||||
, "Address Book"
|
||||
, "Shield/De-Shield"
|
||||
, "Quit"
|
||||
]
|
||||
inputDialog :: State -> Widget Name
|
||||
|
@ -386,6 +435,37 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s]
|
|||
(renderForm (st ^. txForm) <=>
|
||||
C.hCenter
|
||||
(hBox [capCommand "↲ " "Send", capCommand "<esc> " "Cancel"]))
|
||||
DeshieldForm ->
|
||||
D.renderDialog
|
||||
(D.dialog (Just (str " De-Shield ZEC ")) Nothing 50)
|
||||
(C.hCenter
|
||||
(padAll 1 $
|
||||
vBox
|
||||
[ str $
|
||||
"Transparent Bal.: " ++
|
||||
if st ^. network == MainNet
|
||||
then displayZec (st ^. tBalance)
|
||||
else displayTaz (st ^. tBalance)
|
||||
, str $
|
||||
"Shielded Bal.: " ++
|
||||
if st ^. network == MainNet
|
||||
then displayZec (st ^. sBalance)
|
||||
else displayTaz (st ^. sBalance)
|
||||
]) <=>
|
||||
renderForm (st ^. deshieldForm) <=>
|
||||
C.hCenter
|
||||
(hBox [capCommand "P" "roceed", capCommand "<esc> " "Cancel"]))
|
||||
ShieldForm ->
|
||||
D.renderDialog
|
||||
(D.dialog (Just (str " Shield ZEC ")) Nothing 50)
|
||||
(C.hCenter
|
||||
(str $
|
||||
"Shield " ++
|
||||
if st ^. network == MainNet
|
||||
then displayZec (st ^. tBalance)
|
||||
else displayTaz (st ^. tBalance) ++ "?") <=>
|
||||
C.hCenter
|
||||
(hBox [capCommand "P" "roceed", capCommand "<esc> " "Cancel"]))
|
||||
Blank -> emptyWidget
|
||||
-- Address Book List
|
||||
AdrBook ->
|
||||
|
@ -450,7 +530,7 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s]
|
|||
(str
|
||||
" _____ _ _ _ \n|__ /___ _ __ (_) |_| |__\n / // _ \\ '_ \\| | __| '_ \\\n / /| __/ | | | | |_| | | |\n/____\\___|_| |_|_|\\__|_| |_|") <=>
|
||||
C.hCenter
|
||||
(withAttr titleAttr (str "Zcash Wallet v0.6.0.0-beta")) <=>
|
||||
(withAttr titleAttr (str "Zcash Wallet v0.7.1.0-beta")) <=>
|
||||
C.hCenter (withAttr blinkAttr $ str "Press any key..."))
|
||||
else emptyWidget
|
||||
capCommand3 :: String -> String -> String -> Widget Name
|
||||
|
@ -610,14 +690,34 @@ mkInputForm =
|
|||
mkSendForm :: Integer -> SendInput -> Form SendInput e Name
|
||||
mkSendForm bal =
|
||||
newForm
|
||||
[ label "To: " @@= editTextField sendTo RecField (Just 1)
|
||||
[ label "Privacy Level :" @@=
|
||||
radioField
|
||||
policyField
|
||||
[ (Full, PrivacyFullField, "Full")
|
||||
, (Medium, PrivacyMediumField, "Medium")
|
||||
, (Low, PrivacyLowField, "Low")
|
||||
, (None, PrivacyNoneField, "None")
|
||||
]
|
||||
, label "To: " @@= editTextField sendTo RecField (Just 1)
|
||||
, label "Amount: " @@=
|
||||
editShowableFieldWithValidate sendAmt AmtField (isAmountValid bal)
|
||||
, label "Memo: " @@= editTextField sendMemo MemoField (Just 1)
|
||||
]
|
||||
where
|
||||
isAmountValid :: Integer -> Float -> Bool
|
||||
isAmountValid b i = (fromIntegral b / 100000000.0) >= i
|
||||
isAmountValid :: Integer -> Scientific -> Bool
|
||||
isAmountValid b i = fromIntegral b >= (i * scientific 1 8)
|
||||
label s w =
|
||||
padBottom (Pad 1) $ vLimit 1 (hLimit 15 $ str s <+> fill ' ') <+> w
|
||||
|
||||
mkDeshieldForm :: Integer -> ShDshEntry -> Form ShDshEntry e Name
|
||||
mkDeshieldForm tbal =
|
||||
newForm
|
||||
[ label "Amount: " @@=
|
||||
editShowableFieldWithValidate shAmt AmtField (isAmountValid tbal)
|
||||
]
|
||||
where
|
||||
isAmountValid :: Integer -> Scientific -> Bool
|
||||
isAmountValid b i = fromIntegral b >= (i * scientific 1 8)
|
||||
label s w =
|
||||
padBottom (Pad 1) $ vLimit 1 (hLimit 15 $ str s <+> fill ' ') <+> w
|
||||
|
||||
|
@ -722,19 +822,32 @@ abSelAttr = A.attrName "abselected"
|
|||
abMBarAttr :: A.AttrName
|
||||
abMBarAttr = A.attrName "menubar"
|
||||
|
||||
scanZebra :: T.Text -> T.Text -> Int -> Int -> BC.BChan Tick -> IO ()
|
||||
scanZebra dbP zHost zPort b eChan = do
|
||||
_ <- liftIO $ initDb dbP
|
||||
scanZebra ::
|
||||
T.Text
|
||||
-> T.Text
|
||||
-> Int
|
||||
-> Int
|
||||
-> BC.BChan Tick
|
||||
-> ZcashNet
|
||||
-> NoLoggingT IO ()
|
||||
scanZebra dbP zHost zPort b eChan znet = do
|
||||
bStatus <- liftIO $ checkBlockChain zHost zPort
|
||||
pool <- runNoLoggingT $ initPool dbP
|
||||
dbBlock <- runNoLoggingT $ getMaxBlock pool
|
||||
confUp <- try $ updateConfs zHost zPort pool :: IO (Either IOError ())
|
||||
case confUp of
|
||||
Left _e0 ->
|
||||
liftIO $
|
||||
BC.writeBChan eChan $ TickMsg "Failed to update unconfirmed transactions"
|
||||
Right _ -> do
|
||||
let sb = max dbBlock b
|
||||
pool <- liftIO $ runNoLoggingT $ initPool dbP
|
||||
dbBlock <- liftIO $ getMaxBlock pool $ ZcashNetDB znet
|
||||
chkBlock <- liftIO $ checkIntegrity dbP zHost zPort znet dbBlock 1
|
||||
syncChk <- liftIO $ isSyncing pool
|
||||
if syncChk
|
||||
then liftIO $ BC.writeBChan eChan $ TickMsg "Sync alread in progress"
|
||||
else do
|
||||
logDebugN $
|
||||
"dbBlock: " <>
|
||||
T.pack (show dbBlock) <> " chkBlock: " <> T.pack (show chkBlock)
|
||||
let sb =
|
||||
if chkBlock == dbBlock
|
||||
then max dbBlock b
|
||||
else max chkBlock b
|
||||
when (chkBlock /= dbBlock && chkBlock /= 1) $
|
||||
rewindWalletData pool sb $ ZcashNetDB znet
|
||||
if sb > zgb_blocks bStatus || sb < 1
|
||||
then do
|
||||
liftIO $
|
||||
|
@ -746,8 +859,28 @@ scanZebra dbP zHost zPort b eChan = do
|
|||
let step =
|
||||
(1.0 :: Float) /
|
||||
fromIntegral (zgb_blocks bStatus - (sb + 1))
|
||||
mapM_ (processBlock pool step) bList
|
||||
else liftIO $ BC.writeBChan eChan $ TickVal 1.0
|
||||
_ <- liftIO $ startSync pool
|
||||
mapM_ (liftIO . processBlock pool step) bList
|
||||
confUp <-
|
||||
liftIO $ try $ updateConfs zHost zPort pool :: NoLoggingT
|
||||
IO
|
||||
(Either IOError ())
|
||||
case confUp of
|
||||
Left _e0 -> do
|
||||
_ <- liftIO $ completeSync pool Failed
|
||||
liftIO $
|
||||
BC.writeBChan eChan $
|
||||
TickMsg "Failed to update unconfirmed transactions"
|
||||
Right _ -> do
|
||||
logDebugN "Updated confirmations"
|
||||
logDebugN "Starting commitment tree update"
|
||||
_ <- updateCommitmentTrees pool zHost zPort (ZcashNetDB znet)
|
||||
logDebugN "Finished tree update"
|
||||
_ <- liftIO $ completeSync pool Successful
|
||||
liftIO $ BC.writeBChan eChan $ TickMsg "startSync"
|
||||
return ()
|
||||
else do
|
||||
liftIO $ BC.writeBChan eChan $ TickMsg "startSync"
|
||||
where
|
||||
processBlock :: ConnectionPool -> Float -> Int -> IO ()
|
||||
processBlock pool step bl = do
|
||||
|
@ -759,29 +892,20 @@ scanZebra dbP zHost zPort b eChan = do
|
|||
"getblock"
|
||||
[Data.Aeson.String $ T.pack $ show bl, jsonNumber 1]
|
||||
case r of
|
||||
Left e1 -> liftIO $ BC.writeBChan eChan $ TickMsg e1
|
||||
Left e1 -> do
|
||||
_ <- liftIO $ completeSync pool Failed
|
||||
liftIO $ BC.writeBChan eChan $ TickMsg e1
|
||||
Right blk -> do
|
||||
r2 <-
|
||||
liftIO $
|
||||
makeZebraCall
|
||||
zHost
|
||||
zPort
|
||||
"getblock"
|
||||
[Data.Aeson.String $ T.pack $ show bl, jsonNumber 0]
|
||||
case r2 of
|
||||
Left e2 -> liftIO $ BC.writeBChan eChan $ TickMsg e2
|
||||
Right hb -> do
|
||||
let blockTime = getBlockTime hb
|
||||
mapM_ (runNoLoggingT . processTx zHost zPort blockTime pool) $
|
||||
bl_txs $ addTime blk blockTime
|
||||
bi <-
|
||||
saveBlock pool $
|
||||
ZcashBlock
|
||||
(fromIntegral $ bl_height blk)
|
||||
(HexStringDB $ bl_hash blk)
|
||||
(fromIntegral $ bl_confirmations blk)
|
||||
(fromIntegral $ bl_time blk)
|
||||
(ZcashNetDB znet)
|
||||
mapM_ (processTx zHost zPort bi pool) $ bl_txs blk
|
||||
liftIO $ BC.writeBChan eChan $ TickVal step
|
||||
addTime :: BlockResponse -> Int -> BlockResponse
|
||||
addTime bl t =
|
||||
BlockResponse
|
||||
(bl_confirmations bl)
|
||||
(bl_height bl)
|
||||
(fromIntegral t)
|
||||
(bl_txs bl)
|
||||
|
||||
appEvent :: BT.BrickEvent Name Tick -> BT.EventM Name State ()
|
||||
appEvent (BT.AppEvent t) = do
|
||||
|
@ -791,7 +915,35 @@ appEvent (BT.AppEvent t) = do
|
|||
TickMsg m -> do
|
||||
case s ^. displayBox of
|
||||
AddrDisplay -> return ()
|
||||
MsgDisplay -> return ()
|
||||
MsgDisplay -> do
|
||||
when (m == "startSync") $ do
|
||||
selWallet <-
|
||||
do case L.listSelectedElement $ s ^. wallets of
|
||||
Nothing -> do
|
||||
let fWall =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $ s ^. wallets
|
||||
case fWall of
|
||||
Nothing -> throw $ userError "Failed to select wallet"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
_ <-
|
||||
liftIO $
|
||||
runNoLoggingT $
|
||||
syncWallet
|
||||
(Config
|
||||
(s ^. dbPath)
|
||||
(s ^. zebraHost)
|
||||
(s ^. zebraPort)
|
||||
"user"
|
||||
"pwd"
|
||||
8080)
|
||||
selWallet
|
||||
updatedState <- BT.get
|
||||
ns <- liftIO $ refreshWallet updatedState
|
||||
BT.put ns
|
||||
BT.modify $ set msg ""
|
||||
BT.modify $ set displayBox BlankDisplay
|
||||
PhraseDisplay -> return ()
|
||||
TxDisplay -> return ()
|
||||
TxIdDisplay -> return ()
|
||||
|
@ -814,26 +966,9 @@ appEvent (BT.AppEvent t) = do
|
|||
SyncDisplay -> do
|
||||
if s ^. barValue == 1.0
|
||||
then do
|
||||
selWallet <-
|
||||
do case L.listSelectedElement $ s ^. wallets of
|
||||
Nothing -> do
|
||||
let fWall =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $ s ^. wallets
|
||||
case fWall of
|
||||
Nothing -> throw $ userError "Failed to select wallet"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
_ <-
|
||||
liftIO $
|
||||
syncWallet
|
||||
(Config (s ^. dbPath) (s ^. zebraHost) (s ^. zebraPort))
|
||||
selWallet
|
||||
BT.modify $ set displayBox BlankDisplay
|
||||
BT.modify $ set msg "Decoding, please wait..."
|
||||
BT.modify $ set barValue 0.0
|
||||
updatedState <- BT.get
|
||||
ns <- liftIO $ refreshWallet updatedState
|
||||
BT.put ns
|
||||
BT.modify $ set displayBox MsgDisplay
|
||||
else BT.modify $ set barValue $ validBarValue (v + s ^. barValue)
|
||||
BlankDisplay -> do
|
||||
case s ^. dialogBox of
|
||||
|
@ -847,21 +982,27 @@ appEvent (BT.AppEvent t) = do
|
|||
AdrBookForm -> return ()
|
||||
AdrBookUpdForm -> return ()
|
||||
AdrBookDelForm -> return ()
|
||||
DeshieldForm -> return ()
|
||||
ShieldForm -> return ()
|
||||
Blank -> do
|
||||
if s ^. timer == 90
|
||||
then do
|
||||
BT.modify $ set barValue 0.0
|
||||
BT.modify $ set displayBox SyncDisplay
|
||||
sBlock <- liftIO $ getMinBirthdayHeight pool
|
||||
sBlock <-
|
||||
liftIO $
|
||||
getMinBirthdayHeight pool (ZcashNetDB $ s ^. network)
|
||||
_ <-
|
||||
liftIO $
|
||||
forkIO $
|
||||
runNoLoggingT $
|
||||
scanZebra
|
||||
(s ^. dbPath)
|
||||
(s ^. zebraHost)
|
||||
(s ^. zebraPort)
|
||||
sBlock
|
||||
(s ^. eventDispatch)
|
||||
(s ^. network)
|
||||
BT.modify $ set timer 0
|
||||
return ()
|
||||
else BT.modify $ set timer $ 1 + s ^. timer
|
||||
|
@ -1063,7 +1204,8 @@ appEvent (BT.VtyEvent e) = do
|
|||
Just (_k, w) -> return w
|
||||
fs1 <- BT.zoom txForm $ BT.gets formState
|
||||
bl <-
|
||||
liftIO $ getLastSyncBlock pool $ entityKey selWal
|
||||
liftIO $
|
||||
getChainTip (s ^. zebraHost) (s ^. zebraPort)
|
||||
_ <-
|
||||
liftIO $
|
||||
forkIO $
|
||||
|
@ -1078,6 +1220,7 @@ appEvent (BT.VtyEvent e) = do
|
|||
(fs1 ^. sendAmt)
|
||||
(fs1 ^. sendTo)
|
||||
(fs1 ^. sendMemo)
|
||||
(fs1 ^. policyField)
|
||||
BT.modify $ set msg "Preparing transaction..."
|
||||
BT.modify $ set displayBox SendDisplay
|
||||
BT.modify $ set dialogBox Blank
|
||||
|
@ -1091,8 +1234,81 @@ appEvent (BT.VtyEvent e) = do
|
|||
fs <- BT.gets formState
|
||||
BT.modify $
|
||||
setFieldValid
|
||||
(isRecipientValid (fs ^. sendTo))
|
||||
(isRecipientValidGUI
|
||||
(fs ^. policyField)
|
||||
(fs ^. sendTo))
|
||||
RecField
|
||||
DeshieldForm -> do
|
||||
case e of
|
||||
V.EvKey V.KEsc [] -> BT.modify $ set dialogBox Blank
|
||||
V.EvKey (V.KChar 'p') [] -> do
|
||||
if allFieldsValid (s ^. deshieldForm)
|
||||
then do
|
||||
pool <-
|
||||
liftIO $ runNoLoggingT $ initPool $ s ^. dbPath
|
||||
selWal <-
|
||||
do case L.listSelectedElement $ s ^. wallets of
|
||||
Nothing -> do
|
||||
let fWall =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $ s ^. wallets
|
||||
case fWall of
|
||||
Nothing ->
|
||||
throw $
|
||||
userError "Failed to select wallet"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
selAcc <-
|
||||
do case L.listSelectedElement $ s ^. accounts of
|
||||
Nothing -> do
|
||||
let fAcc =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $
|
||||
s ^. accounts
|
||||
case fAcc of
|
||||
Nothing ->
|
||||
throw $
|
||||
userError "Failed to select wallet"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
selAddr <-
|
||||
do case L.listSelectedElement $ s ^. addresses of
|
||||
Nothing -> do
|
||||
let fAddr =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $
|
||||
s ^. addresses
|
||||
case fAddr of
|
||||
Nothing ->
|
||||
throw $
|
||||
userError "Failed to select address"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
fs1 <- BT.zoom deshieldForm $ BT.gets formState
|
||||
bl <-
|
||||
liftIO $
|
||||
getChainTip (s ^. zebraHost) (s ^. zebraPort)
|
||||
_ <-
|
||||
liftIO $
|
||||
forkIO $
|
||||
deshieldTransaction
|
||||
pool
|
||||
(s ^. eventDispatch)
|
||||
(s ^. zebraHost)
|
||||
(s ^. zebraPort)
|
||||
(s ^. network)
|
||||
(entityKey selAcc)
|
||||
bl
|
||||
(fs1 ^. shAmt)
|
||||
BT.modify $ set displayBox SendDisplay
|
||||
BT.modify $ set dialogBox Blank
|
||||
else do
|
||||
BT.modify $ set msg "Invalid inputs"
|
||||
BT.modify $ set displayBox MsgDisplay
|
||||
BT.modify $ set dialogBox Blank
|
||||
ev ->
|
||||
BT.zoom deshieldForm $ do
|
||||
handleFormEvent (BT.VtyEvent ev)
|
||||
AdrBook -> do
|
||||
case e of
|
||||
V.EvKey (V.KChar 'x') [] ->
|
||||
|
@ -1110,7 +1326,7 @@ appEvent (BT.VtyEvent e) = do
|
|||
"Address copied to Clipboard from >>\n" ++
|
||||
T.unpack (addressBookAbdescrip (entityVal a))
|
||||
BT.modify $ set displayBox MsgDisplay
|
||||
_ -> do
|
||||
_any -> do
|
||||
BT.modify $
|
||||
set msg "Error while copying the address!!"
|
||||
BT.modify $ set displayBox MsgDisplay
|
||||
|
@ -1125,7 +1341,8 @@ appEvent (BT.VtyEvent e) = do
|
|||
(SendInput
|
||||
(addressBookAbaddress (entityVal a))
|
||||
0.0
|
||||
"")
|
||||
""
|
||||
Full)
|
||||
BT.modify $ set dialogBox SendTx
|
||||
_ -> do
|
||||
BT.modify $
|
||||
|
@ -1275,6 +1492,53 @@ appEvent (BT.VtyEvent e) = do
|
|||
BT.put s'
|
||||
BT.modify $ set dialogBox AdrBook
|
||||
ev -> BT.modify $ set dialogBox AdrBookDelForm
|
||||
ShieldForm -> do
|
||||
case e of
|
||||
V.EvKey V.KEsc [] -> BT.modify $ set dialogBox Blank
|
||||
V.EvKey (V.KChar 'p') [] -> do
|
||||
pool <- liftIO $ runNoLoggingT $ initPool $ s ^. dbPath
|
||||
selWal <-
|
||||
do case L.listSelectedElement $ s ^. wallets of
|
||||
Nothing -> do
|
||||
let fWall =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $ s ^. wallets
|
||||
case fWall of
|
||||
Nothing ->
|
||||
throw $
|
||||
userError "Failed to select wallet"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
selAcc <-
|
||||
do case L.listSelectedElement $ s ^. accounts of
|
||||
Nothing -> do
|
||||
let fAcc =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $ s ^. accounts
|
||||
case fAcc of
|
||||
Nothing ->
|
||||
throw $
|
||||
userError "Failed to select account"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
bl <- liftIO $ getLastSyncBlock pool $ entityKey selWal
|
||||
_ <-
|
||||
liftIO $
|
||||
forkIO $
|
||||
shieldTransaction
|
||||
pool
|
||||
(s ^. eventDispatch)
|
||||
(s ^. zebraHost)
|
||||
(s ^. zebraPort)
|
||||
(s ^. network)
|
||||
(entityKey selAcc)
|
||||
bl
|
||||
BT.modify $ set msg "Preparing transaction..."
|
||||
BT.modify $ set displayBox SendDisplay
|
||||
BT.modify $ set dialogBox Blank
|
||||
ev ->
|
||||
BT.zoom deshieldForm $ do
|
||||
handleFormEvent (BT.VtyEvent ev)
|
||||
-- Process any other event
|
||||
Blank -> do
|
||||
case e of
|
||||
|
@ -1297,10 +1561,61 @@ appEvent (BT.VtyEvent e) = do
|
|||
V.EvKey (V.KChar 's') [] -> do
|
||||
BT.modify $
|
||||
set txForm $
|
||||
mkSendForm (s ^. balance) (SendInput "" 0.0 "")
|
||||
mkSendForm (s ^. balance) (SendInput "" 0.0 "" Full)
|
||||
BT.modify $ set dialogBox SendTx
|
||||
V.EvKey (V.KChar 'b') [] ->
|
||||
BT.modify $ set dialogBox AdrBook
|
||||
V.EvKey (V.KChar 'd') [] -> do
|
||||
pool <- liftIO $ runNoLoggingT $ initPool $ s ^. dbPath
|
||||
selAcc <-
|
||||
do case L.listSelectedElement $ s ^. accounts of
|
||||
Nothing -> do
|
||||
let fAcc =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $ s ^. accounts
|
||||
case fAcc of
|
||||
Nothing ->
|
||||
throw $
|
||||
userError "Failed to select account"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
tBal <-
|
||||
liftIO $
|
||||
getTransparentBalance pool $ entityKey selAcc
|
||||
sBal <-
|
||||
liftIO $ getShieldedBalance pool $ entityKey selAcc
|
||||
BT.modify $ set tBalance tBal
|
||||
BT.modify $ set sBalance sBal
|
||||
BT.modify $
|
||||
set deshieldForm $
|
||||
mkDeshieldForm sBal (ShDshEntry 0.0)
|
||||
BT.modify $ set dialogBox DeshieldForm
|
||||
V.EvKey (V.KChar 'h') [] -> do
|
||||
pool <- liftIO $ runNoLoggingT $ initPool $ s ^. dbPath
|
||||
selAcc <-
|
||||
do case L.listSelectedElement $ s ^. accounts of
|
||||
Nothing -> do
|
||||
let fAcc =
|
||||
L.listSelectedElement $
|
||||
L.listMoveToBeginning $ s ^. accounts
|
||||
case fAcc of
|
||||
Nothing ->
|
||||
throw $
|
||||
userError "Failed to select account"
|
||||
Just (_j, w1) -> return w1
|
||||
Just (_k, w) -> return w
|
||||
tBal <-
|
||||
liftIO $
|
||||
getTransparentBalance pool $ entityKey selAcc
|
||||
BT.modify $ set tBalance tBal
|
||||
if tBal > 20000
|
||||
then BT.modify $ set dialogBox ShieldForm
|
||||
else do
|
||||
BT.modify $
|
||||
set
|
||||
msg
|
||||
"Not enough transparent funds in this account"
|
||||
BT.modify $ set displayBox MsgDisplay
|
||||
ev ->
|
||||
case r of
|
||||
Just AList ->
|
||||
|
@ -1315,6 +1630,8 @@ appEvent (BT.VtyEvent e) = do
|
|||
printMsg s = BT.modify $ updateMsg s
|
||||
updateMsg :: String -> State -> State
|
||||
updateMsg = set msg
|
||||
-- fs <- BT.gets formState
|
||||
-- ev -> BT.zoom shdshForm $ L.handleListEvent ev
|
||||
appEvent _ = return ()
|
||||
|
||||
theMap :: A.AttrMap
|
||||
|
@ -1363,15 +1680,22 @@ runZenithTUI config = do
|
|||
case bc of
|
||||
Left e1 -> throwIO e1
|
||||
Right chainInfo -> do
|
||||
initDb dbFilePath
|
||||
x <- initDb dbFilePath
|
||||
_ <- upgradeQrTable pool
|
||||
case x of
|
||||
Left e2 -> throwIO $ userError e2
|
||||
Right x' -> do
|
||||
when x' $ rescanZebra host port dbFilePath
|
||||
walList <- getWallets pool $ zgb_net chainInfo
|
||||
accList <-
|
||||
if not (null walList)
|
||||
then runNoLoggingT $ getAccounts pool $ entityKey $ head walList
|
||||
then runNoLoggingT $
|
||||
getAccounts pool $ entityKey $ head walList
|
||||
else return []
|
||||
addrList <-
|
||||
if not (null accList)
|
||||
then runNoLoggingT $ getAddresses pool $ entityKey $ head accList
|
||||
then runNoLoggingT $
|
||||
getAddresses pool $ entityKey $ head accList
|
||||
else return []
|
||||
txList <-
|
||||
if not (null addrList)
|
||||
|
@ -1390,6 +1714,14 @@ runZenithTUI config = do
|
|||
if not (null accList)
|
||||
then getUnconfirmedBalance pool $ entityKey $ head accList
|
||||
else return 0
|
||||
tBal <-
|
||||
if not (null accList)
|
||||
then getTransparentBalance pool $ entityKey $ head accList
|
||||
else return 0
|
||||
sBal <-
|
||||
if not (null accList)
|
||||
then getShieldedBalance pool $ entityKey $ head accList
|
||||
else return 0
|
||||
eventChan <- BC.newBChan 10
|
||||
_ <-
|
||||
forkIO $
|
||||
|
@ -1403,11 +1735,12 @@ runZenithTUI config = do
|
|||
State
|
||||
(zgb_net chainInfo)
|
||||
(L.list WList (Vec.fromList walList) 1)
|
||||
(L.list AcList (Vec.fromList accList) 0)
|
||||
(L.list AcList (Vec.fromList accList) 1)
|
||||
(L.list AList (Vec.fromList addrList) 1)
|
||||
(L.list TList (Vec.fromList txList) 1)
|
||||
("Start up Ok! Connected to Zebra " ++
|
||||
(T.unpack . zgi_build) zebra ++ " on port " ++ show port ++ ".")
|
||||
(T.unpack . zgi_build) zebra ++
|
||||
" on port " ++ show port ++ ".")
|
||||
False
|
||||
(if null walList
|
||||
then WName
|
||||
|
@ -1425,13 +1758,16 @@ runZenithTUI config = do
|
|||
1.0
|
||||
eventChan
|
||||
0
|
||||
(mkSendForm 0 $ SendInput "" 0.0 "")
|
||||
(mkSendForm 0 $ SendInput "" 0.0 "" Full)
|
||||
(L.list ABList (Vec.fromList abookList) 1)
|
||||
(mkNewABForm (AdrBookEntry "" ""))
|
||||
""
|
||||
Nothing
|
||||
uBal
|
||||
Left e -> do
|
||||
(mkDeshieldForm 0 (ShDshEntry 0.0))
|
||||
tBal
|
||||
sBal
|
||||
Left _e -> do
|
||||
print $
|
||||
"No Zebra node available on port " <>
|
||||
show port <> ". Check your configuration."
|
||||
|
@ -1450,7 +1786,7 @@ refreshWallet s = do
|
|||
Just (j, w1) -> return (j, w1)
|
||||
Just (k, w) -> return (k, w)
|
||||
aL <- runNoLoggingT $ getAccounts pool $ entityKey selWallet
|
||||
let bl = zcashWalletLastSync $ entityVal selWallet
|
||||
let bl = zcashWalletLastSync $ entityVal $ walList !! ix
|
||||
addrL <-
|
||||
if not (null aL)
|
||||
then runNoLoggingT $ getAddresses pool $ entityKey $ head aL
|
||||
|
@ -1641,22 +1977,91 @@ sendTransaction ::
|
|||
-> ZcashNet
|
||||
-> ZcashAccountId
|
||||
-> Int
|
||||
-> Float
|
||||
-> Scientific
|
||||
-> T.Text
|
||||
-> T.Text
|
||||
-> PrivacyPolicy
|
||||
-> IO ()
|
||||
sendTransaction pool chan zHost zPort znet accId bl amt ua memo = do
|
||||
sendTransaction pool chan zHost zPort znet accId bl amt ua memo policy = do
|
||||
BC.writeBChan chan $ TickMsg "Preparing transaction..."
|
||||
case parseAddress ua znet of
|
||||
case parseAddress (E.encodeUtf8 ua) of
|
||||
Nothing -> BC.writeBChan chan $ TickMsg "Incorrect address"
|
||||
Just outUA -> do
|
||||
res <-
|
||||
runFileLoggingT "zenith.log" $
|
||||
prepareTx pool zHost zPort znet accId bl amt outUA memo
|
||||
BC.writeBChan chan $ TickMsg "Transaction ready, sending to Zebra..."
|
||||
runNoLoggingT $
|
||||
prepareTxV2
|
||||
pool
|
||||
zHost
|
||||
zPort
|
||||
znet
|
||||
accId
|
||||
bl
|
||||
[ ProposedNote
|
||||
(ValidAddressAPI outUA)
|
||||
amt
|
||||
(if memo == ""
|
||||
then Nothing
|
||||
else Just memo)
|
||||
]
|
||||
policy
|
||||
case res of
|
||||
Left e -> BC.writeBChan chan $ TickMsg $ show e
|
||||
Right rawTx -> do
|
||||
BC.writeBChan chan $ TickMsg "Transaction ready, sending to Zebra..."
|
||||
resp <-
|
||||
makeZebraCall
|
||||
zHost
|
||||
zPort
|
||||
"sendrawtransaction"
|
||||
[Data.Aeson.String $ toText rawTx]
|
||||
case resp of
|
||||
Left e1 -> BC.writeBChan chan $ TickMsg $ "Zebra error: " ++ show e1
|
||||
Right txId -> BC.writeBChan chan $ TickTx txId
|
||||
|
||||
shieldTransaction ::
|
||||
ConnectionPool
|
||||
-> BC.BChan Tick
|
||||
-> T.Text
|
||||
-> Int
|
||||
-> ZcashNet
|
||||
-> ZcashAccountId
|
||||
-> Int
|
||||
-> IO ()
|
||||
shieldTransaction pool chan zHost zPort znet accId bl = do
|
||||
BC.writeBChan chan $ TickMsg "Preparing shielding transaction..."
|
||||
res <- runNoLoggingT $ shieldTransparentNotes pool zHost zPort znet accId bl
|
||||
ops <-
|
||||
mapM
|
||||
(\case
|
||||
Left e -> return $ T.pack $ show e
|
||||
Right x -> do
|
||||
thisOp <- getOperation pool x
|
||||
case thisOp of
|
||||
Nothing -> return ""
|
||||
Just o ->
|
||||
return $
|
||||
(U.toText . getUuid . operationUuid $ entityVal o) <>
|
||||
": " <> (T.pack . show . operationStatus $ entityVal o))
|
||||
res
|
||||
BC.writeBChan chan $ TickMsg $ T.unpack $ T.intercalate "\n" ops
|
||||
|
||||
deshieldTransaction ::
|
||||
ConnectionPool
|
||||
-> BC.BChan Tick
|
||||
-> T.Text
|
||||
-> Int
|
||||
-> ZcashNet
|
||||
-> ZcashAccountId
|
||||
-> Int
|
||||
-> Scientific
|
||||
-> IO ()
|
||||
deshieldTransaction pool chan zHost zPort znet accId bl pnote = do
|
||||
BC.writeBChan chan $ TickMsg "Deshielding funds..."
|
||||
res <- runNoLoggingT $ deshieldNotes pool zHost zPort znet accId bl pnote
|
||||
case res of
|
||||
Left e -> BC.writeBChan chan $ TickMsg $ show e
|
||||
Right rawTx -> do
|
||||
BC.writeBChan chan $ TickMsg "Transaction ready, sending to Zebra..."
|
||||
resp <-
|
||||
makeZebraCall
|
||||
zHost
|
||||
|
|
1340
src/Zenith/Core.hs
1340
src/Zenith/Core.hs
File diff suppressed because it is too large
Load diff
1639
src/Zenith/DB.hs
1639
src/Zenith/DB.hs
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -49,6 +49,9 @@ zenithTheme =
|
|||
L.active .
|
||||
L.btnStyle . L.text ?~
|
||||
baseTextStyle &
|
||||
L.disabled .
|
||||
L.btnStyle . L.text ?~
|
||||
baseTextStyle &
|
||||
L.basic .
|
||||
L.btnMainStyle . L.text ?~
|
||||
hiliteTextStyle &
|
||||
|
|
1113
src/Zenith/RPC.hs
Normal file
1113
src/Zenith/RPC.hs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,29 +2,28 @@
|
|||
|
||||
module Zenith.Scanner where
|
||||
|
||||
import Control.Concurrent.Async (concurrently_, withAsync)
|
||||
import Control.Exception (throwIO, try)
|
||||
import qualified Control.Monad.Catch as CM (try)
|
||||
import Control.Monad (when)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Control.Monad.Logger
|
||||
( LoggingT
|
||||
, NoLoggingT
|
||||
( NoLoggingT
|
||||
, logErrorN
|
||||
, logInfoN
|
||||
, runNoLoggingT
|
||||
, runStderrLoggingT
|
||||
)
|
||||
import Data.Aeson
|
||||
import Data.HexString
|
||||
import Data.Maybe
|
||||
import qualified Data.Text as T
|
||||
import Data.Time (getCurrentTime)
|
||||
import Database.Persist.Sqlite
|
||||
import GHC.Utils.Monad (concatMapM)
|
||||
import Lens.Micro ((&), (.~), (^.), set)
|
||||
import System.Console.AsciiProgress
|
||||
import ZcashHaskell.Types
|
||||
( BlockResponse(..)
|
||||
, RawZebraTx(..)
|
||||
, Transaction(..)
|
||||
, ZcashNet(..)
|
||||
, ZebraGetBlockChainInfo(..)
|
||||
, ZebraTxResponse(..)
|
||||
, fromRawOBundle
|
||||
|
@ -32,59 +31,85 @@ import ZcashHaskell.Types
|
|||
, fromRawTBundle
|
||||
)
|
||||
import ZcashHaskell.Utils (getBlockTime, makeZebraCall, readZebraTransaction)
|
||||
import Zenith.Core (checkBlockChain)
|
||||
import Zenith.Core (checkBlockChain, syncWallet, updateCommitmentTrees)
|
||||
import Zenith.DB
|
||||
( getMaxBlock
|
||||
( ZcashBlock(..)
|
||||
, ZcashBlockId
|
||||
, clearWalletData
|
||||
, clearWalletTransactions
|
||||
, completeSync
|
||||
, getBlock
|
||||
, getMaxBlock
|
||||
, getMinBirthdayHeight
|
||||
, getUnconfirmedBlocks
|
||||
, getWallets
|
||||
, initDb
|
||||
, initPool
|
||||
, saveBlock
|
||||
, saveConfs
|
||||
, saveTransaction
|
||||
, startSync
|
||||
, updateWalletSync
|
||||
, upgradeQrTable
|
||||
)
|
||||
import Zenith.Types
|
||||
( Config(..)
|
||||
, HexStringDB(..)
|
||||
, ZcashNetDB(..)
|
||||
, ZenithStatus(..)
|
||||
)
|
||||
import Zenith.Utils (jsonNumber)
|
||||
|
||||
-- | Function to scan the Zcash blockchain through the Zebra node and populate the Zenith database
|
||||
scanZebra ::
|
||||
Int -- ^ Starting block
|
||||
-> T.Text -- ^ Host
|
||||
rescanZebra ::
|
||||
T.Text -- ^ Host
|
||||
-> Int -- ^ Port
|
||||
-> T.Text -- ^ Path to database file
|
||||
-> NoLoggingT IO ()
|
||||
scanZebra b host port dbFilePath = do
|
||||
_ <- liftIO $ initDb dbFilePath
|
||||
startTime <- liftIO getCurrentTime
|
||||
logInfoN $ "Started sync: " <> T.pack (show startTime)
|
||||
-> IO ()
|
||||
rescanZebra host port dbFilePath = do
|
||||
bc <-
|
||||
liftIO $ try $ checkBlockChain host port :: NoLoggingT
|
||||
IO
|
||||
try $ checkBlockChain host port :: IO
|
||||
(Either IOError ZebraGetBlockChainInfo)
|
||||
case bc of
|
||||
Left e -> logErrorN $ T.pack (show e)
|
||||
Left e -> print e
|
||||
Right bStatus -> do
|
||||
let dbInfo =
|
||||
mkSqliteConnectionInfo dbFilePath & extraPragmas .~
|
||||
["read_uncommited = true"]
|
||||
pool <- createSqlitePoolFromInfo dbInfo 5
|
||||
dbBlock <- getMaxBlock pool
|
||||
let znet = ZcashNetDB $ zgb_net bStatus
|
||||
pool1 <- runNoLoggingT $ initPool dbFilePath
|
||||
{-pool2 <- runNoLoggingT $ initPool dbFilePath-}
|
||||
{-pool3 <- runNoLoggingT $ initPool dbFilePath-}
|
||||
_ <- initDb dbFilePath
|
||||
upgradeQrTable pool1
|
||||
clearWalletTransactions pool1
|
||||
clearWalletData pool1
|
||||
_ <- startSync pool1
|
||||
dbBlock <- getMaxBlock pool1 znet
|
||||
b <- liftIO $ getMinBirthdayHeight pool1 znet
|
||||
let sb = max dbBlock b
|
||||
if sb > zgb_blocks bStatus || sb < 1
|
||||
then liftIO $ throwIO $ userError "Invalid starting block for scan"
|
||||
else do
|
||||
liftIO $
|
||||
print $
|
||||
"Scanning from " ++
|
||||
show (sb + 1) ++ " to " ++ show (zgb_blocks bStatus)
|
||||
let bList = [(sb + 1) .. (zgb_blocks bStatus)]
|
||||
"Scanning from " ++ show sb ++ " to " ++ show (zgb_blocks bStatus)
|
||||
let bList = [sb .. (zgb_blocks bStatus)]
|
||||
{-
|
||||
let batch = length bList `div` 3
|
||||
let bl1 = take batch bList
|
||||
let bl2 = take batch $ drop batch bList
|
||||
let bl3 = drop (2 * batch) bList
|
||||
-}
|
||||
_ <-
|
||||
displayConsoleRegions $ do
|
||||
pg <-
|
||||
liftIO $
|
||||
newProgressBar def {pgTotal = fromIntegral $ length bList}
|
||||
txList <-
|
||||
CM.try $ mapM_ (processBlock host port pool pg) bList :: NoLoggingT
|
||||
IO
|
||||
(Either IOError ())
|
||||
case txList of
|
||||
Left e1 -> logErrorN $ T.pack (show e1)
|
||||
Right txList' -> logInfoN "Finished scan"
|
||||
pg1 <- newProgressBar def {pgTotal = fromIntegral $ length bList}
|
||||
{-pg2 <- newProgressBar def {pgTotal = fromIntegral $ length bl2}-}
|
||||
{-pg3 <- newProgressBar def {pgTotal = fromIntegral $ length bl3}-}
|
||||
mapM_ (processBlock host port pool1 pg1 znet) bList
|
||||
{-`concurrently_`-}
|
||||
{-mapM_ (processBlock host port pool2 pg2 znet) bl2 `concurrently_`-}
|
||||
{-mapM_ (processBlock host port pool3 pg3 znet) bl3-}
|
||||
print "Please wait..."
|
||||
_ <- completeSync pool1 Successful
|
||||
_ <- runNoLoggingT $ updateCommitmentTrees pool1 host port znet
|
||||
print "Rescan complete"
|
||||
|
||||
-- | Function to process a raw block and extract the transaction information
|
||||
processBlock ::
|
||||
|
@ -92,9 +117,10 @@ processBlock ::
|
|||
-> Int -- ^ Port for `zebrad`
|
||||
-> ConnectionPool -- ^ DB file path
|
||||
-> ProgressBar -- ^ Progress bar
|
||||
-> ZcashNetDB -- ^ the network
|
||||
-> Int -- ^ The block number to process
|
||||
-> NoLoggingT IO ()
|
||||
processBlock host port pool pg b = do
|
||||
-> IO ()
|
||||
processBlock host port pool pg net b = do
|
||||
r <-
|
||||
liftIO $
|
||||
makeZebraCall
|
||||
|
@ -103,39 +129,29 @@ processBlock host port pool pg b = do
|
|||
"getblock"
|
||||
[Data.Aeson.String $ T.pack $ show b, jsonNumber 1]
|
||||
case r of
|
||||
Left e -> liftIO $ throwIO $ userError e
|
||||
Left e -> do
|
||||
_ <- completeSync pool Failed
|
||||
liftIO $ throwIO $ userError e
|
||||
Right blk -> do
|
||||
r2 <-
|
||||
liftIO $
|
||||
makeZebraCall
|
||||
host
|
||||
port
|
||||
"getblock"
|
||||
[Data.Aeson.String $ T.pack $ show b, jsonNumber 0]
|
||||
case r2 of
|
||||
Left e2 -> liftIO $ throwIO $ userError e2
|
||||
Right hb -> do
|
||||
let blockTime = getBlockTime hb
|
||||
mapM_ (processTx host port blockTime pool) $
|
||||
bl_txs $ addTime blk blockTime
|
||||
bi <-
|
||||
saveBlock pool $
|
||||
ZcashBlock
|
||||
(fromIntegral $ bl_height blk)
|
||||
(HexStringDB $ bl_hash blk)
|
||||
(fromIntegral $ bl_confirmations blk)
|
||||
(fromIntegral $ bl_time blk)
|
||||
net
|
||||
mapM_ (processTx host port bi pool) $ bl_txs blk
|
||||
liftIO $ tick pg
|
||||
where
|
||||
addTime :: BlockResponse -> Int -> BlockResponse
|
||||
addTime bl t =
|
||||
BlockResponse
|
||||
(bl_confirmations bl)
|
||||
(bl_height bl)
|
||||
(fromIntegral t)
|
||||
(bl_txs bl)
|
||||
|
||||
-- | Function to process a raw transaction
|
||||
processTx ::
|
||||
T.Text -- ^ Host name for `zebrad`
|
||||
-> Int -- ^ Port for `zebrad`
|
||||
-> Int -- ^ Block time
|
||||
-> ZcashBlockId -- ^ Block ID
|
||||
-> ConnectionPool -- ^ DB file path
|
||||
-> HexString -- ^ transaction id
|
||||
-> NoLoggingT IO ()
|
||||
-> IO ()
|
||||
processTx host port bt pool t = do
|
||||
r <-
|
||||
liftIO $
|
||||
|
@ -145,12 +161,15 @@ processTx host port bt pool t = do
|
|||
"getrawtransaction"
|
||||
[Data.Aeson.String $ toText t, jsonNumber 1]
|
||||
case r of
|
||||
Left e -> liftIO $ throwIO $ userError e
|
||||
Left e -> do
|
||||
_ <- completeSync pool Failed
|
||||
liftIO $ throwIO $ userError e
|
||||
Right rawTx -> do
|
||||
case readZebraTransaction (ztr_hex rawTx) of
|
||||
Nothing -> return ()
|
||||
Just rzt -> do
|
||||
_ <-
|
||||
runNoLoggingT $
|
||||
saveTransaction pool bt $
|
||||
Transaction
|
||||
t
|
||||
|
@ -184,3 +203,59 @@ updateConfs host port pool = do
|
|||
Left e -> throwIO $ userError e
|
||||
Right blk -> do
|
||||
saveConfs pool b $ fromInteger $ bl_confirmations blk
|
||||
|
||||
clearSync :: Config -> IO ()
|
||||
clearSync config = do
|
||||
let zHost = c_zebraHost config
|
||||
let zPort = c_zebraPort config
|
||||
let dbPath = c_dbPath config
|
||||
pool <- runNoLoggingT $ initPool dbPath
|
||||
bc <-
|
||||
try $ checkBlockChain zHost zPort :: IO
|
||||
(Either IOError ZebraGetBlockChainInfo)
|
||||
case bc of
|
||||
Left e1 -> throwIO e1
|
||||
Right chainInfo -> do
|
||||
x <- initDb dbPath
|
||||
_ <- upgradeQrTable pool
|
||||
case x of
|
||||
Left e2 -> throwIO $ userError e2
|
||||
Right x' -> do
|
||||
when x' $ rescanZebra zHost zPort dbPath
|
||||
_ <- clearWalletTransactions pool
|
||||
w <- getWallets pool $ zgb_net chainInfo
|
||||
liftIO $ mapM_ (updateWalletSync pool 0 . entityKey) w
|
||||
w' <- liftIO $ getWallets pool $ zgb_net chainInfo
|
||||
r <- runNoLoggingT $ mapM (syncWallet config) w'
|
||||
liftIO $ print r
|
||||
|
||||
-- | Detect chain re-orgs
|
||||
checkIntegrity ::
|
||||
T.Text -- ^ Database path
|
||||
-> T.Text -- ^ Zebra host
|
||||
-> Int -- ^ Zebra port
|
||||
-> ZcashNet -- ^ the network to scan
|
||||
-> Int -- ^ The block to start the check
|
||||
-> Int -- ^ depth
|
||||
-> IO Int
|
||||
checkIntegrity dbP zHost zPort znet b d =
|
||||
if b < 1
|
||||
then return 1
|
||||
else do
|
||||
r <-
|
||||
makeZebraCall
|
||||
zHost
|
||||
zPort
|
||||
"getblock"
|
||||
[Data.Aeson.String $ T.pack $ show b, jsonNumber 1]
|
||||
case r of
|
||||
Left e -> throwIO $ userError e
|
||||
Right blk -> do
|
||||
pool <- runNoLoggingT $ initPool dbP
|
||||
dbBlk <- getBlock pool b $ ZcashNetDB znet
|
||||
case dbBlk of
|
||||
Nothing -> return 1
|
||||
Just dbBlk' ->
|
||||
if bl_hash blk == getHex (zcashBlockHash $ entityVal dbBlk')
|
||||
then return b
|
||||
else checkIntegrity dbP zHost zPort znet (b - 5 * d) (d + 1)
|
||||
|
|
400
src/Zenith/Tree.hs
Normal file
400
src/Zenith/Tree.hs
Normal file
|
@ -0,0 +1,400 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DerivingVia #-}
|
||||
{-# LANGUAGE UndecidableInstances #-}
|
||||
|
||||
module Zenith.Tree where
|
||||
|
||||
import Codec.Borsh
|
||||
import Control.Monad.Logger (NoLoggingT, logDebugN)
|
||||
import Data.HexString
|
||||
import Data.Int (Int32, Int64, Int8)
|
||||
import Data.Maybe (fromJust, isNothing)
|
||||
import qualified Data.Text as T
|
||||
import qualified GHC.Generics as GHC
|
||||
import qualified Generics.SOP as SOP
|
||||
import ZcashHaskell.Orchard (combineOrchardNodes, getOrchardNodeValue)
|
||||
import ZcashHaskell.Sapling (combineSaplingNodes, getSaplingNodeValue)
|
||||
import ZcashHaskell.Types (MerklePath(..), OrchardTree(..), SaplingTree(..))
|
||||
|
||||
type Level = Int8
|
||||
|
||||
maxLevel :: Level
|
||||
maxLevel = 32
|
||||
|
||||
type Position = Int32
|
||||
|
||||
class Monoid v =>
|
||||
Measured a v
|
||||
where
|
||||
measure :: a -> Position -> Int64 -> v
|
||||
|
||||
class Node v where
|
||||
getLevel :: v -> Level
|
||||
getHash :: v -> HexString
|
||||
getPosition :: v -> Position
|
||||
getIndex :: v -> Int64
|
||||
isFull :: v -> Bool
|
||||
isMarked :: v -> Bool
|
||||
mkNode :: Level -> Position -> HexString -> v
|
||||
|
||||
type OrchardCommitment = HexString
|
||||
|
||||
instance Measured OrchardCommitment OrchardNode where
|
||||
measure oc p i =
|
||||
case getOrchardNodeValue (hexBytes oc) of
|
||||
Nothing -> OrchardNode 0 (hexString "00") 0 True 0 False
|
||||
Just val -> OrchardNode p val 0 True i False
|
||||
|
||||
type SaplingCommitment = HexString
|
||||
|
||||
instance Measured SaplingCommitment SaplingNode where
|
||||
measure sc p i =
|
||||
case getSaplingNodeValue (hexBytes sc) of
|
||||
Nothing -> SaplingNode 0 (hexString "00") 0 True 0 False
|
||||
Just val -> SaplingNode p val 0 True i False
|
||||
|
||||
data Tree v
|
||||
= EmptyLeaf
|
||||
| Leaf !v
|
||||
| PrunedBranch !v
|
||||
| Branch !v !(Tree v) !(Tree v)
|
||||
| InvalidTree
|
||||
deriving stock (Eq, GHC.Generic)
|
||||
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
|
||||
deriving (BorshSize, ToBorsh, FromBorsh) via AsEnum (Tree v)
|
||||
|
||||
instance (Node v, Show v) => Show (Tree v) where
|
||||
show EmptyLeaf = "()"
|
||||
show (Leaf v) = "(" ++ show v ++ ")"
|
||||
show (PrunedBranch v) = "{" ++ show v ++ "}"
|
||||
show (Branch s x y) =
|
||||
"<" ++ show (getHash s) ++ ">\n" ++ show x ++ "\n" ++ show y
|
||||
show InvalidTree = "InvalidTree"
|
||||
|
||||
instance (Monoid v, Node v) => Semigroup (Tree v) where
|
||||
(<>) InvalidTree _ = InvalidTree
|
||||
(<>) _ InvalidTree = InvalidTree
|
||||
(<>) EmptyLeaf EmptyLeaf = PrunedBranch $ value $ branch EmptyLeaf EmptyLeaf
|
||||
(<>) EmptyLeaf x = x
|
||||
(<>) (Leaf x) EmptyLeaf = branch (Leaf x) EmptyLeaf
|
||||
(<>) (Leaf x) (Leaf y) = branch (Leaf x) (Leaf y)
|
||||
(<>) (Leaf _) Branch {} = InvalidTree
|
||||
(<>) (Leaf _) (PrunedBranch _) = InvalidTree
|
||||
(<>) (PrunedBranch x) EmptyLeaf = PrunedBranch $ x <> x
|
||||
(<>) (PrunedBranch x) (Leaf y) =
|
||||
if isFull x
|
||||
then InvalidTree
|
||||
else mkSubTree (getLevel x) (Leaf y)
|
||||
(<>) (PrunedBranch x) (Branch s t u) =
|
||||
if getLevel x == getLevel s
|
||||
then branch (PrunedBranch x) (Branch s t u)
|
||||
else InvalidTree
|
||||
(<>) (PrunedBranch x) (PrunedBranch y) = PrunedBranch $ x <> y
|
||||
(<>) (Branch s x y) EmptyLeaf =
|
||||
branch (Branch s x y) $ getEmptyRoot (getLevel s)
|
||||
(<>) (Branch s x y) (PrunedBranch w)
|
||||
| getLevel s == getLevel w = branch (Branch s x y) (PrunedBranch w)
|
||||
| otherwise = InvalidTree
|
||||
(<>) (Branch s x y) (Leaf w)
|
||||
| isFull s = InvalidTree
|
||||
| isFull (value x) = branch x (y <> Leaf w)
|
||||
| otherwise = branch (x <> Leaf w) y
|
||||
(<>) (Branch s x y) (Branch s1 x1 y1)
|
||||
| getLevel s == getLevel s1 = branch (Branch s x y) (Branch s1 x1 y1)
|
||||
| otherwise = InvalidTree
|
||||
|
||||
value :: Monoid v => Tree v -> v
|
||||
value EmptyLeaf = mempty
|
||||
value (Leaf v) = v
|
||||
value (PrunedBranch v) = v
|
||||
value (Branch v _ _) = v
|
||||
value InvalidTree = mempty
|
||||
|
||||
branch :: Monoid v => Tree v -> Tree v -> Tree v
|
||||
branch x y = Branch (value x <> value y) x y
|
||||
|
||||
leaf :: Measured a v => a -> Int32 -> Int64 -> Tree v
|
||||
leaf a p i = Leaf (measure a p i)
|
||||
|
||||
prunedBranch :: Monoid v => Node v => Level -> Position -> HexString -> Tree v
|
||||
prunedBranch level pos val = PrunedBranch $ mkNode level pos val
|
||||
|
||||
root :: Monoid v => Node v => Tree v -> Tree v
|
||||
root tree =
|
||||
if getLevel (value tree) == maxLevel
|
||||
then tree
|
||||
else mkSubTree maxLevel tree
|
||||
|
||||
getEmptyRoot :: Monoid v => Node v => Level -> Tree v
|
||||
getEmptyRoot level = iterate (\x -> x <> x) EmptyLeaf !! fromIntegral level
|
||||
|
||||
append :: Monoid v => Measured a v => Node v => Tree v -> (a, Int64) -> Tree v
|
||||
append tree (n, i) = tree <> leaf n p i
|
||||
where
|
||||
p = 1 + getPosition (value tree)
|
||||
|
||||
mkSubTree :: Node v => Monoid v => Level -> Tree v -> Tree v
|
||||
mkSubTree level t =
|
||||
if getLevel (value subtree) == level
|
||||
then subtree
|
||||
else mkSubTree level subtree
|
||||
where
|
||||
subtree = t <> EmptyLeaf
|
||||
|
||||
path :: Monoid v => Node v => Position -> Tree v -> Maybe MerklePath
|
||||
path pos (Branch s x y) =
|
||||
if length (collectPath (Branch s x y)) /= 32
|
||||
then Nothing
|
||||
else Just $ MerklePath pos $ collectPath (Branch s x y)
|
||||
where
|
||||
collectPath :: Monoid v => Node v => Tree v -> [HexString]
|
||||
collectPath EmptyLeaf = []
|
||||
collectPath Leaf {} = []
|
||||
collectPath PrunedBranch {} = []
|
||||
collectPath InvalidTree = []
|
||||
collectPath (Branch _ j k)
|
||||
| getPosition (value k) /= 0 && getPosition (value k) < pos = []
|
||||
| getPosition (value j) < pos = collectPath k <> [getHash (value j)]
|
||||
| getPosition (value j) >= pos = collectPath j <> [getHash (value k)]
|
||||
| otherwise = []
|
||||
path _ _ = Nothing
|
||||
|
||||
nullPath :: MerklePath
|
||||
nullPath = MerklePath 0 []
|
||||
|
||||
getNotePosition :: Monoid v => Node v => Tree v -> Int64 -> Maybe Position
|
||||
getNotePosition (Leaf x) i
|
||||
| getIndex x == i = Just $ getPosition x
|
||||
| otherwise = Nothing
|
||||
getNotePosition (Branch _ x y) i
|
||||
| getIndex (value x) >= i = getNotePosition x i
|
||||
| getIndex (value y) >= i = getNotePosition y i
|
||||
| otherwise = Nothing
|
||||
getNotePosition _ _ = Nothing
|
||||
|
||||
truncateTree :: Monoid v => Node v => Tree v -> Int64 -> NoLoggingT IO (Tree v)
|
||||
truncateTree (Branch s x y) i
|
||||
| getLevel s == 1 && getIndex (value x) == i = do
|
||||
logDebugN $ T.pack $ show (getLevel s) ++ " Trunc to left leaf"
|
||||
return $ branch x EmptyLeaf
|
||||
| getLevel s == 1 && getIndex (value y) == i = do
|
||||
logDebugN $ T.pack $ show (getLevel s) ++ " Trunc to right leaf"
|
||||
return $ branch x y
|
||||
| getIndex (value x) >= i = do
|
||||
logDebugN $
|
||||
T.pack $
|
||||
show (getLevel s) ++
|
||||
": " ++ show i ++ " left i: " ++ show (getIndex (value x))
|
||||
l <- truncateTree x i
|
||||
return $ branch (l) (getEmptyRoot (getLevel (value x)))
|
||||
| getIndex (value y) /= 0 && getIndex (value y) >= i = do
|
||||
logDebugN $
|
||||
T.pack $
|
||||
show (getLevel s) ++
|
||||
": " ++ show i ++ " right i: " ++ show (getIndex (value y))
|
||||
r <- truncateTree y i
|
||||
return $ branch x (r)
|
||||
| otherwise = do
|
||||
logDebugN $
|
||||
T.pack $
|
||||
show (getLevel s) ++
|
||||
": " ++
|
||||
show (getIndex (value x)) ++ " catchall " ++ show (getIndex (value y))
|
||||
return InvalidTree
|
||||
truncateTree x _ = return x
|
||||
|
||||
countLeaves :: Node v => Tree v -> Int64
|
||||
countLeaves (Branch s x y) =
|
||||
if isFull s
|
||||
then 2 ^ getLevel s
|
||||
else countLeaves x + countLeaves y
|
||||
countLeaves (PrunedBranch x) =
|
||||
if isFull x
|
||||
then 2 ^ getLevel x
|
||||
else 0
|
||||
countLeaves (Leaf _) = 1
|
||||
countLeaves EmptyLeaf = 0
|
||||
countLeaves InvalidTree = 0
|
||||
|
||||
batchAppend ::
|
||||
Measured a v
|
||||
=> Node v => Monoid v => Tree v -> [(Int32, (a, Int64))] -> Tree v
|
||||
batchAppend x [] = x
|
||||
batchAppend (Branch s x y) notes
|
||||
| isFull s = InvalidTree
|
||||
| isFull (value x) = branch x (batchAppend y notes)
|
||||
| otherwise =
|
||||
branch
|
||||
(batchAppend x (take leftSide notes))
|
||||
(batchAppend y (drop leftSide notes))
|
||||
where
|
||||
leftSide = fromIntegral $ 2 ^ getLevel (value x) - countLeaves x
|
||||
batchAppend (PrunedBranch k) notes
|
||||
| isFull k = InvalidTree
|
||||
| otherwise =
|
||||
branch
|
||||
(batchAppend (getEmptyRoot (getLevel k - 1)) (take leftSide notes))
|
||||
(batchAppend (getEmptyRoot (getLevel k - 1)) (drop leftSide notes))
|
||||
where
|
||||
leftSide = fromIntegral $ 2 ^ (getLevel k - 1)
|
||||
batchAppend EmptyLeaf notes
|
||||
| length notes == 1 =
|
||||
leaf (fst $ snd $ head notes) (fst $ head notes) (snd $ snd $ head notes)
|
||||
| otherwise = InvalidTree
|
||||
batchAppend _ notes = InvalidTree
|
||||
|
||||
data SaplingNode = SaplingNode
|
||||
{ sn_position :: !Position
|
||||
, sn_value :: !HexString
|
||||
, sn_level :: !Level
|
||||
, sn_full :: !Bool
|
||||
, sn_index :: !Int64
|
||||
, sn_mark :: !Bool
|
||||
} deriving stock (Eq, GHC.Generic)
|
||||
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
|
||||
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct SaplingNode
|
||||
|
||||
instance Semigroup SaplingNode where
|
||||
(<>) x y =
|
||||
case combineSaplingNodes (sn_level x) (sn_value x) (sn_value y) of
|
||||
Nothing -> x
|
||||
Just newHash ->
|
||||
SaplingNode
|
||||
(max (sn_position x) (sn_position y))
|
||||
newHash
|
||||
(1 + sn_level x)
|
||||
(sn_full x && sn_full y)
|
||||
(max (sn_index x) (sn_index y))
|
||||
(sn_mark x || sn_mark y)
|
||||
|
||||
instance Monoid SaplingNode where
|
||||
mempty = SaplingNode 0 (hexString "00") 0 False 0 False
|
||||
mappend = (<>)
|
||||
|
||||
instance Node SaplingNode where
|
||||
getLevel = sn_level
|
||||
getHash = sn_value
|
||||
getPosition = sn_position
|
||||
getIndex = sn_index
|
||||
isFull = sn_full
|
||||
isMarked = sn_mark
|
||||
mkNode l p v = SaplingNode p v l True 0 False
|
||||
|
||||
instance Show SaplingNode where
|
||||
show = show . sn_value
|
||||
|
||||
saplingSize :: SaplingTree -> Int64
|
||||
saplingSize tree =
|
||||
(if isNothing (st_left tree)
|
||||
then 0
|
||||
else 1) +
|
||||
(if isNothing (st_right tree)
|
||||
then 0
|
||||
else 1) +
|
||||
foldl
|
||||
(\x (i, p) ->
|
||||
case p of
|
||||
Nothing -> x + 0
|
||||
Just _ -> x + 2 ^ i)
|
||||
0
|
||||
(zip [1 ..] $ st_parents tree)
|
||||
|
||||
mkSaplingTree :: SaplingTree -> Tree SaplingNode
|
||||
mkSaplingTree tree =
|
||||
foldl
|
||||
(\t (i, n) ->
|
||||
case n of
|
||||
Just n' -> prunedBranch i 0 n' <> t
|
||||
Nothing -> t <> getEmptyRoot i)
|
||||
leafRoot
|
||||
(zip [1 ..] $ st_parents tree)
|
||||
where
|
||||
leafRoot =
|
||||
case st_right tree of
|
||||
Just r' -> leaf (fromJust $ st_left tree) (pos - 1) 0 <> leaf r' pos 0
|
||||
Nothing -> leaf (fromJust $ st_left tree) pos 0 <> EmptyLeaf
|
||||
pos = fromIntegral $ saplingSize tree - 1
|
||||
|
||||
-- | Orchard
|
||||
data OrchardNode = OrchardNode
|
||||
{ on_position :: !Position
|
||||
, on_value :: !HexString
|
||||
, on_level :: !Level
|
||||
, on_full :: !Bool
|
||||
, on_index :: !Int64
|
||||
, on_mark :: !Bool
|
||||
} deriving stock (Eq, GHC.Generic)
|
||||
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
|
||||
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardNode
|
||||
|
||||
instance Semigroup OrchardNode where
|
||||
(<>) x y =
|
||||
case combineOrchardNodes
|
||||
(fromIntegral $ on_level x)
|
||||
(on_value x)
|
||||
(on_value y) of
|
||||
Nothing -> x
|
||||
Just newHash ->
|
||||
OrchardNode
|
||||
(max (on_position x) (on_position y))
|
||||
newHash
|
||||
(1 + on_level x)
|
||||
(on_full x && on_full y)
|
||||
(max (on_index x) (on_index y))
|
||||
(on_mark x || on_mark y)
|
||||
|
||||
instance Monoid OrchardNode where
|
||||
mempty = OrchardNode 0 (hexString "00") 0 False 0 False
|
||||
mappend = (<>)
|
||||
|
||||
instance Node OrchardNode where
|
||||
getLevel = on_level
|
||||
getHash = on_value
|
||||
getPosition = on_position
|
||||
getIndex = on_index
|
||||
isFull = on_full
|
||||
isMarked = on_mark
|
||||
mkNode l p v = OrchardNode p v l True 0 False
|
||||
|
||||
instance Show OrchardNode where
|
||||
show = show . on_value
|
||||
|
||||
instance Measured OrchardNode OrchardNode where
|
||||
measure o p i =
|
||||
OrchardNode p (on_value o) (on_level o) (on_full o) i (on_mark o)
|
||||
|
||||
orchardSize :: OrchardTree -> Int64
|
||||
orchardSize tree =
|
||||
(if isNothing (ot_left tree)
|
||||
then 0
|
||||
else 1) +
|
||||
(if isNothing (ot_right tree)
|
||||
then 0
|
||||
else 1) +
|
||||
foldl
|
||||
(\x (i, p) ->
|
||||
case p of
|
||||
Nothing -> x + 0
|
||||
Just _ -> x + 2 ^ i)
|
||||
0
|
||||
(zip [1 ..] $ ot_parents tree)
|
||||
|
||||
mkOrchardTree :: OrchardTree -> Tree OrchardNode
|
||||
mkOrchardTree tree =
|
||||
foldl
|
||||
(\t (i, n) ->
|
||||
case n of
|
||||
Just n' -> prunedBranch i 0 n' <> t
|
||||
Nothing -> t <> getEmptyRoot i)
|
||||
leafRoot
|
||||
(zip [1 ..] $ ot_parents tree)
|
||||
where
|
||||
leafRoot =
|
||||
case ot_right tree of
|
||||
Just r' -> leaf (fromJust $ ot_left tree) (pos - 1) 0 <> leaf r' pos 0
|
||||
Nothing -> leaf (fromJust $ ot_left tree) pos 0 <> EmptyLeaf
|
||||
pos = fromIntegral $ orchardSize tree - 1
|
|
@ -10,23 +10,37 @@
|
|||
module Zenith.Types where
|
||||
|
||||
import Data.Aeson
|
||||
import Data.Aeson.TH (deriveJSON)
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Char8 as C
|
||||
import Data.HexString
|
||||
import Data.Int (Int64)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Scientific (Scientific)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as E
|
||||
import Data.Text.Encoding.Error (lenientDecode)
|
||||
import qualified Data.UUID as U
|
||||
import Database.Persist.TH
|
||||
import GHC.Generics
|
||||
import ZcashHaskell.Orchard (encodeUnifiedAddress, parseAddress)
|
||||
import ZcashHaskell.Sapling (encodeSaplingAddress)
|
||||
import ZcashHaskell.Transparent
|
||||
( encodeExchangeAddress
|
||||
, encodeTransparentReceiver
|
||||
)
|
||||
import ZcashHaskell.Types
|
||||
( OrchardSpendingKey(..)
|
||||
( ExchangeAddress(..)
|
||||
, OrchardSpendingKey(..)
|
||||
, Phrase(..)
|
||||
, Rseed(..)
|
||||
, SaplingAddress(..)
|
||||
, SaplingSpendingKey(..)
|
||||
, Scope(..)
|
||||
, TransparentAddress(..)
|
||||
, TransparentSpendingKey
|
||||
, ValidAddress(..)
|
||||
, ZcashNet(..)
|
||||
)
|
||||
|
||||
|
@ -42,6 +56,9 @@ newtype ZcashNetDB = ZcashNetDB
|
|||
{ getNet :: ZcashNet
|
||||
} deriving newtype (Eq, Show, Read)
|
||||
|
||||
instance ToJSON ZcashNetDB where
|
||||
toJSON (ZcashNetDB z) = toJSON z
|
||||
|
||||
derivePersistField "ZcashNetDB"
|
||||
|
||||
newtype UnifiedAddressDB = UnifiedAddressDB
|
||||
|
@ -92,8 +109,165 @@ data Config = Config
|
|||
{ c_dbPath :: !T.Text
|
||||
, c_zebraHost :: !T.Text
|
||||
, c_zebraPort :: !Int
|
||||
, c_zenithUser :: !BS.ByteString
|
||||
, c_zenithPwd :: !BS.ByteString
|
||||
, c_zenithPort :: !Int
|
||||
} deriving (Eq, Prelude.Show)
|
||||
|
||||
data ZcashPool
|
||||
= TransparentPool
|
||||
| SproutPool
|
||||
| SaplingPool
|
||||
| OrchardPool
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
derivePersistField "ZcashPool"
|
||||
|
||||
instance ToJSON ZcashPool where
|
||||
toJSON zp =
|
||||
case zp of
|
||||
TransparentPool -> Data.Aeson.String "p2pkh"
|
||||
SproutPool -> Data.Aeson.String "sprout"
|
||||
SaplingPool -> Data.Aeson.String "sapling"
|
||||
OrchardPool -> Data.Aeson.String "orchard"
|
||||
|
||||
instance FromJSON ZcashPool where
|
||||
parseJSON =
|
||||
withText "ZcashPool" $ \case
|
||||
"p2pkh" -> return TransparentPool
|
||||
"sprout" -> return SproutPool
|
||||
"sapling" -> return SaplingPool
|
||||
"orchard" -> return OrchardPool
|
||||
_ -> fail "Not a known Zcash pool"
|
||||
|
||||
newtype ZenithUuid = ZenithUuid
|
||||
{ getUuid :: U.UUID
|
||||
} deriving newtype (Show, Eq, Read, ToJSON, FromJSON)
|
||||
|
||||
derivePersistField "ZenithUuid"
|
||||
|
||||
-- ** API types
|
||||
data ZcashWalletAPI = ZcashWalletAPI
|
||||
{ zw_index :: !Int
|
||||
, zw_name :: !T.Text
|
||||
, zw_network :: !ZcashNet
|
||||
, zw_birthday :: !Int
|
||||
, zw_lastSync :: !Int
|
||||
} deriving (Eq, Prelude.Show)
|
||||
|
||||
$(deriveJSON defaultOptions {fieldLabelModifier = drop 3} ''ZcashWalletAPI)
|
||||
|
||||
data ZcashAccountAPI = ZcashAccountAPI
|
||||
{ za_index :: !Int
|
||||
, za_wallet :: !Int
|
||||
, za_name :: !T.Text
|
||||
} deriving (Eq, Prelude.Show)
|
||||
|
||||
$(deriveJSON defaultOptions {fieldLabelModifier = drop 3} ''ZcashAccountAPI)
|
||||
|
||||
data ZcashAddressAPI = ZcashAddressAPI
|
||||
{ zd_index :: !Int
|
||||
, zd_account :: !Int
|
||||
, zd_name :: !T.Text
|
||||
, zd_ua :: !T.Text
|
||||
, zd_legacy :: !(Maybe T.Text)
|
||||
, zd_transparent :: !(Maybe T.Text)
|
||||
} deriving (Eq, Prelude.Show)
|
||||
|
||||
$(deriveJSON defaultOptions {fieldLabelModifier = drop 3} ''ZcashAddressAPI)
|
||||
|
||||
data ZcashNoteAPI = ZcashNoteAPI
|
||||
{ zn_txid :: !HexString
|
||||
, zn_pool :: !ZcashPool
|
||||
, zn_amount :: !Float
|
||||
, zn_amountZats :: !Int64
|
||||
, zn_memo :: !T.Text
|
||||
, zn_confirmed :: !Bool
|
||||
, zn_blockheight :: !Int
|
||||
, zn_blocktime :: !Int
|
||||
, zn_outindex :: !Int
|
||||
, zn_change :: !Bool
|
||||
} deriving (Eq, Prelude.Show)
|
||||
|
||||
$(deriveJSON defaultOptions {fieldLabelModifier = drop 3} ''ZcashNoteAPI)
|
||||
|
||||
data AccountBalance = AccountBalance
|
||||
{ acb_transparent :: !Int64
|
||||
, acb_sapling :: !Int64
|
||||
, acb_orchard :: !Int64
|
||||
} deriving (Eq, Prelude.Show)
|
||||
|
||||
$(deriveJSON defaultOptions {fieldLabelModifier = drop 4} ''AccountBalance)
|
||||
|
||||
data ZenithStatus
|
||||
= Processing
|
||||
| Failed
|
||||
| Successful
|
||||
deriving (Eq, Prelude.Show, Read)
|
||||
|
||||
$(deriveJSON defaultOptions ''ZenithStatus)
|
||||
|
||||
derivePersistField "ZenithStatus"
|
||||
|
||||
data PrivacyPolicy
|
||||
= None
|
||||
| Low
|
||||
| Medium
|
||||
| Full
|
||||
deriving (Eq, Show, Read, Ord)
|
||||
|
||||
$(deriveJSON defaultOptions ''PrivacyPolicy)
|
||||
|
||||
newtype ValidAddressAPI = ValidAddressAPI
|
||||
{ getVA :: ValidAddress
|
||||
} deriving newtype (Eq, Show)
|
||||
|
||||
instance ToJSON ValidAddressAPI where
|
||||
toJSON (ValidAddressAPI va) =
|
||||
case va of
|
||||
Unified ua -> Data.Aeson.String $ encodeUnifiedAddress ua
|
||||
Sapling sa ->
|
||||
maybe
|
||||
Data.Aeson.Null
|
||||
Data.Aeson.String
|
||||
(encodeSaplingAddress (net_type sa) (sa_receiver sa))
|
||||
Transparent ta ->
|
||||
Data.Aeson.String $
|
||||
encodeTransparentReceiver (ta_network ta) (ta_receiver ta)
|
||||
Exchange ea ->
|
||||
maybe
|
||||
Data.Aeson.Null
|
||||
Data.Aeson.String
|
||||
(encodeExchangeAddress (ex_network ea) (ex_address ea))
|
||||
|
||||
data ProposedNote = ProposedNote
|
||||
{ pn_addr :: !ValidAddressAPI
|
||||
, pn_amt :: !Scientific
|
||||
, pn_memo :: !(Maybe T.Text)
|
||||
} deriving (Eq, Prelude.Show)
|
||||
|
||||
instance FromJSON ProposedNote where
|
||||
parseJSON =
|
||||
withObject "ProposedNote" $ \obj -> do
|
||||
a <- obj .: "address"
|
||||
n <- obj .: "amount"
|
||||
m <- obj .:? "memo"
|
||||
case parseAddress (E.encodeUtf8 a) of
|
||||
Nothing -> fail "Invalid address"
|
||||
Just a' ->
|
||||
if n > 0 && n < 21000000
|
||||
then pure $ ProposedNote (ValidAddressAPI a') n m
|
||||
else fail "Invalid amount"
|
||||
|
||||
instance ToJSON ProposedNote where
|
||||
toJSON (ProposedNote a n m) =
|
||||
object ["address" .= a, "amount" .= n, "memo" .= m]
|
||||
|
||||
data ShieldDeshieldOp
|
||||
= Shield
|
||||
| Deshield
|
||||
deriving (Eq, Show, Read, Ord)
|
||||
|
||||
-- ** `zebrad`
|
||||
-- | Type for modeling the tree state response
|
||||
data ZebraTreeInfo = ZebraTreeInfo
|
||||
|
@ -138,24 +312,6 @@ instance FromJSON AddressSource where
|
|||
"mnemonic_seed" -> return MnemonicSeed
|
||||
_ -> fail "Not a known address source"
|
||||
|
||||
data ZcashPool
|
||||
= Transparent
|
||||
| Sprout
|
||||
| Sapling
|
||||
| Orchard
|
||||
deriving (Show, Read, Eq, Generic, ToJSON)
|
||||
|
||||
derivePersistField "ZcashPool"
|
||||
|
||||
instance FromJSON ZcashPool where
|
||||
parseJSON =
|
||||
withText "ZcashPool" $ \case
|
||||
"p2pkh" -> return Transparent
|
||||
"sprout" -> return Sprout
|
||||
"sapling" -> return Sapling
|
||||
"orchard" -> return Orchard
|
||||
_ -> fail "Not a known Zcash pool"
|
||||
|
||||
data ZcashAddress = ZcashAddress
|
||||
{ source :: AddressSource
|
||||
, pool :: [ZcashPool]
|
||||
|
@ -203,7 +359,8 @@ instance FromJSON AddressGroup where
|
|||
Nothing -> return []
|
||||
Just x -> do
|
||||
x' <- x .:? "addresses"
|
||||
return $ maybe [] (map (ZcashAddress s1 [Transparent] Nothing)) x'
|
||||
return $
|
||||
maybe [] (map (ZcashAddress s1 [TransparentPool] Nothing)) x'
|
||||
processSapling k s2 =
|
||||
case k of
|
||||
Nothing -> return []
|
||||
|
@ -211,7 +368,7 @@ instance FromJSON AddressGroup where
|
|||
where processOneSapling sx =
|
||||
withObject "Sapling" $ \oS -> do
|
||||
oS' <- oS .: "addresses"
|
||||
return $ map (ZcashAddress sx [Sapling] Nothing) oS'
|
||||
return $ map (ZcashAddress sx [SaplingPool] Nothing) oS'
|
||||
processUnified u =
|
||||
case u of
|
||||
Nothing -> return []
|
||||
|
|
|
@ -3,28 +3,38 @@
|
|||
module Zenith.Utils where
|
||||
|
||||
import Data.Aeson
|
||||
import Data.Char (isAlphaNum, isSpace)
|
||||
import Data.Functor (void)
|
||||
import Data.Maybe
|
||||
import Data.Ord (clamp)
|
||||
import Data.Scientific (Scientific(..), scientific)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as E
|
||||
import System.Directory
|
||||
import System.Process (createProcess_, shell)
|
||||
import Text.Regex.Posix
|
||||
import ZcashHaskell.Orchard (encodeUnifiedAddress, isValidUnifiedAddress)
|
||||
import ZcashHaskell.Orchard
|
||||
( encodeUnifiedAddress
|
||||
, isValidUnifiedAddress
|
||||
, parseAddress
|
||||
)
|
||||
import ZcashHaskell.Sapling (decodeSaplingAddress, isValidShieldedAddress)
|
||||
import ZcashHaskell.Transparent
|
||||
( decodeExchangeAddress
|
||||
, decodeTransparentAddress
|
||||
)
|
||||
import ZcashHaskell.Types
|
||||
( SaplingAddress(..)
|
||||
( ExchangeAddress(..)
|
||||
, SaplingAddress(..)
|
||||
, TransparentAddress(..)
|
||||
, UnifiedAddress(..)
|
||||
, ValidAddress(..)
|
||||
, ZcashNet(..)
|
||||
)
|
||||
import ZcashHaskell.Utils (makeZebraCall)
|
||||
import Zenith.Types
|
||||
( AddressGroup(..)
|
||||
, PrivacyPolicy(..)
|
||||
, UnifiedAddressDB(..)
|
||||
, ZcashAddress(..)
|
||||
, ZcashPool(..)
|
||||
|
@ -69,9 +79,9 @@ getAddresses ag = agtransparent ag <> agsapling ag <> agunified ag
|
|||
-- | Helper function to validate potential Zcash addresses
|
||||
validateAddress :: T.Text -> Maybe ZcashPool
|
||||
validateAddress txt --(tReg || sReg && isJust chk) || (uReg && isJust chk)
|
||||
| tReg = Just Transparent
|
||||
| sReg && chkS = Just Sapling
|
||||
| uReg && chk = Just Orchard
|
||||
| tReg = Just TransparentPool
|
||||
| sReg && chkS = Just SaplingPool
|
||||
| uReg && chk = Just OrchardPool
|
||||
| otherwise = Nothing
|
||||
where
|
||||
transparentRegex = "^t1[a-zA-Z0-9]{33}$" :: String
|
||||
|
@ -83,6 +93,13 @@ validateAddress txt --(tReg || sReg && isJust chk) || (uReg && isJust chk)
|
|||
chk = isJust $ isValidUnifiedAddress $ E.encodeUtf8 txt
|
||||
chkS = isValidShieldedAddress $ E.encodeUtf8 txt
|
||||
|
||||
-- | Return True if Address is valid
|
||||
validateAddressBool :: T.Text -> Bool
|
||||
validateAddressBool a = do
|
||||
case (validateAddress a) of
|
||||
Nothing -> False
|
||||
_ -> True
|
||||
|
||||
-- | Copy an address to the clipboard
|
||||
copyAddress :: ZcashAddress -> IO ()
|
||||
copyAddress a =
|
||||
|
@ -90,12 +107,18 @@ copyAddress a =
|
|||
createProcess_ "toClipboard" $
|
||||
shell $ "echo " ++ T.unpack (addy a) ++ " | xclip -r -selection clipboard"
|
||||
|
||||
-- | Get current user and build zenith path
|
||||
getZenithPath :: IO String
|
||||
getZenithPath = do
|
||||
homeDirectory <- getHomeDirectory
|
||||
return (homeDirectory ++ "/Zenith/")
|
||||
|
||||
-- | Bound a value to the 0..1 range, used for progress reporting on UIs
|
||||
validBarValue :: Float -> Float
|
||||
validBarValue = clamp (0, 1)
|
||||
|
||||
isRecipientValid :: T.Text -> Bool
|
||||
isRecipientValid a =
|
||||
isRecipientValid a = do
|
||||
case isValidUnifiedAddress (E.encodeUtf8 a) of
|
||||
Just _a1 -> True
|
||||
Nothing ->
|
||||
|
@ -103,12 +126,84 @@ isRecipientValid a =
|
|||
(case decodeTransparentAddress (E.encodeUtf8 a) of
|
||||
Just _a3 -> True
|
||||
Nothing ->
|
||||
case decodeExchangeAddress a of
|
||||
case decodeExchangeAddress (E.encodeUtf8 a) of
|
||||
Just _a4 -> True
|
||||
Nothing -> False)
|
||||
|
||||
parseAddress :: T.Text -> ZcashNet -> Maybe UnifiedAddress
|
||||
parseAddress a znet =
|
||||
isUnifiedAddressValid :: T.Text -> Bool
|
||||
isUnifiedAddressValid ua =
|
||||
case isValidUnifiedAddress (E.encodeUtf8 ua) of
|
||||
Just _a1 -> True
|
||||
Nothing -> False
|
||||
|
||||
isSaplingAddressValid :: T.Text -> Bool
|
||||
isSaplingAddressValid sa = isValidShieldedAddress (E.encodeUtf8 sa)
|
||||
|
||||
isTransparentAddressValid :: T.Text -> Bool
|
||||
isTransparentAddressValid ta =
|
||||
case decodeTransparentAddress (E.encodeUtf8 ta) of
|
||||
Just _a3 -> True
|
||||
Nothing -> False
|
||||
|
||||
isExchangeAddressValid :: T.Text -> Bool
|
||||
isExchangeAddressValid xa =
|
||||
case decodeExchangeAddress (E.encodeUtf8 xa) of
|
||||
Just _a4 -> True
|
||||
Nothing -> False
|
||||
|
||||
isRecipientValidGUI :: PrivacyPolicy -> T.Text -> Bool
|
||||
isRecipientValidGUI p a = do
|
||||
let adr = parseAddress (E.encodeUtf8 a)
|
||||
case p of
|
||||
Full ->
|
||||
case adr of
|
||||
Just a ->
|
||||
case a of
|
||||
Unified ua -> True
|
||||
Sapling sa -> True
|
||||
_ -> False
|
||||
Nothing -> False
|
||||
Medium ->
|
||||
case adr of
|
||||
Just a ->
|
||||
case a of
|
||||
Unified ua -> True
|
||||
Sapling sa -> True
|
||||
_ -> False
|
||||
Nothing -> False
|
||||
Low ->
|
||||
case adr of
|
||||
Just a ->
|
||||
case a of
|
||||
Unified ua -> True
|
||||
Sapling sa -> True
|
||||
Transparent ta -> True
|
||||
_ -> False
|
||||
Nothing -> False
|
||||
None ->
|
||||
case adr of
|
||||
Just a ->
|
||||
case a of
|
||||
Transparent ta -> True
|
||||
Exchange ea -> True
|
||||
_ -> False
|
||||
Nothing -> False
|
||||
|
||||
isZecAddressValid :: T.Text -> Bool
|
||||
isZecAddressValid a = do
|
||||
case isValidUnifiedAddress (E.encodeUtf8 a) of
|
||||
Just _a1 -> True
|
||||
Nothing ->
|
||||
isValidShieldedAddress (E.encodeUtf8 a) ||
|
||||
(case decodeTransparentAddress (E.encodeUtf8 a) of
|
||||
Just _a3 -> True
|
||||
Nothing ->
|
||||
case decodeExchangeAddress (E.encodeUtf8 a) of
|
||||
Just _a4 -> True
|
||||
Nothing -> False)
|
||||
|
||||
parseAddressUA :: T.Text -> ZcashNet -> Maybe UnifiedAddress
|
||||
parseAddressUA a znet =
|
||||
case isValidUnifiedAddress (E.encodeUtf8 a) of
|
||||
Just a1 -> Just a1
|
||||
Nothing ->
|
||||
|
@ -120,3 +215,39 @@ parseAddress a znet =
|
|||
Just a3 ->
|
||||
Just $ UnifiedAddress znet Nothing Nothing (Just $ ta_receiver a3)
|
||||
Nothing -> Nothing
|
||||
|
||||
isValidContent :: String -> Bool
|
||||
isValidContent [] = False -- an empty string is invalid
|
||||
isValidContent (x:xs)
|
||||
| not (isAlphaNum x) = False -- string must start with an alphanumeric character
|
||||
| otherwise = allValidChars xs -- process the rest of the string
|
||||
where
|
||||
allValidChars :: String -> Bool
|
||||
allValidChars [] = True -- if we got here, string is valid
|
||||
allValidChars (y:ys)
|
||||
| isAlphaNum y || isSpace y = allValidChars ys -- char is valid, continue
|
||||
| otherwise = False -- found an invalid character, return false
|
||||
|
||||
isValidString :: T.Text -> Bool
|
||||
isValidString c = do
|
||||
let a = T.unpack c
|
||||
isValidContent a
|
||||
|
||||
padWithZero :: Int -> String -> String
|
||||
padWithZero n s
|
||||
| length s >= n = s
|
||||
| otherwise = padWithZero n ("0" ++ s)
|
||||
|
||||
isEmpty :: [a] -> Bool
|
||||
isEmpty [] = True
|
||||
isEmpty _ = False
|
||||
|
||||
getChainTip :: T.Text -> Int -> IO Int
|
||||
getChainTip zHost zPort = do
|
||||
r <- makeZebraCall zHost zPort "getblockcount" []
|
||||
case r of
|
||||
Left e1 -> pure 0
|
||||
Right i -> pure i
|
||||
|
||||
getTransparentFromUA :: UnifiedAddress -> Maybe TransparentAddress
|
||||
getTransparentFromUA ua = TransparentAddress (ua_net ua) <$> t_rec ua
|
||||
|
|
|
@ -123,9 +123,10 @@ sendTx user pwd fromAddy toAddy amount memo = do
|
|||
if source fromAddy /= ImportedWatchOnly
|
||||
then do
|
||||
let privacyPolicy
|
||||
| valAdd == Just Transparent = "AllowRevealedRecipients"
|
||||
| valAdd == Just TransparentPool = "AllowRevealedRecipients"
|
||||
| isNothing (account fromAddy) &&
|
||||
elem Transparent (pool fromAddy) = "AllowRevealedSenders"
|
||||
elem TransparentPool (pool fromAddy) =
|
||||
"AllowRevealedSenders"
|
||||
| otherwise = "AllowRevealedAmounts"
|
||||
let pd =
|
||||
case memo of
|
||||
|
@ -301,7 +302,7 @@ sendWithUri user pwd fromAddy uri repTo = do
|
|||
let addType = validateAddress $ T.pack parsedAddress
|
||||
case addType of
|
||||
Nothing -> putStrLn " Invalid address"
|
||||
Just Transparent -> do
|
||||
Just TransparentPool -> do
|
||||
putStrLn $ " Address is valid: " ++ parsedAddress
|
||||
case (readMaybe parsedAmount :: Maybe Double) of
|
||||
Nothing -> putStrLn " Invalid amount."
|
||||
|
|
802
test/ServerSpec.hs
Normal file
802
test/ServerSpec.hs
Normal file
|
@ -0,0 +1,802 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
import Control.Concurrent (forkIO, threadDelay)
|
||||
import Control.Exception (SomeException, throwIO, try)
|
||||
import Control.Monad (when)
|
||||
import Control.Monad.Logger (runNoLoggingT)
|
||||
import Data.Aeson
|
||||
import qualified Data.ByteString as BS
|
||||
import Data.Configurator
|
||||
import Data.Maybe (fromJust, fromMaybe)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as E
|
||||
import Data.Time.Clock (getCurrentTime)
|
||||
import qualified Data.UUID as U
|
||||
import Network.HTTP.Simple
|
||||
import Network.Wai.Handler.Warp (run)
|
||||
import Servant
|
||||
import System.Directory
|
||||
import Test.HUnit hiding (State)
|
||||
import Test.Hspec
|
||||
import ZcashHaskell.Orchard (isValidUnifiedAddress, parseAddress)
|
||||
import ZcashHaskell.Types
|
||||
( ZcashNet(..)
|
||||
, ZebraGetBlockChainInfo(..)
|
||||
, ZebraGetInfo(..)
|
||||
)
|
||||
import Zenith.Core (checkBlockChain, checkZebra)
|
||||
import Zenith.DB (Operation(..), initDb, initPool, saveOperation)
|
||||
import Zenith.RPC
|
||||
( RpcCall(..)
|
||||
, State(..)
|
||||
, ZenithInfo(..)
|
||||
, ZenithMethod(..)
|
||||
, ZenithParams(..)
|
||||
, ZenithRPC(..)
|
||||
, ZenithResponse(..)
|
||||
, authenticate
|
||||
, zenithServer
|
||||
)
|
||||
import Zenith.Types
|
||||
( Config(..)
|
||||
, PrivacyPolicy(..)
|
||||
, ProposedNote(..)
|
||||
, ValidAddressAPI(..)
|
||||
, ZcashAccountAPI(..)
|
||||
, ZcashAddressAPI(..)
|
||||
, ZcashWalletAPI(..)
|
||||
, ZenithStatus(..)
|
||||
, ZenithUuid(..)
|
||||
)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
config <- load ["$(HOME)/Zenith/zenith.cfg"]
|
||||
let dbFilePath = "test.db"
|
||||
nodeUser <- require config "nodeUser"
|
||||
nodePwd <- require config "nodePwd"
|
||||
zebraPort <- require config "zebraPort"
|
||||
zebraHost <- require config "zebraHost"
|
||||
nodePort <- require config "nodePort"
|
||||
let myConfig = Config dbFilePath zebraHost zebraPort nodeUser nodePwd nodePort
|
||||
hspec $ do
|
||||
describe "RPC methods" $ do
|
||||
beforeAll_ (startAPI myConfig) $ do
|
||||
describe "getinfo" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
GetInfo
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
it "correct credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetInfo
|
||||
BlankParams
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
InfoResponse "zh" (ZenithInfo "0.8.0.0-beta" TestNet "v2.1.0")
|
||||
describe "Wallets" $ do
|
||||
describe "listwallet" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
ListWallets
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
it "correct credentials, no wallet" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListWallets
|
||||
BlankParams
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse
|
||||
"zh"
|
||||
(-32001)
|
||||
"No wallets available. Please create one first"
|
||||
describe "getnewwallet" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
GetNewWallet
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "no params" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewWallet
|
||||
BlankParams
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe` ErrorResponse "zh" (-32602) "Invalid params"
|
||||
it "Valid params" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewWallet
|
||||
(NameParams "Main")
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r -> r `shouldBe` NewItemResponse "zh" 1
|
||||
it "duplicate name" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewWallet
|
||||
(NameParams "Main")
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse
|
||||
"zh"
|
||||
(-32007)
|
||||
"Entity with that name already exists."
|
||||
describe "listwallet" $ do
|
||||
it "wallet exists" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListWallets
|
||||
BlankParams
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (WalletListResponse i k) ->
|
||||
zw_name (head k) `shouldBe` "Main"
|
||||
Right _ -> assertFailure "Unexpected response"
|
||||
describe "Accounts" $ do
|
||||
describe "listaccounts" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
ListAccounts
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "invalid wallet" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListAccounts
|
||||
(AccountsParams 17)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse "zh" (-32008) "Wallet does not exist."
|
||||
it "valid wallet, no accounts" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListAccounts
|
||||
(AccountsParams 1)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse
|
||||
"zh"
|
||||
(-32002)
|
||||
"No accounts available for this wallet. Please create one first"
|
||||
describe "getnewaccount" $ do
|
||||
it "invalid credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
GetNewAccount
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "invalid wallet" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAccount
|
||||
(NameIdParams "Personal" 17)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse "zh" (-32008) "Wallet does not exist."
|
||||
it "valid wallet" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAccount
|
||||
(NameIdParams "Personal" 1)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r -> r `shouldBe` NewItemResponse "zh" 1
|
||||
it "valid wallet, duplicate name" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAccount
|
||||
(NameIdParams "Personal" 1)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse
|
||||
"zh"
|
||||
(-32007)
|
||||
"Entity with that name already exists."
|
||||
describe "listaccounts" $ do
|
||||
it "valid wallet" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListAccounts
|
||||
(AccountsParams 1)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
AccountListResponse "zh" [ZcashAccountAPI 1 1 "Personal"]
|
||||
describe "Addresses" $ do
|
||||
describe "listaddresses" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
ListAddresses
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
it "correct credentials, no addresses" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListAddresses
|
||||
(AddressesParams 1)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse
|
||||
"zh"
|
||||
(-32003)
|
||||
"No addresses available for this account. Please create one first"
|
||||
describe "getnewaddress" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
GetNewAddress
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "invalid account" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAddress
|
||||
(NewAddrParams 17 "Business" False False)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse "zh" (-32006) "Account does not exist."
|
||||
it "valid account" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAddress
|
||||
(NewAddrParams 1 "Business" False False)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (NewAddrResponse i a) -> zd_name a `shouldBe` "Business"
|
||||
Right _ -> assertFailure "unexpected response"
|
||||
it "valid account, duplicate name" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAddress
|
||||
(NewAddrParams 1 "Business" False False)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right r ->
|
||||
r `shouldBe`
|
||||
ErrorResponse
|
||||
"zh"
|
||||
(-32007)
|
||||
"Entity with that name already exists."
|
||||
it "valid account, no sapling" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAddress
|
||||
(NewAddrParams 1 "NoSapling" True False)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (NewAddrResponse i a) -> zd_legacy a `shouldBe` Nothing
|
||||
Right _ -> assertFailure "unexpected response"
|
||||
it "valid account, no transparent" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAddress
|
||||
(NewAddrParams 1 "NoTransparent" False True)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (NewAddrResponse i a) ->
|
||||
zd_transparent a `shouldBe` Nothing
|
||||
Right _ -> assertFailure "unexpected response"
|
||||
it "valid account, orchard only" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetNewAddress
|
||||
(NewAddrParams 1 "OrchOnly" True True)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (NewAddrResponse i a) ->
|
||||
a `shouldSatisfy`
|
||||
(\b ->
|
||||
(zd_transparent b == Nothing) && (zd_legacy b == Nothing))
|
||||
Right _ -> assertFailure "unexpected response"
|
||||
describe "listaddresses" $ do
|
||||
it "correct credentials, addresses exist" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListAddresses
|
||||
(AddressesParams 1)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (AddressListResponse i a) -> length a `shouldBe` 4
|
||||
describe "Notes" $ do
|
||||
describe "listreceived" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
ListReceived
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "no parameters" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListReceived
|
||||
BlankParams
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32602)
|
||||
it "unknown index" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ListReceived
|
||||
(NotesParams "17")
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32004)
|
||||
describe "Balance" $ do
|
||||
describe "getbalance" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
GetBalance
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "no parameters" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetBalance
|
||||
BlankParams
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32602)
|
||||
it "unknown index" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetBalance
|
||||
(BalanceParams 17)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32006)
|
||||
describe "Operations" $ do
|
||||
describe "getoperationstatus" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
GetOperationStatus
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "invalid ID" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetOperationStatus
|
||||
(NameParams "badId")
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32602)
|
||||
it "valid ID" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetOperationStatus
|
||||
(OpParams
|
||||
(ZenithUuid $
|
||||
fromMaybe U.nil $
|
||||
U.fromText "bd2aa95a-db51-4cc4-9fea-0f9cf79003a4"))
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (OpResponse i o) ->
|
||||
operationUuid o `shouldBe`
|
||||
(ZenithUuid $
|
||||
fromMaybe U.nil $
|
||||
U.fromText "bd2aa95a-db51-4cc4-9fea-0f9cf79003a4")
|
||||
Right _ -> assertFailure "unexpected response"
|
||||
it "valid ID not found" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
GetOperationStatus
|
||||
(OpParams
|
||||
(ZenithUuid $
|
||||
fromMaybe U.nil $
|
||||
U.fromText "bd2aa95a-db51-4cc4-9fea-0f9cf79003a5"))
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32009)
|
||||
Right _ -> assertFailure "unexpected response"
|
||||
describe "Send tx" $ do
|
||||
describe "sendmany" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
SendMany
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "invalid account" $ do
|
||||
let uaRead =
|
||||
parseAddress
|
||||
"utest1dl54utt6prjj5e0dnlknwumnxq9hycdjpkfr0sy6e6h522remqee8axe9zax0wsjrwpj76v555pdhvj9rnargpfyycs0vpkapq98xcdhem99gc4wchzn0ggepq3y6nz3a9sscwgqxgsh9vzhcad402y3x9szfregck5gslkya3c79d86xx0l33tpk8gnn7ew9vw37w43zh22u8dgdax"
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
SendMany
|
||||
(SendParams
|
||||
17
|
||||
[ ProposedNote
|
||||
(ValidAddressAPI $ fromJust uaRead)
|
||||
0.005
|
||||
(Just "A cool memo")
|
||||
]
|
||||
Full)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32006)
|
||||
it "valid account, empty notes" $ do
|
||||
let uaRead =
|
||||
parseAddress
|
||||
"utest1dl54utt6prjj5e0dnlknwumnxq9hycdjpkfr0sy6e6h522remqee8axe9zax0wsjrwpj76v555pdhvj9rnargpfyycs0vpkapq98xcdhem99gc4wchzn0ggepq3y6nz3a9sscwgqxgsh9vzhcad402y3x9szfregck5gslkya3c79d86xx0l33tpk8gnn7ew9vw37w43zh22u8dgdax"
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
SendMany
|
||||
(SendParams 1 [] Full)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32602)
|
||||
it "valid account, single output" $ do
|
||||
let uaRead =
|
||||
parseAddress
|
||||
"utest1dl54utt6prjj5e0dnlknwumnxq9hycdjpkfr0sy6e6h522remqee8axe9zax0wsjrwpj76v555pdhvj9rnargpfyycs0vpkapq98xcdhem99gc4wchzn0ggepq3y6nz3a9sscwgqxgsh9vzhcad402y3x9szfregck5gslkya3c79d86xx0l33tpk8gnn7ew9vw37w43zh22u8dgdax"
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
SendMany
|
||||
(SendParams
|
||||
1
|
||||
[ ProposedNote
|
||||
(ValidAddressAPI $ fromJust uaRead)
|
||||
5.0
|
||||
(Just "A cool memo")
|
||||
]
|
||||
Full)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (SendResponse i o) -> o `shouldNotBe` U.nil
|
||||
it "valid account, multiple outputs" $ do
|
||||
let uaRead =
|
||||
parseAddress
|
||||
"utest1dl54utt6prjj5e0dnlknwumnxq9hycdjpkfr0sy6e6h522remqee8axe9zax0wsjrwpj76v555pdhvj9rnargpfyycs0vpkapq98xcdhem99gc4wchzn0ggepq3y6nz3a9sscwgqxgsh9vzhcad402y3x9szfregck5gslkya3c79d86xx0l33tpk8gnn7ew9vw37w43zh22u8dgdax"
|
||||
let uaRead2 =
|
||||
parseAddress
|
||||
"ztestsapling136jp8z89v2jh6kqd5rs4dtvlxym90m43svzdwzxaplyvc5ttzppytpvx80ncllcsqzpmukxjl3y"
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
SendMany
|
||||
(SendParams
|
||||
1
|
||||
[ ProposedNote
|
||||
(ValidAddressAPI $ fromJust uaRead)
|
||||
5.0
|
||||
(Just "A cool memo")
|
||||
, ProposedNote
|
||||
(ValidAddressAPI $ fromJust uaRead2)
|
||||
1.0
|
||||
(Just "Not so cool memo")
|
||||
]
|
||||
Full)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (SendResponse i o) -> o `shouldNotBe` U.nil
|
||||
describe "Shield notes" $ do
|
||||
it "bad credentials" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
"baduser"
|
||||
"idontknow"
|
||||
ShieldNotes
|
||||
BlankParams
|
||||
res `shouldBe` Left "Invalid credentials"
|
||||
describe "correct credentials" $ do
|
||||
it "no parameters" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ShieldNotes
|
||||
BlankParams
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32602)
|
||||
it "invalid account" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ShieldNotes
|
||||
(ShieldNotesParams 27)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32006)
|
||||
it "valid account" $ do
|
||||
res <-
|
||||
makeZenithCall
|
||||
"127.0.0.1"
|
||||
nodePort
|
||||
nodeUser
|
||||
nodePwd
|
||||
ShieldNotes
|
||||
(ShieldNotesParams 1)
|
||||
case res of
|
||||
Left e -> assertFailure e
|
||||
Right (MultiOpResponse i c) -> c `shouldNotBe` []
|
||||
|
||||
startAPI :: Config -> IO ()
|
||||
startAPI config = do
|
||||
putStrLn "Starting test RPC server"
|
||||
checkDbFile <- doesFileExist "test.db"
|
||||
when checkDbFile $ removeFile "test.db"
|
||||
let ctx = authenticate config :. EmptyContext
|
||||
w <-
|
||||
try $ checkZebra (c_zebraHost config) (c_zebraPort config) :: IO
|
||||
(Either IOError ZebraGetInfo)
|
||||
case w of
|
||||
Right zebra -> do
|
||||
bc <-
|
||||
try $ checkBlockChain (c_zebraHost config) (c_zebraPort config) :: IO
|
||||
(Either IOError ZebraGetBlockChainInfo)
|
||||
case bc of
|
||||
Left e1 -> throwIO e1
|
||||
Right chainInfo -> do
|
||||
x <- initDb "test.db"
|
||||
case x of
|
||||
Left e2 -> throwIO $ userError e2
|
||||
Right x' -> do
|
||||
pool <- runNoLoggingT $ initPool "test.db"
|
||||
ts <- getCurrentTime
|
||||
y <-
|
||||
saveOperation
|
||||
pool
|
||||
(Operation
|
||||
(ZenithUuid $
|
||||
fromMaybe U.nil $
|
||||
U.fromText "bd2aa95a-db51-4cc4-9fea-0f9cf79003a4")
|
||||
ts
|
||||
Nothing
|
||||
Processing
|
||||
Nothing)
|
||||
let myState =
|
||||
State
|
||||
(zgb_net chainInfo)
|
||||
(c_zebraHost config)
|
||||
(c_zebraPort config)
|
||||
"test.db"
|
||||
(zgi_build zebra)
|
||||
(zgb_blocks chainInfo)
|
||||
forkIO $
|
||||
run (c_zenithPort config) $
|
||||
serveWithContext
|
||||
(Servant.Proxy :: Servant.Proxy ZenithRPC)
|
||||
ctx
|
||||
(zenithServer myState)
|
||||
threadDelay 1000000
|
||||
putStrLn "Test server is up!"
|
||||
|
||||
-- | Make a Zebra RPC call
|
||||
makeZenithCall ::
|
||||
T.Text -- ^ Hostname for `zebrad`
|
||||
-> Int -- ^ Port for `zebrad`
|
||||
-> BS.ByteString
|
||||
-> BS.ByteString
|
||||
-> ZenithMethod -- ^ RPC method to call
|
||||
-> ZenithParams -- ^ List of parameters
|
||||
-> IO (Either String ZenithResponse)
|
||||
makeZenithCall host port usr pwd m params = do
|
||||
let payload = RpcCall "2.0" "zh" m params
|
||||
let myRequest =
|
||||
setRequestBodyJSON payload $
|
||||
setRequestPort port $
|
||||
setRequestHost (E.encodeUtf8 host) $
|
||||
setRequestBasicAuth usr pwd $ setRequestMethod "POST" defaultRequest
|
||||
r <- httpJSONEither myRequest
|
||||
case getResponseStatusCode r of
|
||||
403 -> return $ Left "Invalid credentials"
|
||||
200 ->
|
||||
case getResponseBody r of
|
||||
Left e -> return $ Left $ show e
|
||||
Right r' -> return $ Right r'
|
||||
e -> return $ Left $ show e ++ show (getResponseBody r)
|
1009
test/Spec.hs
1009
test/Spec.hs
File diff suppressed because it is too large
Load diff
|
@ -1 +1 @@
|
|||
Subproject commit e8074419cfb54559a4c09731ad2448d5930869a2
|
||||
Subproject commit 7d3ae36d2b48b8ed91a70e40a77fb7efe57765a0
|
1031
zenith-openrpc.json
Normal file
1031
zenith-openrpc.json
Normal file
File diff suppressed because it is too large
Load diff
79
zenith.cabal
79
zenith.cabal
|
@ -1,6 +1,6 @@
|
|||
cabal-version: 3.0
|
||||
name: zenith
|
||||
version: 0.6.0.0-beta
|
||||
version: 0.7.1.0-beta
|
||||
license: MIT
|
||||
license-file: LICENSE
|
||||
author: Rene Vergara
|
||||
|
@ -35,56 +35,65 @@ library
|
|||
Zenith.Utils
|
||||
Zenith.Zcashd
|
||||
Zenith.Scanner
|
||||
Zenith.RPC
|
||||
Zenith.Tree
|
||||
hs-source-dirs:
|
||||
src
|
||||
build-depends:
|
||||
Clipboard
|
||||
, Hclip
|
||||
, JuicyPixels
|
||||
, aeson
|
||||
, array
|
||||
, ascii-progress
|
||||
, async
|
||||
, base >=4.12 && <5
|
||||
, base64-bytestring
|
||||
, binary
|
||||
, borsh
|
||||
, brick
|
||||
, bytestring
|
||||
, configurator
|
||||
, data-default
|
||||
, directory
|
||||
, filepath
|
||||
, esqueleto
|
||||
, resource-pool
|
||||
, binary
|
||||
, exceptions
|
||||
, monad-logger
|
||||
, vty-crossplatform
|
||||
, secp256k1-haskell >= 1
|
||||
, pureMD5
|
||||
, filepath
|
||||
, ghc
|
||||
, generics-sop
|
||||
, haskoin-core
|
||||
, hexstring
|
||||
, http-client
|
||||
, http-conduit
|
||||
, http-types
|
||||
, JuicyPixels
|
||||
, qrcode-core
|
||||
, qrcode-juicypixels
|
||||
, microlens
|
||||
, microlens-mtl
|
||||
, microlens-th
|
||||
, monad-logger
|
||||
, transformers
|
||||
, monomer
|
||||
, mtl
|
||||
, persistent
|
||||
, Hclip
|
||||
, persistent-sqlite
|
||||
, persistent-template
|
||||
, process
|
||||
, pureMD5
|
||||
, qrcode-core
|
||||
, qrcode-juicypixels
|
||||
, regex-base
|
||||
, regex-compat
|
||||
, regex-posix
|
||||
, resource-pool
|
||||
, scientific
|
||||
, secp256k1-haskell >= 1
|
||||
, servant-server
|
||||
, text
|
||||
, text-show
|
||||
, time
|
||||
, uuid
|
||||
, vector
|
||||
, vty
|
||||
, vty-crossplatform
|
||||
, word-wrap
|
||||
, zcash-haskell
|
||||
--pkgconfig-depends: rustzcash_wrapper
|
||||
|
@ -110,15 +119,21 @@ executable zenith
|
|||
pkgconfig-depends: rustzcash_wrapper
|
||||
default-language: Haskell2010
|
||||
|
||||
executable zenscan
|
||||
ghc-options: -main-is ZenScan -threaded -rtsopts -with-rtsopts=-N
|
||||
main-is: ZenScan.hs
|
||||
executable zenithserver
|
||||
ghc-options: -main-is Server -threaded -rtsopts -with-rtsopts=-N
|
||||
main-is: Server.hs
|
||||
hs-source-dirs:
|
||||
app
|
||||
build-depends:
|
||||
base >=4.12 && <5
|
||||
, configurator
|
||||
, monad-logger
|
||||
, wai-extra
|
||||
, warp
|
||||
, servant-server
|
||||
, text
|
||||
, unix
|
||||
, zcash-haskell
|
||||
, zenith
|
||||
pkgconfig-depends: rustzcash_wrapper
|
||||
default-language: Haskell2010
|
||||
|
@ -132,8 +147,11 @@ test-suite zenith-tests
|
|||
build-depends:
|
||||
base >=4.12 && <5
|
||||
, bytestring
|
||||
, aeson
|
||||
, configurator
|
||||
, monad-logger
|
||||
, borsh
|
||||
, aeson
|
||||
, data-default
|
||||
, sort
|
||||
, text
|
||||
|
@ -148,3 +166,34 @@ test-suite zenith-tests
|
|||
, zenith
|
||||
pkgconfig-depends: rustzcash_wrapper
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite zenithserver-tests
|
||||
type: exitcode-stdio-1.0
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
main-is: ServerSpec.hs
|
||||
hs-source-dirs:
|
||||
test
|
||||
build-depends:
|
||||
base >=4.12 && <5
|
||||
, bytestring
|
||||
, aeson
|
||||
, configurator
|
||||
, monad-logger
|
||||
, data-default
|
||||
, sort
|
||||
, text
|
||||
, time
|
||||
, uuid
|
||||
, http-conduit
|
||||
, persistent
|
||||
, persistent-sqlite
|
||||
, hspec
|
||||
, hexstring
|
||||
, warp
|
||||
, servant-server
|
||||
, HUnit
|
||||
, directory
|
||||
, zcash-haskell
|
||||
, zenith
|
||||
pkgconfig-depends: rustzcash_wrapper
|
||||
default-language: Haskell2010
|
||||
|
|
BIN
zenith_er.bmp
BIN
zenith_er.bmp
Binary file not shown.
Before Width: | Height: | Size: 7.7 MiB |
BIN
zenith_er.png
BIN
zenith_er.png
Binary file not shown.
Before Width: | Height: | Size: 329 KiB |
Loading…
Reference in a new issue