Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli_wallet crashes when connection to witness node is lost #177

Closed
vikramrajkumar opened this issue Jan 18, 2017 · 6 comments
Closed

cli_wallet crashes when connection to witness node is lost #177

vikramrajkumar opened this issue Jan 18, 2017 · 6 comments
Labels

Comments

@vikramrajkumar
Copy link
Contributor

From @valzav on February 18, 2016 15:15

It's a big issue for the faucet - when the main witness node (out of 4) crashes it makes cli_wallet to crash and this stops faucet from registering new accounts.
It would be nice if cli_wallet could try to reconnect to several witness_node ws sockets in round robin manner.
It can read a list of available witness_node from command line arguments.

Copied from original issue: cryptonomex/graphene#591

@vikramrajkumar
Copy link
Contributor Author

From @ByronAP on April 30, 2016 14:6

on windows the cli wallet doesn't crash but rpc requests fail with invalid state and the wallet needs to be restarted to reconnect. This has been an issue for some time and should be priority. I agree with the round robin idea.

@oxarbitrage
Copy link
Member

oxarbitrage commented May 17, 2017

I was after this issue in the last few days, i know is old but the problem described is still relevant.

At first, i didn't agree with the round robin idea. I don't think we need to disconnect every X seconds and connect to a new node if the main one is working. My first approach to this was to add a command line option "--backup-servers" expecting a vector of nodes that the client may use in case the main one is failing.

So the idea was:

If main node is disconnected -> use the first backup server.
If first backup server disconnects -> use second and so on.
When no more backup servers available -> just close the program as usual.

I started to code this in the cli wallet client, had some problems i have to admit but that actually made me think on an alternative way to accomplish the same thing.

The less code we introduce to the core the less bugs we may have and this is also true for the cli_wallet. The cli_wallet is actually acting a server for other apps connect to it so we want to keep it bug free.

The new code added to bitshares need to be reviewed , tested and finally merged. This takes time but it is the correct way to go, with rigorous developer community analysis of new introduced code. Unless there is no alternative way, we only then introduce new code. If it can be resolved by alternative ways then we expose them and use that instead. This is a kind of no written law ;)

This is no laziness from my side here, in fact, find an alternative method to go over this particular issue actually toke the same or more time than adding the command line option to the core itself.

With the introduction said, lets go over the alternative way to go over this disconnection issue. Problem is a cli_wallet connected to a witness node loosing connection to this witness. Cli_wallet will crash and applications depending on it too.

The rich linux environment should have a solution for this networking problem. Yes, i think is a networking issue, not a cli_wallet problem.

Tried some python subprocess libs but i had no luck, tried some other alternatives before finding what i think it can be the best.

Meet expect(http://expect.sourceforge.net/), a tool designed to interact with interactive programs. The cli_wallet is an interactive process, where user send commands and program send a response to the console.

Expect can be simple installed(if it is not already there) in our favorite debian based box by simply:

apt-get install expect

My approach to expect was because when the node stops the cli wallets terminate always with msg:

Server has disconnected us.

My idea was to catch(expect) this msg and spawn new cli_wallet when the disconnection message is shown.

Expect is scriptable, so a file with commands will be now in charge of launching the cli_wallet.

For simplicity we are going to just use 2 node servers:

X.X.X.X:8090 -> main node server
Y.Y.Y.Y:8090 -> alternative server that will be used if main node fails.

The same concept can be extended to more than 2 severs, actually with any number of backup nodes a user will want.

Expect is not easy to use, it was not easy to get what you need done. There is plenty of information , questions and answers in the internet about it but to do complex stuff the most valuable resources are the man page of expect and a book named "Exploring Expect" by Don Libes published by O`Reilly.

Lets suppose that our cli_wallet command is something like:

/root/bitshares-munich/issue177/bitshares-core/programs/cli_wallet/cli_wallet --wallet-file my-wallet.json --server-rpc-endpoint ws://X.X.X.X:8090 -u '' -p '' -H 127.0.0.1:6666

This start the cli wallet while it expose an HTTP RPC port at 127.0.0.1:6666

This is used for example as:

curl --data '{"jsonrpc": "2.0", "params": [0, "get_block", [16292266]], "method": "call", "id": 10}' http://127.0.0.1:6666/rpc | jq

As server X.X.X.X:8090 gets down port 6666 will be closed and commands will not work anymore.

Check the following expect script exp3.exp:

#!/usr/bin/expect -f
set timeout -1
spawn /root/bitshares-munich/issue177/bitshares-core/programs/cli_wallet/cli_wallet --wallet-file my-wallet.json --server-rpc-endpoint ws://X.X.X.X:8090 -u '' -p '' -H 127.0.0.1:6666
set id1 $spawn_id
expect -i id1 eof {
        spawn  /root/bitshares-munich/issue177/bitshares-core/programs/cli_wallet/cli_wallet --wallet-file my-wallet.json --server-rpc-endpoint ws://Y.Y.Y.Y:8090 -u '' -p '' -H 127.0.0.1:6666
        set id2 $spawn_id
        expect -i id2 eof
}

Make the file executable:

chmod u+x exp3.exp

Run simply as:

./exp3.exp

Start with debug info by:

./exp3.exp -d

As we are interested on the cli just to serve in port 6666 we can't interact as a user with the cli_wallet. This can be changed by the use of "interact" keyword in our script. Interact will allow to actually use the cli the expect script launched but i left that outside as my intention was not to interact but just to serve at port 6666.

The script will run the first command until eof(end of program in this case) is found. Cli_wallet(and probably all programs) will send eof when they end(by disconnection in this case).

We don't need to be after specific "Disconnected" string as i tough in the begging.

At the moment of EOF of the first wallet, script will spawn the second command. When the second cli_wallet command is executed port 6666 will be available again for curl but this time we will be pulling data from Y.Y.Y.Y:8060 instead of X.X.X.X:8090 because main node is down.

Please note that exposing a web socket instead of an http one(this is with option -r instead of -H in the cli starting command) can be more tricky. Tools as wscat will detect the disconnection and kick the user off even if the port 6666 is available again some seconds later. This setup will need some more work but it is still possible with expect itself to regain websocket connection.

I hope this helps not only for this issue in particular but to also for similar ones that can be resolved with expect. A tool that i learned to use while writing this and that i am sure it will be part of my nodemaster arsenal, side by side with screen, wscat, upticks and many others.

@oxarbitrage
Copy link
Member

in telegram we discussed this issue with @abitmore and @pmconrad today.
@pmconrad pointed out that the script will work for the cli_wallet acting as a daemon but will not work for a normal cli_wallet session.

This is true, but it can be adapted, consider the following expect script:

#!/usr/bin/expect -f
set timeout -1
spawn /root/bitshares-munich/issue177/bitshares-core/programs/cli_wallet/cli_wallet --wallet-file my-wallet.json --server-rpc-endpoint ws://X.X.X.X:8090 -u '' -p ''
set id1 $spawn_id
interact -i id1 eof {
        spawn  /root/bitshares-munich/issue177/bitshares-core/programs/cli_wallet/cli_wallet --wallet-file my-wallet.json --server-rpc-endpoint ws://Y.Y.Y.Y:8090 -u '' -p ''
        set id2 $spawn_id
        interact -i id2 eof
}

This will allow to interact with the first node and when disconnected it will start a new one.

Please check the following script output:

root@alfredo:~/bitshares-munich/cli_python# ./exp4.exp 
spawn /root/bitshares-munich/issue177/bitshares-core/programs/cli_wallet/cli_wallet --wallet-file my-wallet.json --server-rpc-endpoint ws://X.X.X.X:8090 -u 
'' -p ''
Logging RPC to file: logs/rpc/rpc.log
3567103ms th_a       main.cpp:122                  main                 ] key_to_wif( committee_private_key ): 5KCBDTcyDqzsqehcb52tW5nU6pXife6V2rX9Yf7c3saYSzbDZ5
W 
3567104ms th_a       main.cpp:126                  main                 ] nathan_pub_key: BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV 
3567104ms th_a       main.cpp:127                  main                 ] key_to_wif( nathan_private_key ): 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 
3567104ms th_a       main.cpp:174                  main                 ] wdata.ws_server: ws://X.X.X.X:8090 
3567104ms th_a       main.cpp:179                  main                 ] wdata.ws_user: '' wdata.ws_password: '' 
Please use the set_password method to initialize a new wallet before continuing
new >>> 

new >>> get_block 1
get_block 1
{
  "previous": "0000000000000000000000000000000000000000",
  "timestamp": "2015-10-13T14:12:24",
  "witness": "1.6.8",
  "transaction_merkle_root": "0000000000000000000000000000000000000000",
  "extensions": [],
  "witness_signature": "1f53542bb60f1f7a653bac70d6b1613e73b9adc952031e30e591e601dd60d493ba5c9a832e155ff0c40ea1dd53512e9f93bf65a8191497ea67d701bc2502f93af7",
  "transactions": [],
  "block_id": "00000001b656820f72f6b28cda811778632d4998",
  "signing_key": "BTS6ZQEFsPmG6jWspNDdZHkehNmPpG7gkSHkphmRZQWaJ2LrcaVSi",
  "transaction_ids": []
}
new >>> Server has disconnected us.
9 canceled_exception: Canceled

    {}
    th_a  thread_d.hpp:461 start_next_fiber
3576166ms th_a       wallet.cpp:788                save_wallet_file     ] saving wallet to file my-wallet.json
pure virtual method called
terminate called without an active exception
spawn /root/bitshares-munich/issue177/bitshares-core/programs/cli_wallet/cli_wallet --wallet-file my-wallet.json --server-rpc-endpoint ws://Y.Y.Y.Y:8090 -
u '' -p ''
Logging RPC to file: logs/rpc/rpc.log
3576196ms th_a       main.cpp:122                  main                 ] key_to_wif( committee_private_key ): 5KCBDTcyDqzsqehcb52tW5nU6pXife6V2rX9Yf7c3saYSzbDZ5W
 
3576196ms th_a       main.cpp:126                  main                 ] nathan_pub_key: BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV 
3576196ms th_a       main.cpp:127                  main                 ] key_to_wif( nathan_private_key ): 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 
3576196ms th_a       main.cpp:174                  main                 ] wdata.ws_server: ws://Y.Y.Y.Y:8090 
3576410ms th_a       main.cpp:179                  main                 ] wdata.ws_user: '' wdata.ws_password: '' 
Please use the set_password method to initialize a new wallet before continuing
new >>> get_block 1
get_block 1
{
  "previous": "0000000000000000000000000000000000000000",
  "timestamp": "2015-10-13T14:12:24",
  "witness": "1.6.8",
  "transaction_merkle_root": "0000000000000000000000000000000000000000",
  "extensions": [],
  "witness_signature": "1f53542bb60f1f7a653bac70d6b1613e73b9adc952031e30e591e601dd60d493ba5c9a832e155ff0c40ea1dd53512e9f93bf65a8191497ea67d701bc2502f93af7",
  "transactions": [],
  "block_id": "00000001b656820f72f6b28cda811778632d4998",
  "signing_key": "BTS6ZQEFsPmG6jWspNDdZHkehNmPpG7gkSHkphmRZQWaJ2LrcaVSi",
  "transaction_ids": []
}
new >>> 

As you can see above as soon as we disconnect from the first node we open a new process to the backup one. In regards to the history, we are kind of dead there with this alternative. The history is not dumped to file but lives fully in process memory. As initial process is closed we don't have access to that memory anymore in the second.

In order to keep history across sessions we may need to dump it to a local file but that i suppose will be a subject of another issue.

@oxarbitrage
Copy link
Member

oxarbitrage commented May 19, 2017

Same can be done by wscat with the following expect script, maybe someone find it useful:

#!/usr/bin/expect -f
set timeout -1
spawn wscat -c ws://X.X.X.X:8090
set id1 $spawn_id
interact -i id1 eof {
        spawn wscat -c ws://Y.Y.Y.Y:8090
        set id2 $spawn_id
        interact -i id2 eof
}

When disconnected script opens new websocket to alternative server, command history is lost just as i n the cli_wallet previous sample, it is a new process:

root@alfredo:~/bitshares-munich/cli_python# ./exp_wscat.exp 
spawn wscat -c ws://X.X.X.X:8090
connected (press CTRL+C to quit)
> {"id":1, "method":"call", "params":[0,"get_block",[1]]}
< {"id":1,"result":{"previous":"0000000000000000000000000000000000000000","timestamp":"2015-10-13T14:12:24","witness":"1.6.8","transaction_merkle_root":"00000000
00000000000000000000000000000000","extensions":[],"witness_signature":"1f53542bb60f1f7a653bac70d6b1613e73b9adc952031e30e591e601dd60d493ba5c9a832e155ff0c40ea1dd53
512e9f93bf65a8191497ea67d701bc2502f93af7","transactions":[]}}
disconnected
spawn wscat -c ws://Y.Y.Y.Y:8090
connected (press CTRL+C to quit)
> {"id":1, "method":"call", "params":[0,"get_block",[1]]}
< {"id":1,"result":{"previous":"0000000000000000000000000000000000000000","timestamp":"2015-10-13T14:12:24","witness":"1.6.8","transaction_merkle_root":"00000000
00000000000000000000000000000000","extensions":[],"witness_signature":"1f53542bb60f1f7a653bac70d6b1613e73b9adc952031e30e591e601dd60d493ba5c9a832e155ff0c40ea1dd53
512e9f93bf65a8191497ea67d701bc2502f93af7","transactions":[]}}
> 

@abitmore abitmore added the cli label Nov 27, 2017
@abitmore abitmore added this to the Future Non-Consensus-Changing Release milestone Nov 27, 2017
@oxarbitrage
Copy link
Member

i am closing this issue as there are workarounds for this with tools outside the project to handle the situation and there is no further complains about the subject, issue is also very old.

@abitmore abitmore removed this from the Future Non-Consensus-Changing Release milestone Feb 25, 2018
@alkorsan
Copy link

alkorsan commented Jun 27, 2018

just an advice.
I had the same problem with automating websocket communication, then when I readed this

The most used tool is wscat, this is a great tool but it is not scriptable. I found myself pasting the same commands like login and subscribe to database, crypto and other apis over and over again.

then I told to my self you need this fantastic websocket command line client called websocat

here is an article that teached me how to script it without expect or any external utility (using only bash). it works super : https://stackoverflow.com/questions/48912184/wscat-commands-from-script-how-to-pass

here i past the script maybe the article get deleted :

wscat is a poorly-chosen tool for the job; it isn't written to follow conventions that make it suitable for scripted use (such as keeping prompts on stderr rather than stdout; or suppressing prompts when output is not to a TTY; or treating an EOF as a signal to close a connection). Consider websocat instead:

#!/usr/bin/env bash

runscript() {
  commands=( "first command" "second command" "third command" )

  for command in "${commands[@]}"; do
    echo "Writing command to server" >&2
    echo "$command"
    echo "Reading response from server (assuming exactly one line)" >&2
    read -r line
    echo "Received response: $line" >&2
  done

  # kill websocat, even if the websocket doesn't get closed
  kill "$PPID"
}

export -f runscript
websocat ws://echo.websocket.org sh-c:'exec bash -c runscript'

and websocat have a simple mode and an advanced mode both can be used in scripts.
for exemple to send only one command and close I use this simple mode:

$ echo test | websocat ws://echo.websocket.org
test  

sorry for my english :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants