-
Notifications
You must be signed in to change notification settings - Fork 9
/
coinswap_run.py
253 lines (242 loc) · 11.3 KB
/
coinswap_run.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#!/usr/bin/env python
from __future__ import print_function
import jmbitcoin as btc
from jmclient import (SegwitWallet, WalletError, estimate_tx_fee,
validate_address, sync_wallet, BitcoinCoreInterface,
RegtestBitcoinCoreInterface)
from jmbase.support import get_password
from coinswap import (cs_single, CoinSwapPublicParameters, CoinSwapAlice,
CoinSwapCarol, CoinSwapJSONRPCClient,
get_current_blockheight, get_log, load_coinswap_config,
get_coinswap_parser, CoinSwapCarolJSONServer, start_tor)
from twisted.internet import reactor
try:
from twisted.internet import ssl
except ImportError:
pass
from twisted.python import log
from twisted.web import server
import time
import os
import sys
import json
cslog = get_log()
def parse_server_string(server_string):
scheme, server, port = server_string.split(":")
print("got this scheme, server, port: ", scheme, server, port)
if scheme == "https":
usessl = True
elif scheme == "http":
usessl = False
else:
print("Invalid server string: ", server_string)
sys.exit(0)
if not server[:2] == "//":
print("Invalid server string: ", server_string)
sys.exit(0)
return server, port, usessl
def print_status(status):
"""Used for checkonly option
"""
print(json.dumps(status, indent=4))
reactor.stop()
def get_ssl_context():
"""Construct an SSL context factory from the user's privatekey/cert.
TODO: document set up for server operators.
"""
pkcdata = {}
for x, y in zip(["ssl_private_key_location", "ssl_certificate_location"],
["key.pem", "cert.pem"]):
if cs_single().config.get("SERVER", x) == "0":
sslpath = os.path.join(cs_single().homedir, "ssl")
if not os.path.exists(sslpath):
print("No ssl configuration in home directory, please read "
"installation instructions and try again.")
sys.exit(0)
pkcdata[x] = os.path.join(sslpath, y)
else:
pkcdata[x] = cs_single().config.get("SERVER", x)
return ssl.DefaultOpenSSLContextFactory(pkcdata["ssl_private_key_location"],
pkcdata["ssl_certificate_location"])
def main_server(options, wallet, test_data=None):
"""The use_ssl option is only for tests, and flags that case.
"""
if test_data and not test_data['use_ssl']:
cs_single().config.set("SERVER", "use_ssl", "false")
cs_single().bc_interface.start_unspent_monitoring(wallet)
#to allow testing of confirm/unconfirm callback for multiple txs
if isinstance(cs_single().bc_interface, RegtestBitcoinCoreInterface):
cs_single().bc_interface.tick_forward_chain_interval = 2
cs_single().bc_interface.simulating = True
cs_single().config.set("BLOCKCHAIN", "notify_port", "62652")
cs_single().config.set("BLOCKCHAIN", "rpc_host", "127.0.0.2")
#if restart option selected, read state and backout
#(TODO is to attempt restarting normally before backing out)
if options.recover:
session_id = options.recover
carol = CoinSwapCarol(wallet, 'carolstate')
carol.bbmb = wallet.get_balance_by_mixdepth(verbose=False)
carol.load(sessionid=session_id)
carol.backout("Recovering from shutdown")
reactor.run()
return
#TODO currently ignores server setting here and uses localhost
port = cs_single().config.getint("SERVER", "port")
testing_mode = True if test_data else False
carol_class = test_data['alt_c_class'] if test_data and \
test_data['alt_c_class'] else CoinSwapCarol
fcs = test_data["fail_carol_state"] if test_data else None
#Hidden service has first priority
if cs_single().config.get("SERVER", "use_onion") != "false":
s = server.Site(CoinSwapCarolJSONServer(wallet,
testing_mode=testing_mode,
carol_class=carol_class,
fail_carol_state=fcs))
hiddenservice_dir = os.path.join(cs_single().homedir, "hiddenservice")
if not os.path.exists(hiddenservice_dir):
os.makedirs(hiddenservice_dir)
if 'hs_dir' in cs_single().config.options('SERVER'):
hiddenservice_dir = cs_single().config.get("SERVER", "hs_dir")
d = start_tor(s, cs_single().config.getint("SERVER", "onion_port"),
hiddenservice_dir)
#Any callbacks after Tor is inited can be added here with d.addCallback
elif cs_single().config.get("SERVER", "use_ssl") != "false":
reactor.listenSSL(int(port), server.Site(CoinSwapCarolJSONServer(wallet,
testing_mode=testing_mode, carol_class=carol_class,
fail_carol_state=fcs)), contextFactory = get_ssl_context())
else:
cslog.info("WARNING! Serving over HTTP, no TLS used!")
reactor.listenTCP(int(port), server.Site(CoinSwapCarolJSONServer(wallet,
testing_mode=testing_mode,
carol_class=carol_class,
fail_carol_state=fcs)))
if not test_data:
reactor.run()
def main_cs(test_data=None):
#twisted logging (TODO disable for non-debug runs)
if test_data:
wallet_name, args, options, use_ssl, alt_class, alt_c_class, fail_alice_state, fail_carol_state = test_data
server, port, usessl = parse_server_string(options.serverport)
else:
parser = get_coinswap_parser()
(options, args) = parser.parse_args()
#Will only be used by client
server, port, usessl = parse_server_string(options.serverport)
if options.checkonly:
#no need for any more data; just query
alice_client = CoinSwapJSONRPCClient(server[2:], port, usessl=usessl)
reactor.callWhenRunning(alice_client.send_poll_unsigned,
"status", print_status)
reactor.run()
return
log.startLogging(sys.stdout)
load_coinswap_config()
wallet_name = args[0]
#depth 0: spend in, depth 1: receive out, depth 2: for backout transactions.
max_mix_depth = 3
wallet_dir = os.path.join(cs_single().homedir, 'wallets')
if not os.path.exists(os.path.join(wallet_dir, wallet_name)):
wallet = SegwitWallet(wallet_name, None, max_mix_depth, 6,
wallet_dir=wallet_dir)
else:
while True:
try:
pwd = get_password("Enter wallet decryption passphrase: ")
wallet = SegwitWallet(wallet_name, pwd, max_mix_depth, 6,
wallet_dir=wallet_dir)
except WalletError:
print("Wrong password, try again.")
continue
except Exception as e:
print("Failed to load wallet, error message: " + repr(e))
sys.exit(0)
break
#for testing main script (not test framework), need funds.
if not test_data and isinstance(
cs_single().bc_interface, RegtestBitcoinCoreInterface):
for i in range(3):
cs_single().bc_interface.grab_coins(wallet.get_new_addr(0, 0, True), 2.0)
wallet.index[0][0] -= 3
time.sleep(3)
sync_wallet(wallet, fast=options.fastsync)
if test_data:
cs_single().bc_interface.wallet_synced = True
wallet.used_coins = None
if options.serve:
#sanity check that client params were not provided:
if len(args) > 1:
print("Extra parameters provided for running as server. "
"Are you sure you didn't want to run as client?")
sys.exit(0)
if not test_data:
main_server(options, wallet)
else:
main_server(options, wallet, {'use_ssl': use_ssl,
'alt_c_class': alt_c_class,
'fail_carol_state': fail_carol_state})
return wallet.get_balance_by_mixdepth()
return
if not options.recover:
target_amount = int(args[1])
#Reset the targetting for backout transactions
#TODO must be removed/changed for updated fees handling
oldtarget = cs_single().config.get("POLICY", "tx_fees")
newtarget = cs_single().config.getint("POLICY", "backout_fee_target")
multiplier = float(cs_single().config.get("POLICY", "backout_fee_multiplier"))
cs_single().config.set("POLICY", "tx_fees", str(newtarget))
tx23fee = estimate_tx_fee((1, 2, 2), 1, txtype='p2shMofN')
tx23fee = int(multiplier * tx23fee)
tx24_recipient_amount = target_amount - tx23fee
tx35_recipient_amount = target_amount - tx23fee
cs_single().config.set("POLICY", "tx_fees", oldtarget)
#to allow testing of confirm/unconfirm callback for multiple txs
if isinstance(cs_single().bc_interface, RegtestBitcoinCoreInterface):
cs_single().bc_interface.tick_forward_chain_interval = 2
cs_single().bc_interface.simulating = True
cs_single().config.set("BLOCKCHAIN", "notify_port", "62652")
cs_single().config.set("BLOCKCHAIN", "rpc_host", "127.0.0.2")
#if restart option selected, read state and backout
if options.recover:
session_id = options.recover
alice = CoinSwapAlice(wallet, 'alicestate')
alice.bbmb = wallet.get_balance_by_mixdepth(verbose=False)
alice.load(sessionid=session_id)
alice.backout("Recovering from shutdown")
reactor.run()
return
if len(args) > 2:
tx5address = args[2]
if not validate_address(tx5address):
print("Invalid address: ", tx5address)
sys.exit(0)
else:
#Our destination address should be in a separate mixdepth
tx5address = wallet.get_new_addr(1, 1, True)
#instantiate the parameters, but don't yet have the ephemeral pubkeys
#or destination addresses.
#TODO figure out best estimate incl. priority
btcfee_est = estimate_tx_fee((1, 2, 2), 1, txtype='p2shMofN')
cpp = CoinSwapPublicParameters(base_amount=target_amount, bitcoin_fee=btcfee_est)
cpp.set_addr_data(addr5=tx5address)
testing_mode = True if test_data else False
aliceclass = alt_class if test_data and alt_class else CoinSwapAlice
if test_data and fail_alice_state:
alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode,
fail_state=fail_alice_state)
else:
if testing_mode or options.checkfee:
alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode)
else:
alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode,
fee_checker="cli")
alice_client = CoinSwapJSONRPCClient(server[2:], port,
alice.sm.tick, alice.backout, usessl)
alice.set_jsonrpc_client(alice_client)
reactor.callWhenRunning(alice_client.send_poll_unsigned, "status",
alice.check_server_status)
if not test_data:
reactor.run()
if test_data:
return alice
if __name__ == "__main__":
main_cs()