diff --git a/client/asset/btc/livetest/livetest.go b/client/asset/btc/livetest/livetest.go index d8144317b6..d7fe5471a2 100644 --- a/client/asset/btc/livetest/livetest.go +++ b/client/asset/btc/livetest/livetest.go @@ -63,6 +63,7 @@ func tBackend(ctx context.Context, t *testing.T, cfg *Config, dir string, wallet reportName := fmt.Sprintf("%s:%s-%s", cfg.Asset.Symbol, walletName.Node, walletName.Name) walletCfg := &asset.WalletConfig{ + Type: walletName.WalletType, Settings: settings, TipChange: func(err error) { blkFunc(reportName, err) @@ -136,6 +137,8 @@ func randBytes(l int) []byte { type WalletName struct { Node string Name string + // WalletType is optional + WalletType string // Filename is optional. If specified, it will be used instead of // [node].conf. Filename string diff --git a/client/asset/firo/firo.go b/client/asset/firo/firo.go index 9ffe94362d..3203c990d5 100644 --- a/client/asset/firo/firo.go +++ b/client/asset/firo/firo.go @@ -26,18 +26,19 @@ import ( ) const ( - version = 0 - BipID = 136 // Zcoin XZC - minNetworkVersion = 141201 // bitcoin 0.14 base - walletTypeRPC = "firodRPC" - estimateFeeConfs = 2 // 2 blocks should be enough + version = 0 + BipID = 136 // Zcoin XZC + minNetworkVersion = 141201 // bitcoin 0.14 base + walletTypeRPC = "firodRPC" + walletTypeElectrum = "electrumRPC" + estimateFeeConfs = 2 // 2 blocks should be enough mainnetExplorerFeeAPI = "https://explorer.firo.org/insight-api-zcoin/utils/estimatefee" testnetExplorerFeeAPI = "https://testexplorer.firo.org/insight-api-zcoin/utils/estimatefee" ) var ( - configOpts = append(btc.RPCConfigOpts("Firo", "8168"), []*asset.ConfigOption{ + configOpts = append(btc.RPCConfigOpts("Firo", "8888"), []*asset.ConfigOption{ { Key: "fallbackfee", DisplayName: "Fallback fee rate", @@ -70,13 +71,21 @@ var ( Version: version, SupportedVersions: []uint32{version}, UnitInfo: dexfiro.UnitInfo, - AvailableWallets: []*asset.WalletDefinition{{ - Type: walletTypeRPC, - Tab: "External", - Description: "Connect to firod", - DefaultConfigPath: dexbtc.SystemConfigPath("firo"), - ConfigOpts: configOpts, - }}, + AvailableWallets: []*asset.WalletDefinition{ + { + Type: walletTypeRPC, + Tab: "Firo Core (external)", + Description: "Connect to firod", + DefaultConfigPath: dexbtc.SystemConfigPath("firo"), + ConfigOpts: configOpts, + }, + { + Type: walletTypeElectrum, + Tab: "Electrum-Firo (external)", + Description: "Use an external Electrum-Firo Wallet", + ConfigOpts: append(btc.ElectrumConfigOpts, btc.CommonConfigOpts("FIRO", false)...), + }, + }, } ) @@ -128,8 +137,6 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) Simnet: "28888", } - var w *btc.ExchangeWalletFullNode - cloneCFG := &btc.BTCCloneCFG{ WalletCFG: cfg, MinNetworkVersion: minNetworkVersion, @@ -144,7 +151,7 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) Segwit: false, InitTxSize: dexbtc.InitTxSize, InitTxSizeBase: dexbtc.InitTxSizeBase, - LegacyBalance: true, + LegacyBalance: cfg.Type == walletTypeRPC, LegacyRawFeeLimit: true, // sendrawtransaction Has single arg allowhighfees ArglessChangeAddrRPC: true, // getrawchangeaddress has No address-type arg OmitAddressType: true, // getnewaddress has No address-type arg @@ -153,18 +160,30 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) NumericGetRawRPC: false, // getrawtransaction uses either 0/1 Or true/false LegacyValidateAddressRPC: true, // use validateaddress to read 'ismine' bool SingularWallet: true, // one wallet/node - UnlockSpends: false, // checked after sendtoaddress + UnlockSpends: true, // Firo chain wallet does Not unlock coins after sendrawtransaction AssetID: BipID, FeeEstimator: estimateFee, ExternalFeeEstimator: fetchExternalFee, - PrivKeyFunc: func(addr string) (*btcec.PrivateKey, error) { - return privKeyForAddress(w, addr) - }, + PrivKeyFunc: nil, // set only for walletTypeRPC below } - var err error - w, err = btc.BTCCloneWallet(cloneCFG) - return w, err + switch cfg.Type { + case walletTypeRPC: + var exw *btc.ExchangeWalletFullNode + // override PrivKeyFunc - we need our own Firo dumpprivkey fn + cloneCFG.PrivKeyFunc = func(addr string) (*btcec.PrivateKey, error) { + return privKeyForAddress(exw, addr) + } + var err error + exw, err = btc.BTCCloneWallet(cloneCFG) + return exw, err + case walletTypeElectrum: + // override Ports - no default ports + cloneCFG.Ports = dexbtc.NetPorts{} + return btc.ElectrumWallet(cloneCFG) + default: + return nil, fmt.Errorf("unknown wallet type %q for firo", cfg.Type) + } } // rpcCaller is satisfied by ExchangeWalletFullNode (baseWallet), providing diff --git a/client/asset/firo/regnet_test.go b/client/asset/firo/regnet_test.go index 9b71a4f9fa..80a6689a05 100644 --- a/client/asset/firo/regnet_test.go +++ b/client/asset/firo/regnet_test.go @@ -5,14 +5,9 @@ package firo // Regnet tests expect the Firo test harness to be running. // // Sim harness info: -// The harness has three wallets, alpha, beta, and gamma. -// All three wallets have confirmed UTXOs. -// The beta wallet has only coinbase outputs. -// The alpha wallet has coinbase outputs too, but has sent some to the gamma -// wallet, so also has some change outputs. -// The gamma wallet has regular transaction outputs of varying size and -// confirmation count. Value:Confirmations = -// 10:8, 18:7, 5:6, 7:5, 1:4, 15:3, 3:2, 25:1 +// The harness has four nodes: alpha, beta, gamma and delta with one wallet +// per node. All wallets have confirmed UTXOs. The alpha wallet has only +// coinbase outputs. import ( "context" @@ -38,23 +33,25 @@ var ( } ) +// Run harness with NOMINER="1" func TestWallet(t *testing.T) { livetest.Run(t, &livetest.Config{ NewWallet: NewWallet, LotSize: tLotSize, Asset: tFIRO, + SplitTx: true, FirstWallet: &livetest.WalletName{ - Node: "alpha", - Name: "alpha", + WalletType: walletTypeRPC, + Node: "alpha", }, SecondWallet: &livetest.WalletName{ - Node: "beta", - Name: "beta", + WalletType: walletTypeRPC, + Node: "gamma", }, }) } -// Tests only mainnet, testnet expected to return -1 normally +// Tests only mainnet, testnet is expected to return -1 normally func TestFetchExternalFee(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() @@ -62,5 +59,5 @@ func TestFetchExternalFee(t *testing.T) { if err != nil { t.Fatal(err) } - fmt.Printf("#### External fee rate fetched:: %d sat/B\n", rate) + fmt.Printf("External fee rate fetched:: %d sat/B\n", rate) } diff --git a/client/cmd/simnet-trade-tests/run b/client/cmd/simnet-trade-tests/run index eb14b3e327..37483fd9bb 100755 --- a/client/cmd/simnet-trade-tests/run +++ b/client/cmd/simnet-trade-tests/run @@ -68,6 +68,10 @@ case $1 in ./simnet-trade-tests --base1node trading1 --base2node trading2 --quote firo ${@:2} ;; + dcrfiroelectrum) + ./simnet-trade-tests --base1node trading1 --base2node trading2 --quote firo --quote1type electrum ${@:2} + ;; + zecbtc) ./simnet-trade-tests --base zec --quote btc --regasset btc ${@:2} ;; @@ -98,6 +102,7 @@ dcrdoge - RPC wallets on DCR-DOGE market dcrdgb - RPC wallets on DCR-DGB market dcreth - Decred RPC wallet and Ethereum native wallet on DCR-ETH market dcrfiro - RPC wallets on DCR-FIRO market +dcrfiroelectrum - Decred RPC wallet and Firo Electrum wallet on DCR-FIRO market zecbtc - RPC wallets on ZEC-BTC market dcrdextt - Decred RPC wallet and Ethereum wallet with dextt.eth test token on market DCR-DEXTT.ETH diff --git a/client/core/simnet_trade.go b/client/core/simnet_trade.go index b39ede4c23..30665eeae8 100644 --- a/client/core/simnet_trade.go +++ b/client/core/simnet_trade.go @@ -1612,6 +1612,10 @@ func bchWallet(wt SimWalletType, node string) (*tWallet, error) { return btcCloneWallet(bch.BipID, node, wt) } +func firoWallet(wt SimWalletType, node string) (*tWallet, error) { + return btcCloneWallet(firo.BipID, node, wt) +} + func ethWallet() (*tWallet, error) { return &tWallet{ fund: true, @@ -1721,10 +1725,6 @@ func dgbWallet(node string) (*tWallet, error) { return btcCloneWallet(dgb.BipID, node, WTCoreClone) } -func firoWallet(node string) (*tWallet, error) { - return btcCloneWallet(firo.BipID, node, WTCoreClone) -} - func zecWallet(node string) (*tWallet, error) { return btcCloneWallet(zec.BipID, node, WTCoreClone) } @@ -1754,7 +1754,7 @@ func (s *simulationTest) newClient(name string, cl *SimClient) (*simulationClien case dash.BipID: tw, err = dashWallet(node) case firo.BipID: - tw, err = firoWallet(node) + tw, err = firoWallet(wt, node) case zec.BipID: tw, err = zecWallet(node) default: diff --git a/dex/testing/firo/README_ELECTRUM_HARNESSES.md b/dex/testing/firo/README_ELECTRUM_HARNESSES.md new file mode 100644 index 0000000000..7bb13a9247 --- /dev/null +++ b/dex/testing/firo/README_ELECTRUM_HARNESSES.md @@ -0,0 +1,135 @@ + +# Electrum-Firo Harness Support + +Harnesses to run Electrum-Firo wallet on regtest. + +## 1. Firo Chain Server Test Harness + +The **harness.sh** chain server harness is a collection of tmux scripts that +collectively create a sandboxed environment for testing dex swap transactions. +See Also: README_HARNESS.md + +## 2. ElectrumX-Firo Test Harness + +The harness is a script named **electrumx.sh** which downloads a git repo +containing a release version of ElectrumX-Firo server. + +It requires **harness.sh** Firo chain server harness running. + +## 3. Electrum-Firo Test Harness + +The harness is a script named **electrum.sh** which downloads a git repo containing +a release version of Electrum-Firo wallet client. + +It requires **electrumx.sh** Firo ElectrumX-Firo server harness running. + +Which in turn requires **harness.sh** Firo chain server harness running. + +## Dependencies + +The **harness.sh** script depends on [firod] and [firo-cli] to run. +Go to for binaries or source. + +The **electrumx.sh** script depends on [python3], [python3 pip] and [git] to run. +Python3 v3.6 is coded but this script was tested using python3.10. Some testing +(minimal) was done with 3.7. Git should be latest. Pip will be downloaded and it's version upgraded by the script into a virtual environment each time. + +The **electrum.sh** script depends on [python3], [python3 pip] and [git] to run. +Python3 v3.6 is coded but this script was tested using python3.10. Some testing +(minimal) was done with 3.7. Python 3.8, 3.9 would be expected to work although +untested. However using Python 3.11 there are dependencies which will not build +and are unsupported by their respective maintainers on PyPI. Git should be latest. +Pip will be re-downloaded and it's version upgraded by the script into a virtual environment each time. + +### Architecture + +- ELECTRUM WALLET CLIENT +- ELECTRUMX SERVER +- FIRO CHAIN HARNESS + +All three scripts store data in **~/dextest/...** dirsectory tree. + +## Using + +You must have `firod` and `firo-cli` in `PATH` to use the chain server harness. Use 3 tty's +and run each line below in a **separate** tty: + +```bash +$ ./harness.sh +$ ./electrumx.sh +$ ./electrum.sh +``` + +The Electrum-Firo wallet client will have a prepared, empty but encrypted +regtest wallet. + +Password is "abc". + +The script starts the Electrum-Firo wallet client in CLI mode with debug level +logging to stderr. Change STARTUP= in the script to "GUI" to start the Gui or +"DAEMON" to start as a daemon. Stop the daemon with the `stop_daemon` script. + +## Development + +### Firo Chain Server + +For the Firo chain server see the README_HARNESS.md in this directory. + +### Server + +The **electrumx.sh** script first cleans part of the **~/dextest/electrum/firo/server...** +directory tree. + +The script then downloads a specific commit from: + to the .../server/electrumx-repo directory. + +It then creates a python virtual environment (venv) sandbox and installs the +requesting python3 interpreter, latest version of pip and all required modules +in `setup.py` into the sandbox. +If any need building from C source it will be done at this point. + +Server certificates are created. Then all the required environment variables to +configure the server exported; and the electrumX daemon started + +The electrumX daemon will connect to the firo chain harness node **alpha** + +### Client + +The **electrum.sh** script first cleans part of the **~/dextest/electrum/firo/client...** +directory tree. + +The script then downloads a specific commit from: + to the ../client/electrum-repo directory. + +It then creates a python virtual environment (venv) sandbox and installs the +requesting python3 interpreter, latest version of pip and all required modules +from files in `contrib/requirements` directory into the sandbox. If any need building from C source it will be done at +this point. + +A prepared, empty but encrypted electrum wallet is copied to the electrum data +directory at **~/dextest/electrum/firo/client/wallet/regtest/wallets**. + +By default the electrum client is started as a daemon and the default wallet is loaded. +Use **stop-daemon** script to stop the daemon. + +The electrum client wallet will connect to the firo electrumX daemon. + +### Simnet Trade Testing + +In seperate tty's start up: + + - btc harness + - dcr harness + - firo harness + - electrumx.sh Electrum-Firo regtest server + - electrum.sh Electrum-Firo regtest wallet + - dcrdex harness + +Ensure dcrctl, firod and firo-cli are in PATH for simnet-trade-tests exec process +mining. + +```bash +$ ./run dcrfiroelectrum --runonce --all +``` + +Best results by nuking everything after each run of the simnet-trade-test test suite. diff --git a/dex/testing/firo/README_HARNESS.md b/dex/testing/firo/README_HARNESS.md new file mode 100644 index 0000000000..07f37b9761 --- /dev/null +++ b/dex/testing/firo/README_HARNESS.md @@ -0,0 +1,106 @@ +# Firo Chain Server Test Harness + +The harness is a collection of tmux scripts that collectively creates a +sandboxed environment for testing dex swap transactions. + +## Dependencies + +The harness depends on [firod] and [firo-cli] to run. +Go to for binaries or source. + +## Using + +You must have `firod` and `firo-cli` in `PATH` to use the harness. + +The harness script will create four connected regtest nodes **alpha**, **beta**, +**gamma**, **delta**. Each node will have one empty but encrypted wallet. The script will mine some blocks and send some regular transactions. + +This simnet harness sets up 4 Firo nodes and a set of harness controls +Each node has a prepared, encrypted, empty wallet + +```text +alpha/ +├── alpha.conf +└── regtest + ├── wallet.dat + +beta/ +├── beta.conf +└── regtest + ├── wallet.dat + +gamma/ +├── gamma.conf +└── regtest + ├── wallet.dat + +delta/ +├── delta.conf +└── regtest + ├── wallet.dat + +└── harness-ctl + ├── alpha + ├── beta + ├── connect-alpha + ├── delta + ├── gamma + ├── mine-alpha + ├── mine-beta + ├── quit + ├── reorg + ├── start-wallet + └── stop-wallet +``` + +**alpha** is purely a mining node/wallet, and will have mostly coinbase +UTXOs to spend. + +**beta**, **gamma**, **delta** will all connect to **alpha** as peers. + +## Harness Control Scripts + +The `./harness.sh` script will drop you into a tmux window in a directory +called `harness-ctl`. Inside this directory are a number of scripts to +allow you to perform RPC calls against each wallet. + +`./alpha` `./beta` `./gamma` `./delta` are just `firo-cli` configured for their +respective node-wallets. + +`./alpha getbalance`, for example. + +### Other Examples + +`./reorg` will step through a script that causes the alpha node to undergo a reorg. + +`./quit` shuts down the nodes and closes the tmux session. + +## Background Miner + +By default the harness also sets up a background miner which mines on the alpha +node every 15s. This can upset some test logic so it can be disabled by +setting environment var NOMINER="1" + +## Dev Stuff + +If things aren't looking right, you may need to look at the node windows to +see errors. In tmux, you can navigate between windows by typing `Ctrl+b` and +then the window number. The window numbers are listed at the bottom +of the tmux window. `Ctrl+b` followed by the number `0`, for example, will +change to the alpha node window. Examining the node output to look for errors +is usually a good first debugging step. + +An unfortunate issue that will pop up if you're fiddling with the script is +zombie firod processes preventing the harness nodes from binding to the +specified ports. You'll have to manually hunt down the zombie PIDs and `kill` +them if this happens. + +### Zombie Killer + +(debian) + +```bash +$ kill -9 # find & kill any running regtest daemons + ... +$ tmux kill-session -t firo-harness # kill firo-harness session +``` diff --git a/dex/testing/firo/electrum.sh b/dex/testing/firo/electrum.sh new file mode 100755 index 0000000000..9c68666d9c --- /dev/null +++ b/dex/testing/firo/electrum.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# +# Set up Electrum-Firo (electrum) regtest client wallet for testing. +# - Expects the firo regtest chain server harness to be running. +# - Expects an ElectrumX_Firo server runing on top of the chain server +# - Connects to the ElectrumX server over SSL at localhost:EX_PORT +# - Exposes electrum RPC at localhost:RPC_PORT - Dex connects here +# +# Requires: +# - python3 - tested python3.10 and minimal testing python3.7 +# - pip3 - for boostrap loading pip +# - git +# +# See Also: README_ELECTRUM_HARNESSES.md and README_HARNESS.md + +set -ex + +# https://github.com/spesmilo/electrum/issues/7833 (fixed in 4.3) +# possible alt workaround: python3 -m pip install "protobuf>=3.12,<4" +export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + +SCRIPT_DIR=$(pwd) + +# Electrum-Firo Version 4.1.5.2 +COMMIT=a3f64386efc9069cae83e23c241331de6f418b2f + +GENESIS=a42b98f04cc2916e8adfb5d9db8a2227c4629bc205748ed2f33180b636ee885b # regtest +RPCPORT=8001 +EX_PORT=50002 + +ASSET_DIR=~/dextest/electrum/firo +ELECTRUM_DIR=${ASSET_DIR}/client +REPO_DIR=${ELECTRUM_DIR}/electrum-repo +WALLET_DIR=${ELECTRUM_DIR}/wallet +NET_DIR=${WALLET_DIR}/regtest + +# startup options +# CLI, DAEMON, GUI (Default start as CLI) +STARTUP=CLI +ELECTRUM_REGTEST_ARGS="--regtest --dir=${WALLET_DIR}" +WALLET_PASSWORD="abc" + +rm -rf ${NET_DIR}/blockchain_headers ${NET_DIR}/forks ${NET_DIR}/certs ${NET_DIR}/wallets/default_wallet +mkdir -p ${NET_DIR}/regtest +mkdir -p ${NET_DIR}/wallets +mkdir -p ${REPO_DIR} + +cd ${REPO_DIR} + +if [ ! -d "${REPO_DIR}/.git" ]; then + git init + git remote add origin https://github.com/firoorg/electrum-firo.git +fi + +git remote -v + +git fetch --depth 1 origin ${COMMIT} +git reset --hard FETCH_HEAD + +if [ ! -d "${ELECTRUM_DIR}/venv" ]; then + # The venv interpreter will be this python version, e.g. python3.10 + python3 -m venv ${ELECTRUM_DIR}/venv +fi +source ${ELECTRUM_DIR}/venv/bin/activate +python --version +python -m pip install --upgrade pip # can support more versions than ensurepip +pip install -e . +pip install requests cryptography pycryptodomex +if [ "${STARTUP}" == "GUI" ]; +then + pip install pyqt5 +fi + +cp "${SCRIPT_DIR}/electrum_regtest_wallet" "${NET_DIR}/wallets/default_wallet" + +cat > "${NET_DIR}/config" < "${ASSET_DIR}/client-config.ini" < "${ALPHA_DIR}/alpha.conf" < "./start-wallet" < ${NODES_ROOT}/\$1/\$1.conf - ${DAEMON} -rpcuser=user -rpcpassword=pass \ -rpcport=\$2 -datadir=${NODES_ROOT}/\$1 -txindex=1 -regtest=1 -dandelion=0 \ -debug=rpc -debug=net -debug=mempool -debug=walletdb -debug=addrman -debug=mempoolrej \ @@ -285,46 +284,64 @@ tmux send-keys -t $SESSION:2 C-c tmux send-keys -t $SESSION:3 C-c tmux wait-for alpha${SYMBOL} tmux wait-for beta${SYMBOL} +tmux wait-for delta${SYMBOL} +tmux wait-for gamma${SYMBOL} # seppuku tmux kill-session EOF chmod +x "${HARNESS_DIR}/quit" ################################################################################ -# Generate the first block +# Hook up peers ################################################################################ tmux send-keys -t $SESSION:4 "./beta addnode 127.0.0.1:${ALPHA_LISTEN_PORT} add${DONE}" C-m\; ${WAIT} tmux send-keys -t $SESSION:4 "./delta addnode 127.0.0.1:${ALPHA_LISTEN_PORT} add${DONE}" C-m\; ${WAIT} tmux send-keys -t $SESSION:4 "./gamma addnode 127.0.0.1:${ALPHA_LISTEN_PORT} add${DONE}" C-m\; ${WAIT} -# This timeout is apparently critical. Give the nodes time to sync. -sleep 1 +# Give the nodes time to sync. +sleep 3 +################################################################################ +# Generate block #1 +################################################################################ +echo "Unlocking mining wallet" tmux send-keys -t $SESSION:4 "./alpha walletpassphrase ${WALLET_PASSWORD} 100000000${DONE}" C-m\; ${WAIT} -tmux send-keys -t $SESSION:4 "./beta walletpassphrase ${WALLET_PASSWORD} 100000000${DONE}" C-m\; ${WAIT} -echo "Generating 350 blocks for alpha" -tmux send-keys -t $SESSION:4 "./alpha generatetoaddress 350 ${ALPHA_MINING_ADDR}${DONE}" C-m\; ${WAIT} +echo "Generating 333 blocks for alpha" +tmux send-keys -t $SESSION:4 "./alpha generatetoaddress 333 ${ALPHA_MINING_ADDR}${DONE}" C-m\; ${WAIT} -################################################################################# -# Send beta some coin ################################################################################ +# Send beta, gamma & delta some coins +################################################################################ +echo "Unlocking test wallets beta, delta & gamma" +tmux send-keys -t $SESSION:4 "./beta walletpassphrase ${WALLET_PASSWORD} 100000000${DONE}" C-m\; ${WAIT} +tmux send-keys -t $SESSION:4 "./gamma walletpassphrase ${WALLET_PASSWORD} 100000000${DONE}" C-m\; ${WAIT} +tmux send-keys -t $SESSION:4 "./delta walletpassphrase ${WALLET_PASSWORD} 100000000${DONE}" C-m\; ${WAIT} -DELTA_ADDR=`./delta getnewaddress` -echo "delta address = '${DELTA_ADDR}'" -GAMMA_ADDR=`./gamma getnewaddress` -echo "gamma address = '${GAMMA_ADDR}'" - -# Send the beta wallet some dough. -echo "Sending some FIRO funds to other wallets in 8 transactions each" -for i in 100 180 500 700 100 150 300 250 +echo "Sending some FIRO funds to other wallets" +for i in 100 180 500 700 100 150 300 250 7 7 7 7 7 7 7 5 5 5 5 5 3 3 3 2 2 1 do - tmux send-keys -t $SESSION:4 "./alpha sendtoaddress ${BETA_MINING_ADDR} ${i}${DONE}" C-m\; ${WAIT} + tmux send-keys -t $SESSION:4 "./alpha sendtoaddress ${BETA_ADDR} ${i}${DONE}" C-m\; ${WAIT} tmux send-keys -t $SESSION:4 "./alpha sendtoaddress ${DELTA_ADDR} ${i}${DONE}" C-m\; ${WAIT} tmux send-keys -t $SESSION:4 "./alpha sendtoaddress ${GAMMA_ADDR} ${i}${DONE}" C-m\; ${WAIT} done +tmux send-keys -t $SESSION:4 "./mine-alpha 7${DONE}" C-m\; ${WAIT} -tmux send-keys -t $SESSION:4 "./mine-alpha 2${DONE}" C-m\; ${WAIT} - -# Reenable history and attach to the control session. +################################################################################ +# Setup watch background miner -- if required +################################################################################ +if [ -z "$NOMINER" ] ; then + tmux new-window -t $SESSION:5 -n "miner" $SHELL + tmux send-keys -t $SESSION:5 "cd ${HARNESS_DIR}" C-m + tmux send-keys -t $SESSION:5 "watch -n 15 ./mine-alpha 1" C-m +fi + +# Live stop/start +#tmux send-keys -t $SESSION:5 C-c C-m +#tmux send-keys -t $SESSION:5 "watch -n 15 ./mine-alpha 1" C-m + +###################################################################################### +# Reenable history select the harness control window & attach to the control session # +###################################################################################### tmux send-keys -t $SESSION:4 "set -o history" C-m +tmux select-window -t $SESSION:4 tmux attach-session -t $SESSION diff --git a/dex/testing/firo/stop-daemon b/dex/testing/firo/stop-daemon new file mode 100755 index 0000000000..5857f944d4 --- /dev/null +++ b/dex/testing/firo/stop-daemon @@ -0,0 +1,5 @@ +#!/bin/bash + +curl --data-binary \ + '{"jsonrpc":"2.0","id":"curltext","method":"stop","params":[]}' \ + http://user:pass@127.0.0.1:8001 diff --git a/dex/testing/firo/test/README.md b/dex/testing/firo/test/README.md new file mode 100644 index 0000000000..bddcbfee6c --- /dev/null +++ b/dex/testing/firo/test/README.md @@ -0,0 +1,44 @@ +# Testing Firo ElectrumX Servers + +## ElectrumX Test + +This runs a test which accesses: + +- Firo testnet electrumX server: __95.179.164.13:51002__ +- Firo mainnet electrumX server: __electrumx.firo.org:50002__ + +Optionally: + +- Firo simnet ElectrumX server running a dex testing harness chain. + +It tests the capabilities of the server. + +## Dependencies + +The regtest/simnet server test has a dependency on: + +- The Firo harness chain: __harness.sh__ +- The Firo simnet electrumx server: __electrumx.sh__ + +The simnet ElectrumX server (electrumx.sh) should connect over RPC to the +harness chain. + +## Testing mainnet, testnet only + +Run as a Go test: + +__go test -v -count=1 ./...__ + +## Testing mainnet, testnet and additionally with regtest + +Start **./harness.sh__ tmux script which starts 4 instances of the Firo regtest +harness chain. + +Set environment REGTEST="1" + +Run __./electrumx.sh__ which starts a Firo ElectrumX server that accesses the +harness chain over RPC. + +Run as a Go test: + +__go test -v -count=1 ./...__ diff --git a/dex/testing/firo/test/electrumx_network_test.go b/dex/testing/firo/test/electrumx_network_test.go new file mode 100644 index 0000000000..71c2b0b86d --- /dev/null +++ b/dex/testing/firo/test/electrumx_network_test.go @@ -0,0 +1,191 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org/license/1.0.0. + +// Connects to one ElectrumX-Firo testnet server and one ElectrumX-Firo +// mainnet server and tests a subset of electrumx-firo commands. +// +// See also: +// https://electrumx.readthedocs.io/en/latest/protocol-methods.html +// +// To also run on Regtest network: +// +// Chain: dex/testing/firo/harness.sh +// ElectrumX-Firo server: dex/testing/firo/electrumx.sh +// export REGTEST=1 + +package firo + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/hex" + "fmt" + "net" + "os" + "sort" + "strings" + "testing" + "time" + + "decred.org/dcrdex/client/asset/btc/electrum" + + "github.com/davecgh/go-spew/spew" + "github.com/gcash/bchd/wire" +) + +type electrumNetwork string + +var regtest electrumNetwork = "regtest" +var testnet electrumNetwork = "testnet" +var mainnet electrumNetwork = "mainnet" + +func TestServerConn(t *testing.T) { + var serverAddr string + var genesis string + var randomTx string + var blockHeight uint32 + var blockCount uint32 + + if os.Getenv("REGTEST") != "" { + serverAddr = "127.0.0.1:50002" // electrumx-firo regtest + genesis = "a42b98f04cc2916e8adfb5d9db8a2227c4629bc205748ed2f33180b636ee885b" + randomTx = "7e3d477aaac3534da5624aecf82583a1381508bf672ec18dfc0b9e7b7b0e2dfe" + blockHeight = 330 + blockCount = 5 + runOneNet(t, regtest, serverAddr, genesis, randomTx, blockHeight, blockCount) + } + + serverAddr = "95.179.164.13:51002" + genesis = "aa22adcc12becaf436027ffe62a8fb21b234c58c23865291e5dc52cf53f64fca" + randomTx = "af5bc24f8f995ff0439af0c9f3ba607995f74a84d8cc06e4a6ba7f3c7692a41a" + blockHeight = 105793 + blockCount = 3 + runOneNet(t, testnet, serverAddr, genesis, randomTx, blockHeight, blockCount) + + serverAddr = "electrumx.firo.org:50002" + genesis = "4381deb85b1b2c9843c222944b616d997516dcbd6a964e1eaf0def0830695233" + randomTx = "3cbf438c07938a5da6293f3a9be9b786e01c14b459d56cd595dcd534db128ed3" + blockHeight = 676700 + blockCount = 3 + runOneNet(t, mainnet, serverAddr, genesis, randomTx, blockHeight, blockCount) +} + +func runOneNet(t *testing.T, electrumNetwork electrumNetwork, + addr, genesis, randomTx string, blockHeight, blockCount uint32) { + + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + + host, _, err := net.SplitHostPort(addr) + if err != nil { + t.Fatal(err) + } + + rootCAs, _ := x509.SystemCertPool() + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + RootCAs: rootCAs, + MinVersion: tls.VersionTLS12, // works ok + ServerName: host, + } + + opts := &electrum.ConnectOpts{ + TLSConfig: tlsConfig, + DebugLogger: electrum.StdoutPrinter, + } + + sc, err := electrum.ConnectServer(ctx, addr, opts) + if err != nil { + t.Fatal(err) + } + t.Log(sc.Proto()) + + fmt.Printf("\n\n ** Connected to %s **\n\n", electrumNetwork) + + banner, err := sc.Banner(ctx) + if err != nil { + t.Fatal(err) + } + spew.Dump(banner) + + feats, err := sc.Features(ctx) + if err != nil { + t.Fatal(err) + } + spew.Dump(feats) + + if feats.Genesis != genesis { + t.Fatalf("wrong genesis hash for Firo-Electrum on %s: %v", + feats.Genesis, electrumNetwork) + } + t.Log("Genesis correct") + + // All tx hashes will change on each regtest startup. So use + // listtransactions or listunspent on the harness to get a + // new random tx. + txres, err := sc.GetTransaction(ctx, randomTx) + if err != nil { + t.Fatal(err) + } + spew.Dump(txres) + + // Do not make block count too big or electrumX may throttle response + // as an anti ddos measure + hdrsRes, err := sc.BlockHeaders(ctx, blockHeight, blockCount) + if err != nil { + t.Fatal(err) + } + spew.Dump(hdrsRes) + + hdrReader := hex.NewDecoder(strings.NewReader(hdrsRes.HexConcat)) + var lastHdr *wire.BlockHeader + timestamps := make([]int64, 0, hdrsRes.Count) + for i := uint32(0); i < hdrsRes.Count; i++ { + hdr := &wire.BlockHeader{} + err = hdr.Deserialize(hdrReader) + if err != nil { + if i > 0 { + t.Fatalf("Failed to deserialize header for block %d: %v", + blockHeight+i, err) + break // we have at least one time stamp, work with it + } + t.Fatal(err) + } + timestamps = append(timestamps, hdr.Timestamp.Unix()) + if i == blockCount-1 && blockCount == hdrsRes.Count { + lastHdr = hdr + } + } + spew.Dump(lastHdr) + + sort.Slice(timestamps, func(i, j int) bool { + return timestamps[i] < timestamps[j] + }) + + medianTimestamp := timestamps[len(timestamps)/2] + t.Logf("Median time for block %d: %v", blockHeight+hdrsRes.Count-1, time.Unix(medianTimestamp, 0)) + + hdrRes, _, _ := sc.SubscribeHeaders(ctx) + spew.Dump(hdrRes) + + height := blockHeight +out: + for { + select { + case <-ctx.Done(): + break out + case <-time.After(5 * time.Second): + hdr, err := sc.BlockHeader(ctx, height) + if err != nil { + t.Log(err) + break out + } + t.Log(hdr) + } + height++ + } + sc.Shutdown() + + <-sc.Done() +} diff --git a/dex/testing/firo/alpha_wallet.dat b/dex/testing/firo/wallets/alpha_wallet.dat similarity index 100% rename from dex/testing/firo/alpha_wallet.dat rename to dex/testing/firo/wallets/alpha_wallet.dat diff --git a/dex/testing/firo/beta_wallet.dat b/dex/testing/firo/wallets/beta_wallet.dat similarity index 100% rename from dex/testing/firo/beta_wallet.dat rename to dex/testing/firo/wallets/beta_wallet.dat diff --git a/dex/testing/firo/wallets/delta_wallet.dat b/dex/testing/firo/wallets/delta_wallet.dat new file mode 100644 index 0000000000..d137f9d01e Binary files /dev/null and b/dex/testing/firo/wallets/delta_wallet.dat differ diff --git a/dex/testing/firo/wallets/gamma_wallet.dat b/dex/testing/firo/wallets/gamma_wallet.dat new file mode 100644 index 0000000000..5c34fd2084 Binary files /dev/null and b/dex/testing/firo/wallets/gamma_wallet.dat differ diff --git a/run_tests.sh b/run_tests.sh index 75951baf60..601b97bb5f 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -64,6 +64,7 @@ go test -c -o /dev/null -tags electrumlive ./client/asset/btc go test -c -o /dev/null -tags harness ./client/asset/btc/livetest go test -c -o /dev/null -tags harness ./client/asset/ltc go test -c -o /dev/null -tags harness ./client/asset/bch +go test -c -o /dev/null -tags harness ./client/asset/firo go test -c -o /dev/null -tags harness ./client/asset/eth go test -c -o /dev/null -tags rpclive ./client/asset/eth go test -c -o /dev/null -tags harness ./client/asset/zec