Projects : gbw-node : gbw-node_genesis

command/gbw-node

Dir - Raw

1#!/usr/bin/python2
2# J. Welsh, December 2019
3
4from os import getenv, open as os_open, O_RDONLY, O_WRONLY, mkdir, mkfifo, read, write, close, stat
5from stat import S_ISDIR, S_ISFIFO
6from sys import argv, stdin, stdout, stderr, exit
7from socket import socket
8from threading import Thread, Event
9from binascii import a2b_hex, b2a_hex
10from base64 import b64encode
11from struct import Struct
12from hashlib import sha256 as _sha256
13from decimal import Decimal
14from inspect import getdoc
15from getpass import getpass
16import errno
17import signal
18import string
19import json
20import sqlite3
21from sqlite3 import IntegrityError
22
23# Safety level: scanning stops this many blocks behind tip
24CONFIRMS = 6
25
26# There's no provision for handling forks/reorgs. In the event of one deeper than CONFIRMS, a heavy workaround would be:
27# $ sqlite3 ~/.gbw/db
28# sqlite> DELETE FROM output;
29# sqlite> DELETE FROM input;
30# sqlite> DELETE FROM tx;
31# sqlite> .exit
32# $ gbw-node reset
33# $ gbw-node scan
34
35gbw_home = getenv('HOME') + '/.gbw'
36bitcoin_conf_path = getenv('HOME') + '/.bitcoin/bitcoin.conf'
37
38# Further knobs in main() for database tuning.
39db = None
40
41b2lx = lambda b: b2a_hex(b[::-1])
42lx2b = lambda x: a2b_hex(x)[::-1]
43
44COIN = 10**8
45
46def format_coin(v):
47 neg = False
48 if v < 0:
49 v = -v
50 neg = True
51 s = '%d.%08d' % divmod(v, COIN)
52 if neg:
53 return '-' + s
54 return s
55
56u16 = Struct('<H')
57u32 = Struct('<I')
58u64 = Struct('<Q')
59s64 = Struct('<q')
60unpack_u16 = u16.unpack
61unpack_u32 = u32.unpack
62unpack_u64 = u64.unpack
63unpack_s64 = s64.unpack
64unpack_header = Struct('<i32s32sIII').unpack
65unpack_outpoint = Struct('<32sI').unpack
66
67def sha256(v):
68 return _sha256(v).digest()
69
70def sha256d(v):
71 return _sha256(_sha256(v).digest()).digest()
72
73class Conflict(ValueError):
74 pass
75
76def read_all(fd):
77 parts = []
78 while True:
79 part = read(fd, 65536)
80 if len(part) == 0:
81 break
82 parts.append(part)
83 return ''.join(parts)
84
85def require_dir(path):
86 try:
87 mkdir(path)
88 except OSError, e:
89 if e.errno != errno.EEXIST:
90 raise
91 if not S_ISDIR(stat(path).st_mode):
92 die('not a directory: %r' % path)
93
94def require_fifo(path):
95 try:
96 mkfifo(path)
97 except OSError, e:
98 if e.errno != errno.EEXIST:
99 raise
100 if not S_ISFIFO(stat(path).st_mode):
101 die('not a fifo: %r' % path)
102
103##################################################
104# RPC client
105
106class JSONRPCError(Exception):
107 "Error returned in JSON-RPC response"
108
109 def __init__(self, error):
110 super(JSONRPCError, self).__init__(error['code'], error['message'])
111
112 def __str__(self):
113 return 'code: {}, message: {}'.format(*self.args)
114
115bitcoin_conf = None
116def require_conf():
117 global bitcoin_conf
118 if bitcoin_conf is None:
119 bitcoin_conf = {}
120 with open(bitcoin_conf_path) as f:
121 for line in f:
122 line = line.split('#', 1)[0].rstrip()
123 if not line:
124 continue
125 k, v = line.split('=', 1)
126 bitcoin_conf[k.strip()] = v.lstrip()
127
128def rpc(method, *args):
129 require_conf()
130 host = bitcoin_conf.get('rpcconnect', '127.0.0.1')
131 port = int(bitcoin_conf.get('rpcport', 8332))
132 auth = 'Basic ' + b64encode('%s:%s' % (
133 bitcoin_conf.get('rpcuser', ''),
134 bitcoin_conf.get('rpcpassword', '')))
135 payload = json.dumps({'method': method, 'params': args})
136 headers = [
137 ('Host', host),
138 ('Content-Type', 'application/json'),
139 ('Content-Length', len(payload)),
140 ('Connection', 'close'),
141 ('Authorization', auth),
142 ]
143 msg = 'POST / HTTP/1.1\r\n%s\r\n\r\n%s' % ('\r\n'.join('%s: %s' % kv for kv in headers), payload)
144 sock = socket()
145 sock.connect((host, port))
146 sock.sendall(msg)
147 response = read_all(sock.fileno())
148 sock.close()
149 headers, payload = response.split('\r\n\r\n', 1)
150 r = json.loads(payload, parse_float=Decimal)
151 if r['error'] is not None:
152 raise JSONRPCError(r['error'])
153 return r['result']
154
155#################################################
156# Bitcoin data parsing
157
158# "load" functions take a memoryview and return the object and number of bytes consumed.
159
160def load_compactsize(v):
161 # serialize.h WriteCompactSize
162 size = ord(v[0])
163 if size < 253:
164 return size, 1
165 elif size == 253:
166 return unpack_u16(v[1:3])[0], 3
167 elif size == 254:
168 return unpack_u32(v[1:5])[0], 5
169 else:
170 return unpack_u64(v[1:9])[0], 9
171
172def load_string(v):
173 # serialize.h Serialize, std::basic_string and CScript overloads
174 n, i = load_compactsize(v)
175 return v[i:i+n].tobytes(), i+n
176
177def vector_loader(load_element):
178 # serialize.h Serialize_impl
179 def load_vector(v):
180 n, i = load_compactsize(v)
181 r = [None]*n
182 for elem in xrange(n):
183 r[elem], delta = load_element(v[i:])
184 i += delta
185 return r, i
186 return load_vector
187
188def load_txin(v):
189 # main.h CTxIn
190 i = 36
191 txid, pos = unpack_outpoint(v[:i])
192 scriptsig, delta = load_string(v[i:])
193 i += delta
194 i += 4 # skipping sequence
195 return (txid, pos, scriptsig), i
196
197load_txins = vector_loader(load_txin)
198
199def load_txout(v):
200 # main.h CTxOut
201 i = 8
202 value, = unpack_s64(v[:i])
203 scriptpubkey, delta = load_string(v[i:])
204 return (value, scriptpubkey), i+delta
205
206load_txouts = vector_loader(load_txout)
207
208def load_transaction(v):
209 # main.h CTransaction
210 i = 4 # skipping version
211 txins, delta = load_txins(v[i:])
212 i += delta
213 txouts, delta = load_txouts(v[i:])
214 i += delta
215 i += 4 # skipping locktime
216 hash = sha256d(v[:i])
217 return (hash, i, txins, txouts), i
218
219load_transactions = vector_loader(load_transaction)
220
221def load_block(v):
222 # main.h CBlock
223 i = 80
224 head = v[:i]
225 version, prev, root, time, target, nonce = unpack_header(head)
226 hash = sha256d(head)
227 txs, delta = load_transactions(v[i:])
228 return (hash, prev, time, target, txs), i+delta
229
230def out_script_address(s):
231 # Standard P2PKH script: OP_DUP OP_HASH160 20 ... OP_EQUALVERIFY OP_CHECKSIG
232 if len(s) == 25 and s[:3] == '\x76\xA9\x14' and s[23:] == '\x88\xAC':
233 return s[3:23]
234 return None
235
236getblock_thread = None
237getblock_done = Event()
238getblock_result = None
239def getblock_reader(pipe):
240 global getblock_result
241 while True:
242 fd = os_open(pipe, O_RDONLY)
243 getblock_result = read_all(fd)
244 getblock_done.set()
245 close(fd)
246
247def getblock(height):
248 global getblock_thread
249 pipe = gbw_home + '/blockpipe'
250 if getblock_thread is None:
251 require_fifo(pipe)
252 getblock_thread = Thread(target=getblock_reader, args=(pipe,))
253 getblock_thread.daemon = True
254 getblock_thread.start()
255 if not rpc('dumpblock', height, pipe):
256 raise ValueError('dumpblock returned false')
257 getblock_done.wait()
258 getblock_done.clear()
259 return getblock_result
260
261##################################################
262# Base58
263
264base58_alphabet = (string.digits + string.uppercase + string.lowercase).translate(None, '0OIl')
265base58_inverse = [None]*256
266def init_base58_inverse():
267 for index, character in enumerate(base58_alphabet):
268 base58_inverse[ord(character)] = index
269init_base58_inverse()
270
271def bytes_to_int(b):
272 "Convert big-endian byte sequence to unsigned integer"
273 i = 0
274 for byte in b:
275 i = (i << 8) + ord(byte)
276 return i
277
278def b2a_base58check(data):
279 data += sha256d(data)[:4]
280
281 leading_zeros = 0
282 for b in data:
283 if b != '\x00':
284 break
285 leading_zeros += 1
286
287 data_num = bytes_to_int(data)
288
289 digits = []
290 while data_num:
291 data_num, digit = divmod(data_num, 58)
292 digits.append(digit)
293 digits.extend([0] * leading_zeros)
294
295 return ''.join(base58_alphabet[digit] for digit in reversed(digits))
296
297class Base58Error(ValueError):
298 pass
299
300class BadDigit(Base58Error):
301 pass
302
303class BadChecksum(Base58Error):
304 pass
305
306def a2b_base58(data):
307 digits = [base58_inverse[ord(b)] for b in data]
308 if None in digits:
309 raise BadDigit
310
311 leading_zeros = 0
312 for digit in digits:
313 if digit != 0:
314 break
315 leading_zeros += 1
316
317 data_num = 0
318 for digit in digits:
319 data_num = 58*data_num + digit
320
321 data_bytes = []
322 while data_num:
323 data_bytes.append(data_num & 0xFF)
324 data_num = data_num >> 8
325 data_bytes.extend([0] * leading_zeros)
326
327 return ''.join(chr(b) for b in reversed(data_bytes))
328
329def a2b_base58check(data):
330 data = a2b_base58(data)
331 payload = data[:-4]
332 check = data[-4:]
333 if check != sha256d(payload)[:4]:
334 raise BadChecksum
335 return payload
336
337class BadAddressLength(ValueError):
338 pass
339
340class BadAddressVersion(ValueError):
341 pass
342
343def parse_address(a):
344 b = a2b_base58check(a)
345 if len(b) != 21:
346 raise BadAddressLength
347 if b[0] != '\x00':
348 raise BadAddressVersion(ord(b[0]))
349 return b[1:]
350
351def format_address(b):
352 return b2a_base58check('\x00' + b)
353
354##################################################
355# Common database operations
356
357def get_address_id(a):
358 r = db.execute('SELECT address_id FROM address WHERE address=?', (buffer(a),)).fetchone()
359 return None if r is None else r[0]
360
361def insert_or_get_address_id(a):
362 try:
363 return db.execute('INSERT INTO address (address) VALUES (?)', (buffer(a),)).lastrowid
364 except IntegrityError:
365 return get_address_id(a)
366
367def get_tx_id(hash):
368 r = db.execute('SELECT tx_id FROM tx WHERE hash=?', (buffer(hash),)).fetchone()
369 return None if r is None else r[0]
370
371def insert_or_get_tx_id(hash, blkhash, height, n, size):
372 try:
373 return db.execute('INSERT INTO tx (hash, block_hash, block_height, n, size) VALUES (?,?,?,?,?)',
374 (buffer(hash), buffer(blkhash), height, n, size)).lastrowid
375 except IntegrityError:
376 r = db.execute('SELECT tx_id, block_hash, block_height, n, size FROM tx WHERE hash=?',
377 (buffer(hash),)).fetchone()
378 if r is None:
379 raise Conflict('differing transactions with same (height, index)', (height, n), hash)
380 if r[1:] != (buffer(blkhash), height, n, size):
381 raise Conflict('differing transactions with same hash',
382 hash, (blkhash, height, n, size), r[1:])
383 return r[0]
384
385def insert_output(tx_id, n, addr_id, val):
386 try:
387 db.execute('INSERT INTO output (tx_id, n, address_id, value) VALUES (?,?,?,?)',
388 (tx_id, n, addr_id, val))
389 except IntegrityError:
390 r = db.execute('SELECT address_id, value FROM output WHERE tx_id=? AND n=?',
391 (tx_id, n)).fetchone()
392 if r != (addr_id, val):
393 raise Conflict('output differs from previous content', tx_id, n, (addr_id, val), r)
394
395def insert_input(tx_id, n, prevout_id):
396 try:
397 input_id = db.execute('INSERT INTO input (tx_id, n) VALUES (?,?)', (tx_id, n)).lastrowid
398 except IntegrityError:
399 input_id = db.execute('SELECT input_id FROM input WHERE tx_id=? AND n=?',
400 (tx_id, n)).fetchone()[0]
401 db.execute('UPDATE output SET spent=? WHERE output_id=?', (input_id, prevout_id))
402
403def get_output_id(tx_id, n):
404 r = db.execute('SELECT output_id FROM output WHERE tx_id=? AND n=?', (tx_id, n)).fetchone()
405 return None if r is None else r[0]
406
407def get_tag_id(name):
408 r = db.execute('SELECT tag_id FROM tag WHERE name=?', (name,)).fetchone()
409 return None if r is None else r[0]
410
411def insert_or_get_tag_id(name):
412 try:
413 return db.execute('INSERT INTO tag (name) VALUES (?)', (name,)).lastrowid
414 except IntegrityError:
415 return get_tag_id(name)
416
417##################################################
418# Command implementations
419
420def scan_block(height, v):
421 stdout.write('block %s' % height)
422 (blkhash, prev, time, target, txs), size = load_block(v)
423
424 count_out = 0
425 n_tx = 0
426 for (hash, size, txins, txouts) in txs:
427 matched_outs = []
428 for n, txout in enumerate(txouts):
429 val, script = txout
430 a = out_script_address(script)
431 if a is not None:
432 #print format_address(a)
433 addr_id = get_address_id(a)
434 if addr_id is not None:
435 matched_outs.append((n, addr_id, val))
436 if len(matched_outs) > 0:
437 tx_id = insert_or_get_tx_id(hash, blkhash, height, n_tx, size)
438 for n, addr_id, val in matched_outs:
439 insert_output(tx_id, n, addr_id, val)
440 count_out += len(matched_outs)
441 n_tx += 1
442 stdout.write(' new-outs %s' % count_out)
443
444 # Inputs scanned second in case an output from the same block is spent.
445 # Coinbase (input of first tx in block) doesn't reference anything.
446 count_in = 0
447 n_tx = 1
448 for (hash, size, txins, txouts) in txs[1:]:
449 matched_ins = []
450 for n, txin in enumerate(txins):
451 prevout_hash, prevout_n, scriptsig = txin
452 prevout_tx_id = get_tx_id(prevout_hash)
453 if prevout_tx_id is not None:
454 prevout_id = get_output_id(prevout_tx_id, prevout_n)
455 if prevout_id is not None:
456 matched_ins.append((n, prevout_id))
457 if len(matched_ins) > 0:
458 tx_id = insert_or_get_tx_id(hash, blkhash, height, n_tx, size)
459 for n, prevout_id in matched_ins:
460 insert_input(tx_id, n, prevout_id)
461 count_in += len(matched_ins)
462 n_tx += 1
463 stdout.write(' spent-outs %s\n' % count_in)
464
465def die(msg, help=False):
466 stderr.write('gbw-node: %s\n' % msg)
467 if help:
468 cmd_help([])
469 exit(-1)
470
471def require_tag(name):
472 i = get_tag_id(name)
473 if i is None:
474 die('tag not found: %r' % name)
475 return i
476
477def cmd_scan(argv):
478 '''
479 scan
480
481 Iterate blocks from bitcoind, indexing transaction inputs and outputs affecting watched addresses. May be safely interrupted and resumed.
482
483 NOT PRESENTLY SAFE TO RUN CONCURRENT INSTANCES due to the dumpblock to named pipe kludge.
484 '''
485 db.execute('PRAGMA synchronous=NORMAL')
486 height = db.execute('SELECT scan_height FROM state').fetchone()[0]
487 # RPC calls can have high latency, so refresh the goal height upon reaching the previous rather than for each block scanned.
488 last_blockcount = -1
489 while True:
490 blockcount = max(-1, rpc('getblockcount') - CONFIRMS)
491 if blockcount == last_blockcount:
492 break
493 last_blockcount = blockcount
494 while height < blockcount:
495 height += 1
496 scan_block(height, memoryview(getblock(height)))
497 db.execute('UPDATE state SET scan_height = ?', (height,))
498 db.commit()
499
500def cmd_reset(argv):
501 '''
502 reset
503
504 Reset the scan pointer so the next scan will proceed from the genesis block, to find transactions associated with newly watched addresses.
505 '''
506 db.execute('UPDATE state SET scan_height = -1')
507 db.commit()
508
509def cmd_tags(argv):
510 '''
511 tags [ADDRESS]
512
513 List tags for the given ADDRESS (or all tags).
514 '''
515 if len(argv) > 0:
516 addr_id = get_address_id(parse_address(argv.pop(0)))
517 if addr_id is None:
518 die('address not found: %r' % name)
519 r = db.execute('SELECT name FROM tag \
520 JOIN address_tag ON tag.tag_id=address_tag.tag_id \
521 WHERE address_id=? ORDER BY name', (addr_id,))
522 else:
523 r = db.execute('SELECT name FROM tag ORDER BY name')
524 for name, in r:
525 stdout.write(name + '\n')
526
527def cmd_addresses(argv):
528 '''
529 addresses [TAG]
530
531 List addresses with the given TAG (or all watched addresses).
532 '''
533 if len(argv) > 0:
534 tag_id = require_tag(argv.pop(0))
535 r = db.execute('SELECT address FROM address \
536 JOIN address_tag ON address.address_id=address_tag.address_id \
537 WHERE tag_id=? ORDER BY address', (tag_id,))
538 else:
539 r = db.execute('SELECT address FROM address ORDER BY address')
540 for a, in r:
541 stdout.write(format_address(str(a)) + '\n')
542
543def cmd_unspent_outs(argv):
544 '''
545 unspent-outs [TAG]
546
547 Display the unspent outputs table for addresses with the given TAG (or all watched addresses), as required by the offline wallet, ordered by age.
548 '''
549 if len(argv) > 0:
550 tag_id = require_tag(argv.pop(0))
551 r = db.execute('SELECT address, value, hash, output.n, block_height, tx.n FROM output \
552 JOIN address ON output.address_id = address.address_id \
553 JOIN tx ON output.tx_id = tx.tx_id \
554 JOIN address_tag ON output.address_id = address_tag.address_id \
555 WHERE spent IS NULL AND tag_id=? \
556 ORDER BY block_height DESC', (tag_id,))
557 else:
558 r = db.execute('SELECT address, value, hash, output.n, block_height, tx.n FROM output \
559 JOIN address ON output.address_id = address.address_id \
560 JOIN tx ON output.tx_id = tx.tx_id \
561 WHERE spent IS NULL \
562 ORDER BY block_height DESC')
563 for a, v, hash, n_out, height, n_tx in r:
564 stdout.write('%s %s %s %s #blk %s tx %s\n' % (format_address(str(a)), format_coin(v), b2lx(hash), n_out, height, n_tx))
565
566def cmd_balance(argv):
567 '''
568 balance [TAG]
569
570 Display confirmed balance of addresses with the given TAG (or all watched addresses).
571 '''
572 if len(argv) > 0:
573 tag_id = require_tag(argv.pop(0))
574 r = db.execute('SELECT COALESCE(SUM(value),0) FROM output \
575 JOIN address_tag ON output.address_id = address_tag.address_id \
576 WHERE spent IS NULL AND tag_id=?', (tag_id,))
577 else:
578 r = db.execute('SELECT COALESCE(SUM(value),0) FROM output WHERE spent IS NULL')
579 bal, = r.fetchone()
580 stdout.write('%s\n' % format_coin(bal))
581
582def cmd_register(argv):
583 '''
584 register [TAG]
585
586 Display a tab-delimited transaction register report for addresses with the given TAG (or all watched addresses). Columns are:
587
588 - confirmation block height
589 - number of transaction within block
590 - total deposits (new outputs)
591 - total withdrawals (spent outputs)
592 - running balance
593 '''
594 if len(argv) > 0:
595 tag_id = require_tag(argv.pop(0))
596 outs = db.execute('SELECT block_height, tx.n, COALESCE(SUM(value),0) FROM tx \
597 JOIN output ON output.tx_id = tx.tx_id \
598 JOIN address_tag ON output.address_id = address_tag.address_id \
599 WHERE tag_id=? \
600 GROUP BY tx.tx_id \
601 ORDER BY block_height, tx.n', (tag_id,))
602 ins = db.execute('SELECT block_height, tx.n, COALESCE(SUM(value),0) FROM tx \
603 JOIN input ON input.tx_id = tx.tx_id \
604 JOIN output ON input.input_id = output.spent \
605 JOIN address_tag ON output.address_id = address_tag.address_id \
606 WHERE tag_id=? \
607 GROUP BY tx.tx_id \
608 ORDER BY block_height, tx.n', (tag_id,))
609 else:
610 outs = db.execute('SELECT block_height, tx.n, COALESCE(SUM(value),0) FROM tx \
611 JOIN output ON output.tx_id = tx.tx_id \
612 GROUP BY tx.tx_id \
613 ORDER BY block_height, tx.n')
614 ins = db.execute('SELECT block_height, tx.n, COALESCE(SUM(value),0) FROM tx \
615 JOIN input ON input.tx_id = tx.tx_id \
616 JOIN output ON input.input_id = output.spent \
617 GROUP BY tx.tx_id \
618 ORDER BY block_height, tx.n')
619 bal = 0
620 for height, n, o_val, i_val in merge_moves(outs.fetchall(), ins.fetchall()):
621 bal = bal + o_val - i_val
622 stdout.write('%s\t%s\t%s\t%s\t%s\n' % (height, n, format_coin(o_val), format_coin(-i_val), format_coin(bal)))
623
624# Merge ordered lists of total input and output values per transaction into single table with columns for both.
625def merge_moves(outs, ins):
626 i = o = 0
627
628 while True:
629 if o == len(outs):
630 for height, n, val in ins[i:]:
631 yield (height, n, 0, val)
632 return
633 o_height, o_n, o_val = outs[o]
634 o_key = (o_height, o_n)
635
636 if i == len(ins):
637 for height, n, val in outs[o:]:
638 yield (height, n, val, 0)
639 return
640 i_height, i_n, i_val = ins[i]
641 i_key = (i_height, i_n)
642
643 if o_key < i_key:
644 yield (o_height, o_n, o_val, 0)
645 o += 1
646 elif i_key < o_key:
647 yield (i_height, i_n, 0, i_val)
648 i += 1
649 else:
650 yield (o_height, o_n, o_val, i_val)
651 i += 1
652 o += 1
653
654def cmd_watch(argv):
655 '''
656 watch [TAG]
657
658 Import a set of addresses to watch linewise from stdin, optionally named by the given TAG. Addresses can be associated with multiple tags using multiple watch commands.
659 '''
660 tag_id = None
661 if len(argv) > 0:
662 name = argv.pop(0)
663 if '\n' in name:
664 die('newline not allowed in tag name')
665 tag_id = insert_or_get_tag_id(name)
666 while True:
667 l = stdin.readline()
668 if len(l) == 0:
669 break
670 addr_id = insert_or_get_address_id(parse_address(l.rstrip('\n')))
671 if tag_id is not None:
672 try:
673 db.execute('INSERT INTO address_tag (address_id, tag_id) VALUES (?,?)',
674 (addr_id, tag_id))
675 except IntegrityError:
676 pass
677 db.commit()
678
679def cmd_push(argv):
680 '''
681 push
682
683 Import raw hex transactions linewise from stdin and send to bitcoind.
684 '''
685 while True:
686 line = stdin.readline()
687 if len(line) == 0:
688 break
689 tx_hex = line.rstrip('\n')
690 stdout.write('txid %s\n' % rpc('sendrawtransaction', tx_hex))
691
692def cmd_unlock_wallet(argv):
693 '''
694 unlock-wallet [TIMEOUT]
695
696 Read encryption passphrase from the terminal and unlock the bitcoind internal wallet for TIMEOUT seconds (default 60).
697 '''
698 timeout = int(argv.pop(0)) if len(argv) > 0 else 60
699 r = rpc('walletpassphrase', getpass('Passphrase: '), timeout)
700 if r is not None:
701 stdout.write('%r\n' % r)
702 else:
703 stdout.write('Cached for %s seconds\n' % timeout)
704
705def cmd_help(argv):
706 '''
707 help [COMMAND]
708
709 Display help for a given command or list all commands.
710 '''
711 if len(argv) > 0:
712 name = argv.pop(0)
713 name, func = get_command(name)
714 doc = getdoc(func)
715 if doc is None:
716 stdout.write('No help for %r\n' % name)
717 else:
718 stdout.write('gbw-node %s\n' % doc)
719 else:
720 stdout.write('''Usage: gbw-node COMMAND [ARGS]
721
722Available commands (can be abbreviated when unambiguous):
723
724%s
725''' % '\n'.join([name for name, f in cmds]))
726
727cmds = (
728 ('help', cmd_help),
729 ('scan', cmd_scan),
730 ('reset', cmd_reset),
731 ('tags', cmd_tags),
732 ('addresses', cmd_addresses),
733 ('unspent-outs', cmd_unspent_outs),
734 ('watch', cmd_watch),
735 ('push', cmd_push),
736 ('balance', cmd_balance),
737 ('register', cmd_register),
738 ('unlock-wallet', cmd_unlock_wallet),
739)
740
741def get_command(name):
742 rows = [r for r in cmds if r[0].startswith(name)]
743 if len(rows) == 0:
744 die('command not found: %s' % name)
745 if len(rows) > 1:
746 die('ambiguous command %s. Completions: %s' % (name, ' '.join([r[0] for r in rows])))
747 return rows[0]
748
749def main():
750 global db
751 signal.signal(signal.SIGINT, signal.SIG_DFL)
752 require_dir(gbw_home)
753 db = sqlite3.connect(gbw_home + '/db', timeout=600) # in seconds
754 db.execute('PRAGMA foreign_keys=ON')
755 db.execute('PRAGMA cache_size=-8000') # negative means in KiB
756 db.execute('PRAGMA wal_autocheckpoint=10000') # in pages (4k)
757 if len(argv) < 2:
758 die('missing command', help=True)
759 get_command(argv[1])[1](argv[2:])
760
761if __name__ == '__main__':
762 main()