Merge branch '2.0'

This commit is contained in:
ThomasV 2014-02-27 10:41:22 +01:00
commit 7267579fe0
14 changed files with 730 additions and 311 deletions

125
electrum
View File

@ -89,6 +89,7 @@ def arg_parser():
parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
parser.add_option("--bip32", action="store_true", dest="bip32", default=False, help="bip32")
return parser
@ -105,15 +106,34 @@ def print_help_cb(self, opt, value, parser):
def run_command(cmd, password=None, args=[]):
import xmlrpclib, socket
cmd_runner = Commands(wallet, network)
func = getattr(cmd_runner, cmd)
func = getattr(cmd_runner, cmd.name)
cmd_runner.password = password
if cmd.requires_network and not options.offline:
cmd_runner.network = xmlrpclib.ServerProxy('http://localhost:8000')
if wallet:
wallet.start_threads(cmd_runner.network)
wallet.update()
else:
cmd_runner.network = None
try:
result = func(*args[1:])
except socket.error:
print "Daemon not running"
sys.exit(1)
except Exception:
traceback.print_exc(file=sys.stdout)
sys.exit(1)
if cmd.requires_network and not options.offline:
if wallet:
wallet.stop_threads()
if type(result) == str:
util.print_msg(result)
elif result is not None:
@ -191,15 +211,6 @@ if __name__ == '__main__':
# instanciate wallet for command-line
storage = WalletStorage(config)
if cmd.requires_wallet:
wallet = Wallet(storage)
else:
wallet = None
if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
print_msg("Error: Wallet file not found.")
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
sys.exit(0)
if cmd.name in ['create', 'restore']:
if storage.file_exists:
@ -215,35 +226,26 @@ if __name__ == '__main__':
if not config.get('server'):
config.set_key('server', pick_random_server())
fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
if fee:
wallet.set_fee(float(fee)*100000000)
if gap:
wallet.change_gap_limit(int(gap))
#fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
#gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
#if fee:
# wallet.set_fee(float(fee)*100000000)
#if gap:
# wallet.change_gap_limit(int(gap))
if cmd.name == 'restore':
import getpass
seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
try:
seed.decode('hex')
except Exception:
print_error("Warning: Not hex, trying decode.")
seed = mnemonic_decode(seed.split(' '))
if not seed:
sys.exit("Error: No seed")
wallet.init_seed(str(seed))
wallet = Wallet.from_seed(str(seed),storage)
if not wallet:
sys.exit("Error: Invalid seed")
wallet.save_seed(password)
if not options.offline:
network = Network(config)
network.start()
wallet.start_threads(network)
print_msg("Recovering wallet...")
wallet.restore(lambda x: x)
if wallet.is_found():
print_msg("Recovery successful")
else:
@ -253,6 +255,7 @@ if __name__ == '__main__':
print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
else:
wallet = Wallet(storage)
wallet.init_seed(None)
wallet.save_seed(password)
wallet.synchronize()
@ -264,6 +267,19 @@ if __name__ == '__main__':
# terminate
sys.exit(0)
if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
print_msg("Error: Wallet file not found.")
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
sys.exit(0)
if cmd.requires_wallet:
wallet = Wallet(storage)
else:
wallet = None
# important warning
if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
print_msg("WARNING: ALL your private keys are secret.")
@ -345,19 +361,37 @@ if __name__ == '__main__':
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
args = args[0:cmd.min_args] + [message]
# open session
if cmd.requires_network and not options.offline:
network = Network(config)
if not network.start(wait=True):
print_msg("Not connected, aborting.")
sys.exit(1)
print_error("Connected to " + network.interface.connection_msg)
if wallet:
wallet.start_threads(network)
wallet.update()
else:
network = None
if cmd.name == 'daemon' and args[1] == 'start':
pid = os.fork()
if (pid == 0): # The first child.
os.chdir("/")
os.setsid()
os.umask(0)
pid2 = os.fork()
if (pid2 == 0): # Second child
from SimpleXMLRPCServer import SimpleXMLRPCServer
# start the daemon
network = Network(config)
if not network.start(wait=True):
print_msg("Not connected, aborting.")
sys.exit(1)
print_msg("Connected to " + network.interface.connection_msg)
server = SimpleXMLRPCServer(('localhost',8000), allow_none=True, logRequests=False)
server.register_function(network.synchronous_get, 'synchronous_get')
server.register_function(network.get_servers, 'get_servers')
server.register_function(network.main_server, 'main_server')
server.register_function(network.send, 'send')
server.register_function(network.subscribe, 'subscribe')
server.register_function(network.is_connected, 'is_connected')
server.register_function(network.is_up_to_date, 'is_up_to_date')
server.register_function(lambda: setattr(server,'running', False), 'stop')
server.running = True
while server.running:
server.handle_request()
print_msg("Daemon stopped")
sys.exit(0)
# run the command
if cmd.name == 'deseed':
@ -394,11 +428,8 @@ if __name__ == '__main__':
wallet.update_password(password, new_password)
else:
run_command(cmd.name, password, args)
run_command(cmd, password, args)
if network:
if wallet:
wallet.stop_threads()
network.stop()
time.sleep(0.1)
sys.exit(0)
time.sleep(0.1)
sys.exit(0)

View File

@ -3,7 +3,7 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
from electrum.i18n import _
from electrum import Wallet, mnemonic
from electrum import Wallet
from seed_dialog import SeedDialog
from network_dialog import NetworkDialog
@ -259,13 +259,13 @@ class InstallWizard(QDialog):
if not action:
return
wallet = Wallet(self.storage)
gap = self.config.get('gap_limit', 5)
if gap != 5:
wallet.gap_limit = gap
wallet.storage.put('gap_limit', gap, True)
#gap = self.config.get('gap_limit', 5)
#if gap != 5:
# wallet.gap_limit = gap
# wallet.storage.put('gap_limit', gap, True)
if action == 'create':
wallet = Wallet(self.storage)
wallet.init_seed(None)
if not self.show_seed(wallet):
return
@ -277,23 +277,14 @@ class InstallWizard(QDialog):
wallet.synchronize() # generate first addresses offline
self.waiting_dialog(create)
elif action == 'restore':
seed = self.seed_dialog()
if not seed:
return
try:
wallet.init_seed(seed)
except Exception:
import traceback
traceback.print_exc(file=sys.stdout)
QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
return
wallet = Wallet.from_seed(seed, self.storage)
ok, old_password, password = self.password_dialog(wallet)
wallet.save_seed(password)
elif action == 'watching':
mpk = self.mpk_dialog()
if not mpk:

View File

@ -426,6 +426,9 @@ class ElectrumWindow(QMainWindow):
raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
raw_transaction_text.triggered.connect(self.do_process_from_text)
raw_transaction_text = raw_transaction_menu.addAction(_("&From the blockchain"))
raw_transaction_text.triggered.connect(self.do_process_from_txid)
help_menu = menubar.addMenu(_("&Help"))
show_about = help_menu.addAction(_("&About"))
@ -1887,6 +1890,18 @@ class ElectrumWindow(QMainWindow):
if tx:
self.show_transaction(tx)
def do_process_from_txid(self):
from electrum import transaction
txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
if ok and txid:
r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
if r:
tx = transaction.Transaction(r)
if tx:
self.show_transaction(tx)
else:
self.show_message("unknown transaction")
def do_process_from_csvReader(self, csvReader):
outputs = []
try:

View File

@ -1,7 +1,7 @@
from version import ELECTRUM_VERSION
from util import format_satoshis, print_msg, print_json, print_error, set_verbosity
from wallet import WalletSynchronizer, WalletStorage
from wallet_factory import WalletFactory as Wallet
from wallet import Wallet
from verifier import TxVerifier
from network import Network, DEFAULT_SERVERS, DEFAULT_PORTS, pick_random_server
from interface import Interface

View File

@ -53,14 +53,25 @@ def op_push(i):
def sha256(x):
return hashlib.sha256(x).digest()
def Hash(x):
if type(x) is unicode: x=x.encode('utf-8')
return hashlib.sha256(hashlib.sha256(x).digest()).digest()
return sha256(sha256(x))
hash_encode = lambda x: x[::-1].encode('hex')
hash_decode = lambda x: x.decode('hex')[::-1]
hmac_sha_512 = lambda x,y: hmac.new(x, y, hashlib.sha512).digest()
mnemonic_hash = lambda x: hmac_sha_512("Bitcoin mnemonic", x).encode('hex')
def mnemonic_to_seed(mnemonic, passphrase):
from pbkdf2 import PBKDF2
import hmac
PBKDF2_ROUNDS = 2048
return PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
from version import SEED_PREFIX
is_seed = lambda x: hmac_sha_512("Seed version", x).encode('hex')[0:2].startswith(SEED_PREFIX)
# pywallet openssl private key implementation
@ -115,11 +126,11 @@ def i2o_ECPublicKey(pubkey, compressed=False):
def hash_160(public_key):
try:
md = hashlib.new('ripemd160')
md.update(hashlib.sha256(public_key).digest())
md.update(sha256(public_key))
return md.digest()
except Exception:
import ripemd
md = ripemd.new(hashlib.sha256(public_key).digest())
md = ripemd.new(sha256(public_key))
return md.digest()
@ -137,15 +148,6 @@ def bc_address_to_hash_160(addr):
bytes = b58decode(addr, 25)
return ord(bytes[0]), bytes[1:21]
def encode_point(pubkey, compressed=False):
order = generator_secp256k1.order()
p = pubkey.pubkey.point
x_str = ecdsa.util.number_to_string(p.x(), order)
y_str = ecdsa.util.number_to_string(p.y(), order)
if compressed:
return chr(2 + (p.y() & 1)) + x_str
else:
return chr(4) + pubkey.to_string() #x_str + y_str
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
__b58base = len(__b58chars)
@ -233,8 +235,7 @@ def regenerate_key(sec):
if not b:
return False
b = b[0:32]
secret = int('0x' + b.encode('hex'), 16)
return EC_KEY(secret)
return EC_KEY(b)
def GetPubKey(pubkey, compressed=False):
return i2o_ECPublicKey(pubkey, compressed)
@ -282,13 +283,14 @@ try:
except Exception:
print "cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa"
exit()
from ecdsa.curves import SECP256k1
from ecdsa.ellipticcurve import Point
from ecdsa.util import string_to_number, number_to_string
def msg_magic(message):
varint = var_int(len(message))
encoded_varint = "".join([chr(int(varint[i:i+2], 16)) for i in xrange(0, len(varint), 2)])
return "\x18Bitcoin Signed Message:\n" + encoded_varint + message
@ -301,9 +303,66 @@ def verify_message(address, signature, message):
return False
def chunks(l, n):
return [l[i:i+n] for i in xrange(0, len(l), n)]
def ECC_YfromX(x,curved=curve_secp256k1, odd=True):
_p = curved.p()
_a = curved.a()
_b = curved.b()
for offset in range(128):
Mx = x + offset
My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p
My = pow(My2, (_p+1)/4, _p )
if curved.contains_point(Mx,My):
if odd == bool(My&1):
return [My,offset]
return [_p-My,offset]
raise Exception('ECC_YfromX: No Y found')
def private_header(msg,v):
assert v<1, "Can't write version %d private header"%v
r = ''
if v==0:
r += ('%08x'%len(msg)).decode('hex')
r += sha256(msg)[:2]
return ('%02x'%v).decode('hex') + ('%04x'%len(r)).decode('hex') + r
def public_header(pubkey,v):
assert v<1, "Can't write version %d public header"%v
r = ''
if v==0:
r = sha256(pubkey)[:2]
return '\x6a\x6a' + ('%02x'%v).decode('hex') + ('%04x'%len(r)).decode('hex') + r
def negative_point(P):
return Point( P.curve(), P.x(), -P.y(), P.order() )
def point_to_ser(P, comp=True ):
if comp:
return ( ('%02x'%(2+(P.y()&1)))+('%064x'%P.x()) ).decode('hex')
return ( '04'+('%064x'%P.x())+('%064x'%P.y()) ).decode('hex')
def ser_to_point(Aser):
curve = curve_secp256k1
generator = generator_secp256k1
_r = generator.order()
assert Aser[0] in ['\x02','\x03','\x04']
if Aser[0] == '\x04':
return Point( curve, str_to_long(Aser[1:33]), str_to_long(Aser[33:]), _r )
Mx = string_to_number(Aser[1:])
return Point( curve, Mx, ECC_YfromX(Mx, curve, Aser[0]=='\x03')[0], _r )
class EC_KEY(object):
def __init__( self, secret ):
def __init__( self, k ):
secret = string_to_number(k)
self.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret )
self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )
self.secret = secret
@ -323,10 +382,11 @@ class EC_KEY(object):
else:
raise Exception("error: cannot sign message")
@classmethod
def verify_message(self, address, signature, message):
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
from ecdsa import numbertheory, ellipticcurve, util
from ecdsa import numbertheory, util
import msqr
curve = curve_secp256k1
G = generator_secp256k1
@ -352,7 +412,7 @@ class EC_KEY(object):
beta = msqr.modular_sqrt(alpha, curve.p())
y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
# 1.4 the constructor checks that nR is at infinity
R = ellipticcurve.Point(curve, x, y, order)
R = Point(curve, x, y, order)
# 1.5 compute e from message:
h = Hash( msg_magic(message) )
e = string_to_number(h)
@ -364,11 +424,94 @@ class EC_KEY(object):
# check that Q is the public key
public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
# check that we get the original signing address
addr = public_key_to_bc_address( encode_point(public_key, compressed) )
addr = public_key_to_bc_address( point_to_ser(public_key.pubkey.point, compressed) )
if address != addr:
raise Exception("Bad signature")
# ecdsa encryption/decryption methods
# credits: jackjack, https://github.com/jackjack-jj/jeeq
@classmethod
def encrypt_message(self, message, pubkey):
generator = generator_secp256k1
curved = curve_secp256k1
r = ''
msg = private_header(message,0) + message
msg = msg + ('\x00'*( 32-(len(msg)%32) ))
msgs = chunks(msg,32)
_r = generator.order()
str_to_long = string_to_number
P = generator
if len(pubkey)==33: #compressed
pk = Point( curve_secp256k1, str_to_long(pubkey[1:33]), ECC_YfromX(str_to_long(pubkey[1:33]), curve_secp256k1, pubkey[0]=='\x03')[0], _r )
else:
pk = Point( curve_secp256k1, str_to_long(pubkey[1:33]), str_to_long(pubkey[33:65]), _r )
for i in range(len(msgs)):
n = ecdsa.util.randrange( pow(2,256) )
Mx = str_to_long(msgs[i])
My, xoffset = ECC_YfromX(Mx, curved)
M = Point( curved, Mx+xoffset, My, _r )
T = P*n
U = pk*n + M
toadd = point_to_ser(T) + point_to_ser(U)
toadd = chr(ord(toadd[0])-2 + 2*xoffset) + toadd[1:]
r += toadd
return base64.b64encode(public_header(pubkey,0) + r)
def decrypt_message(self, enc):
G = generator_secp256k1
curved = curve_secp256k1
pvk = self.secret
pubkeys = [point_to_ser(G*pvk,True), point_to_ser(G*pvk,False)]
enc = base64.b64decode(enc)
str_to_long = string_to_number
assert enc[:2]=='\x6a\x6a'
phv = str_to_long(enc[2])
assert phv==0, "Can't read version %d public header"%phv
hs = str_to_long(enc[3:5])
public_header=enc[5:5+hs]
checksum_pubkey=public_header[:2]
address=filter(lambda x:sha256(x)[:2]==checksum_pubkey, pubkeys)
assert len(address)>0, 'Bad private key'
address=address[0]
enc=enc[5+hs:]
r = ''
for Tser,User in map(lambda x:[x[:33],x[33:]], chunks(enc,66)):
ots = ord(Tser[0])
xoffset = ots>>1
Tser = chr(2+(ots&1))+Tser[1:]
T = ser_to_point(Tser)
U = ser_to_point(User)
V = T*pvk
Mcalc = U + negative_point(V)
r += ('%064x'%(Mcalc.x()-xoffset)).decode('hex')
pvhv = str_to_long(r[0])
assert pvhv==0, "Can't read version %d private header"%pvhv
phs = str_to_long(r[1:3])
private_header = r[3:3+phs]
size = str_to_long(private_header[:4])
checksum = private_header[4:6]
r = r[3+phs:]
msg = r[:size]
hashmsg = sha256(msg)[:2]
checksumok = hashmsg==checksum
return [msg, checksumok, address]
###################################### BIP32 ##############################
random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
@ -408,7 +551,7 @@ def CKD(k, c, n):
import hmac
from ecdsa.util import string_to_number, number_to_string
order = generator_secp256k1.order()
keypair = EC_KEY(string_to_number(k))
keypair = EC_KEY(k)
K = GetPubKey(keypair.pubkey,True)
if n & BIP32_PRIME: # We want to make a "secret" address that can't be determined from K
@ -531,8 +674,35 @@ def test_bip32(seed, sequence):
def test_crypto():
G = generator_secp256k1
_r = G.order()
pvk = ecdsa.util.randrange( pow(2,256) ) %_r
Pub = pvk*G
pubkey_c = point_to_ser(Pub,True)
pubkey_u = point_to_ser(Pub,False)
addr_c = public_key_to_bc_address(pubkey_c)
addr_u = public_key_to_bc_address(pubkey_u)
print "Private key ", '%064x'%pvk
print "Compressed public key ", pubkey_c.encode('hex')
print "Uncompressed public key", pubkey_u.encode('hex')
message = "Chancellor on brink of second bailout for banks"
enc = EC_KEY.encrypt_message(message,pubkey_c)
eck = EC_KEY(number_to_string(pvk,_r))
dec = eck.decrypt_message(enc)
print "decrypted", dec
signature = eck.sign_message(message, True, addr_c)
print signature
EC_KEY.verify_message(addr_c, signature, message)
if __name__ == '__main__':
test_bip32("000102030405060708090a0b0c0d0e0f", "0'/1/2'/2/1000000000")
test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","0/2147483647'/1/2147483646'/2")
test_crypto()
#test_bip32("000102030405060708090a0b0c0d0e0f", "0'/1/2'/2/1000000000")
#test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","0/2147483647'/1/2147483646'/2")

View File

@ -67,9 +67,9 @@ register_command('dumpprivkeys', 0, 0, False, True, True, 'dump all pr
register_command('freeze', 1, 1, False, True, True, 'Freeze the funds at one of your wallet\'s addresses', 'freeze <address>')
register_command('getbalance', 0, 1, True, True, False, 'Return the balance of your wallet, or of one account in your wallet', 'getbalance [<account>]')
register_command('getservers', 0, 0, True, False, False, 'Return the list of available servers')
register_command('getversion', 0, 0, False, False, False, 'Return the version of your client', 'getversion')
register_command('getaddressbalance', 1, 1, True, True, False, 'Return the balance of an address', 'getaddressbalance <address>')
register_command('getaddresshistory', 1, 1, True, True, False, 'Return the transaction history of a wallet address', 'getaddresshistory <address>')
register_command('getversion', 0, 0, False, False, False, 'Return the version of your client', 'getversion')
register_command('getaddressbalance', 1, 1, True, False, False, 'Return the balance of an address', 'getaddressbalance <address>')
register_command('getaddresshistory', 1, 1, True, False, False, 'Return the transaction history of a wallet address', 'getaddresshistory <address>')
register_command('getconfig', 1, 1, False, False, False, 'Return a configuration variable', 'getconfig <name>')
register_command('getpubkeys', 1, 1, False, True, False, 'Return the public keys for a wallet address', 'getpubkeys <bitcoin address>')
register_command('getrawtransaction', 1, 1, True, False, False, 'Retrieve a transaction', 'getrawtransaction <txhash>')
@ -79,7 +79,8 @@ register_command('help', 0, 1, False, False, False, 'Prints this
register_command('history', 0, 0, True, True, False, 'Returns the transaction history of your wallet')
register_command('importprivkey', 1, 1, False, True, True, 'Import a private key', 'importprivkey <privatekey>')
register_command('listaddresses', 2, 2, False, True, False, 'Returns your list of addresses.', '', listaddr_options)
register_command('listunspent', 0, 0, True, True, False, 'Returns the list of unspent inputs in your wallet.')
register_command('listunspent', 0, 0, True, False, False, 'Returns the list of unspent inputs in your wallet.')
register_command('getaddressunspent', 1, 1, True, False, False, 'Returns the list of unspent inputs in your wallet.')
register_command('mktx', 5, 5, False, True, True, 'Create a signed transaction', 'mktx <recipient> <amount> [label]', payto_options)
register_command('mksendmanytx', 4, 4, False, True, True, 'Create a signed transaction', mksendmany_syntax, payto_options)
register_command('payto', 5, 5, True, True, True, 'Create and broadcast a transaction.', payto_syntax, payto_options)
@ -95,6 +96,12 @@ register_command('unfreeze', 1, 1, False, True, False, 'Unfreeze th
register_command('validateaddress', 1, 1, False, False, False, 'Check that the address is valid', 'validateaddress <address>')
register_command('verifymessage', 3,-1, False, False, False, 'Verifies a signature', verifymessage_syntax)
register_command('encrypt', 2,-1, False, False, False, 'encrypt a message with pubkey','encrypt <pubkey> <message>')
register_command('decrypt', 2,-1, False, False, False, 'decrypt a message with privkey','decrypt <privkey> <message>')
register_command('daemon', 1, 1, True, False, False, 'start/stop daemon')
register_command('getproof', 1, 1, True, False, False, 'get merkle proof', 'getproof <address>')
register_command('getunspentaddress', 2, 2, True, False, False, 'get the address of an unspent','getunspentaddress <txid> <pos>')
@ -106,6 +113,7 @@ class Commands:
self._callback = callback
self.password = None
def _run(self, method, args, password_getter):
cmd = known_commands[method]
if cmd.requires_password and self.wallet.use_encryption:
@ -117,11 +125,22 @@ class Commands:
apply(self._callback, ())
return result
def getaddresshistory(self, addr):
assert self.wallet.is_mine(addr)
h = self.wallet.get_history(addr)
if h is None: h = self.network.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0]
return h
return self.network.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0]
def daemon(self, arg):
if arg=='stop':
return self.network.stop()
elif arg=='status':
return {
'server':self.network.main_server(),
'connected':self.network.is_connected()
}
else:
return "unknown command \"%s\""% arg
def listunspent(self):
import copy
@ -129,6 +148,15 @@ class Commands:
for i in l: i["value"] = str(Decimal(i["value"])/100000000)
return l
def getaddressunspent(self, addr):
return self.network.synchronous_get([ ('blockchain.address.listunspent',[addr]) ])[0]
def getunspentaddress(self, txid, num):
return self.network.synchronous_get([ ('blockchain.utxo.get_address',[txid, num]) ])[0]
def createrawtransaction(self, inputs, outputs):
# convert to own format
for i in inputs:
@ -199,9 +227,14 @@ class Commands:
return out
def getaddressbalance(self, addr):
c, u = self.wallet.get_addr_balance(addr)
out = { "confirmed": str(Decimal(c)/100000000) }
if u: out["unconfirmed"] = str(Decimal(u)/100000000)
b = self.network.synchronous_get([ ('blockchain.address.get_balance',[addr]) ])[0]
return str(Decimal(b)/100000000)
def getproof(self, addr):
p = self.network.synchronous_get([ ('blockchain.address.get_proof',[addr]) ])[0]
out = []
for i,s in p:
out.append(i)
return out
def getservers(self):
@ -350,11 +383,27 @@ class Commands:
if cmd.options: print_msg("options:\n" + cmd.options)
return None
def getrawtransaction(self, tx_hash):
import transaction
if self.wallet:
tx = self.wallet.transactions.get(tx_hash)
if tx:
return tx
return self.network.retrieve_transaction(tx_hash)
r = self.network.synchronous_get([ ('blockchain.transaction.get',[tx_hash]) ])[0]
if r:
return transaction.Transaction(r)
else:
return "unknown transaction"
def encrypt(self, pubkey, message):
return EC_KEY.encrypt_message(message, pubkey.decode('hex'))
def decrypt(self, secret, message):
ec = regenerate_key(secret)
decrypted = ec.decrypt_message(message)
return decrypted[0]

View File

@ -598,6 +598,22 @@ class Interface(threading.Thread):
self.queue.put(self)
def synchronous_get(self, requests, timeout=100000000):
queue = Queue.Queue()
ids = self.send(requests, lambda i,r: queue.put(r))
id2 = ids[:]
res = {}
while ids:
r = queue.get(True, timeout)
_id = r.get('id')
if _id in ids:
ids.remove(_id)
res[_id] = r.get('result')
out = []
for _id in id2:
out.append(res[_id])
return out
if __name__ == "__main__":

View File

@ -22,6 +22,37 @@ DEFAULT_SERVERS = {
}
def parse_servers(result):
""" parse servers list into dict format"""
from version import PROTOCOL_VERSION
servers = {}
for item in result:
host = item[1]
out = {}
version = None
pruning_level = '-'
if len(item) > 2:
for v in item[2]:
if re.match("[stgh]\d*", v):
protocol, port = v[0], v[1:]
if port == '': port = DEFAULT_PORTS[protocol]
out[protocol] = port
elif re.match("v(.?)+", v):
version = v[1:]
elif re.match("p\d*", v):
pruning_level = v[1:]
if pruning_level == '': pruning_level = '0'
try:
is_recent = float(version)>=float(PROTOCOL_VERSION)
except Exception:
is_recent = False
if out and is_recent:
out['pruning'] = pruning_level
servers[host] = out
return servers
def filter_protocol(servers, p):
@ -66,6 +97,8 @@ class Network(threading.Thread):
self.interface = None
self.proxy = self.config.get('proxy')
self.heights = {}
self.merkle_roots = {}
self.utxo_roots = {}
self.server_lag = 0
dir_path = os.path.join( self.config.path, 'certs')
@ -82,6 +115,14 @@ class Network(threading.Thread):
return self.interface and self.interface.is_connected
def is_up_to_date(self):
return self.interface.is_up_to_date()
def main_server(self):
return self.interface.server
def send_subscriptions(self):
for cb, sub in self.subscriptions.items():
self.interface.send(sub, cb)
@ -327,6 +368,8 @@ class Network(threading.Thread):
if not result: return
height = result.get('block_height')
self.heights[i.server] = height
self.merkle_roots[i.server] = result.get('merkle_root')
self.utxo_roots[i.server] = result.get('utxo_root')
# notify blockchain about the new height
self.blockchain.queue.put((i,result))
@ -341,7 +384,7 @@ class Network(threading.Thread):
def on_peers(self, i, r):
if not r: return
self.irc_servers = self.parse_servers(r.get('result'))
self.irc_servers = parse_servers(r.get('result'))
self.trigger_callback('peers')
def on_banner(self, i, r):
@ -356,59 +399,26 @@ class Network(threading.Thread):
def synchronous_get(self, requests, timeout=100000000):
queue = Queue.Queue()
ids = self.interface.send(requests, lambda i,r: queue.put(r))
id2 = ids[:]
res = {}
while ids:
r = queue.get(True, timeout)
_id = r.get('id')
if _id in ids:
ids.remove(_id)
res[_id] = r.get('result')
out = []
for _id in id2:
out.append(res[_id])
return out
return self.interface.synchronous_get(requests)
def retrieve_transaction(self, tx_hash, tx_height=0):
import transaction
r = self.synchronous_get([ ('blockchain.transaction.get',[tx_hash, tx_height]) ])[0]
if r:
return transaction.Transaction(r)
#def retrieve_transaction(self, tx_hash, tx_height=0):
# import transaction
# r = self.synchronous_get([ ('blockchain.transaction.get',[tx_hash, tx_height]) ])[0]
# if r:
# return transaction.Transaction(r)
def parse_servers(self, result):
""" parse servers list into dict format"""
from version import PROTOCOL_VERSION
servers = {}
for item in result:
host = item[1]
out = {}
version = None
pruning_level = '-'
if len(item) > 2:
for v in item[2]:
if re.match("[stgh]\d*", v):
protocol, port = v[0], v[1:]
if port == '': port = DEFAULT_PORTS[protocol]
out[protocol] = port
elif re.match("v(.?)+", v):
version = v[1:]
elif re.match("p\d*", v):
pruning_level = v[1:]
if pruning_level == '': pruning_level = '0'
try:
is_recent = float(version)>=float(PROTOCOL_VERSION)
except Exception:
is_recent = False
if out and is_recent:
out['pruning'] = pruning_level
servers[host] = out
return servers
class NetworkProxy:
# interface to the network object.
# handle subscriptions and callbacks
# the network object can be jsonrpc server
def __init__(self, network):
self.network = network

View File

@ -1,5 +1,5 @@
ELECTRUM_VERSION = "1.9.8" # version of the client package
PROTOCOL_VERSION = '0.6' # protocol version requested
SEED_VERSION = 4 # bump this every time the seed generation is modified
PROTOCOL_VERSION = '0.9' # protocol version requested
NEW_SEED_VERSION = 6 # bip32 wallets
OLD_SEED_VERSION = 4 # old electrum deterministic generation
SEED_PREFIX = '01' # the hash of the mnemonic seed must begin with this

View File

@ -73,6 +73,7 @@ class WalletStorage:
def __init__(self, config):
self.lock = threading.Lock()
self.config = config
self.data = {}
self.file_exists = False
self.path = self.init_path(config)
@ -151,7 +152,10 @@ class WalletStorage:
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
class Wallet:
class NewWallet:
def __init__(self, storage):
@ -160,7 +164,7 @@ class Wallet:
self.gap_limit_for_change = 3 # constant
# saved fields
self.seed_version = storage.get('seed_version', SEED_VERSION)
self.seed_version = storage.get('seed_version', NEW_SEED_VERSION)
self.gap_limit = storage.get('gap_limit', 5)
self.use_change = storage.get('use_change',True)
@ -180,12 +184,6 @@ class Wallet:
self.next_addresses = storage.get('next_addresses',{})
if self.seed_version not in [4, 6]:
msg = "This wallet seed is not supported."
if self.seed_version in [5]:
msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%self.seed_version
print msg
sys.exit(1)
# This attribute is set when wallet.start_threads is called.
self.synchronizer = None
@ -290,42 +288,27 @@ class Wallet:
# we keep only 13 words, that's approximately 139 bits of entropy
words = mnemonic.mn_encode(s)[0:13]
seed = ' '.join(words)
if mnemonic_hash(seed).startswith(SEED_PREFIX):
break # this removes 12 bits of entropy
if is_seed(seed):
break # this will remove 8 bits of entropy
nonce += 1
return seed
def init_seed(self, seed):
import mnemonic
import mnemonic, unicodedata
if self.seed:
raise Exception("a seed exists")
self.seed_version = NEW_SEED_VERSION
if not seed:
self.seed = random_seed(128)
self.seed_version = 4
self.seed = self.make_seed()
return
# check if seed is hexadecimal
seed = seed.strip()
try:
assert seed
seed.decode('hex')
self.seed_version = 4
self.seed = str(seed)
return
except Exception:
pass
self.seed = unicodedata.normalize('NFC', unicode(seed.strip()))
words = seed.split()
self.seed_version = 4
self.seed = mnemonic.mn_decode(words)
if not self.seed:
raise Exception("Invalid seed")
def save_seed(self, password):
@ -338,41 +321,26 @@ class Wallet:
self.create_accounts(password)
def create_watching_only_wallet(self, params):
K0, c0 = params
if not K0:
return
if not c0:
self.seed_version = 4
self.storage.put('seed_version', self.seed_version, True)
self.create_old_account(K0)
return
cK0 = ""
def create_watching_only_wallet(self, K0, c0):
cK0 = "" #FIXME
self.master_public_keys = {
"m/0'/": (c0, K0, cK0),
}
self.storage.put('master_public_keys', self.master_public_keys, True)
self.storage.put('seed_version', self.seed_version, True)
self.create_account('1','Main account')
self.create_account('1of1','Main account')
def create_accounts(self, password):
seed = pw_decode(self.seed, password)
if self.seed_version == 4:
mpk = OldAccount.mpk_from_seed(seed)
self.create_old_account(mpk)
else:
# create default account
self.create_master_keys('1', password)
self.create_account('1','Main account')
# create default account
self.create_master_keys('1of1', password)
self.create_account('1of1','Main account')
def create_master_keys(self, account_type, password):
master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(None))
if account_type == '1':
master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(password))
if account_type == '1of1':
k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/")
self.master_public_keys["m/0'/"] = (c0, K0, cK0)
self.master_private_keys["m/0'/"] = pw_encode(k0, password)
@ -398,7 +366,7 @@ class Wallet:
self.storage.put('master_private_keys', self.master_private_keys, True)
def has_master_public_keys(self, account_type):
if account_type == '1':
if account_type == '1of1':
return "m/0'/" in self.master_public_keys
elif account_type == '2of2':
return set(["m/1'/", "m/2'/"]) <= set(self.master_public_keys.keys())
@ -421,17 +389,18 @@ class Wallet:
def deseed_branch(self, k):
# check that parent has no seed
assert self.seed == ''
# assert self.seed == ''
self.master_private_keys.pop(k)
self.storage.put('master_private_keys', self.master_private_keys, True)
def is_watching_only(self):
return (self.seed == '') and (self.master_private_keys == {})
def account_id(self, account_type, i):
if account_type == '1':
if account_type == '1of1':
return "m/0'/%d"%i
elif account_type == '2of2':
return "m/1'/%d & m/2'/%d"%(i,i)
@ -451,7 +420,7 @@ class Wallet:
return i
def new_account_address(self, account_type = '1'):
def new_account_address(self, account_type = '1of1'):
i = self.num_accounts(account_type)
k = self.account_id(account_type,i)
@ -465,12 +434,12 @@ class Wallet:
return k, addr
def next_account(self, account_type = '1'):
def next_account(self, account_type = '1of1'):
i = self.num_accounts(account_type)
account_id = self.account_id(account_type,i)
if account_type is '1':
if account_type is '1of1':
master_c0, master_K0, _ = self.master_public_keys["m/0'/"]
c0, K0, cK0 = bip32_public_derivation(master_c0.decode('hex'), master_K0.decode('hex'), "m/0'/", "m/0'/%d"%i)
account = BIP32_Account({ 'c':c0, 'K':K0, 'cK':cK0 })
@ -514,7 +483,7 @@ class Wallet:
def create_account(self, account_type = '1', name = None):
def create_account(self, account_type = '1of1', name = None):
k, account = self.next_account(account_type)
if k in self.pending_accounts:
self.pending_accounts.pop(k)
@ -526,12 +495,6 @@ class Wallet:
self.set_label(k, name)
def create_old_account(self, mpk):
self.storage.put('master_public_key', mpk, True)
self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
self.save_accounts()
def save_accounts(self):
d = {}
for k, v in self.accounts.items():
@ -596,14 +559,13 @@ class Wallet:
return s[0] == 1
def get_master_public_key(self):
if self.seed_version == 4:
return self.storage.get("master_public_key")
else:
c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
return repr((c, K))
c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
return repr((c, K))
def get_master_private_key(self, account, password):
master_k = pw_decode( self.master_private_keys[account], password)
k = self.master_private_keys.get(account)
if not k: return
master_k = pw_decode( k, password)
master_c, master_K, master_Kc = self.master_public_keys[account]
try:
K, Kc = get_pubkeys_from_secret(master_k.decode('hex'))
@ -675,25 +637,14 @@ class Wallet:
return '&'.join(dd)
def get_seed(self, password):
s = pw_decode(self.seed, password)
if self.seed_version == 4:
seed = s
self.accounts[0].check_seed(seed)
else:
seed = mnemonic_hash(s)
seed = mnemonic_to_seed(s,'').encode('hex')
return seed
def get_mnemonic(self, password):
import mnemonic
s = pw_decode(self.seed, password)
if self.seed_version == 4:
return ' '.join(mnemonic.mn_encode(s))
else:
return s
return pw_decode(self.seed, password)
def get_private_key(self, address, password):
@ -746,23 +697,6 @@ class Wallet:
for txin in tx.inputs:
keyid = txin.get('KeyID')
if keyid:
if self.seed_version == 4:
m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
if not m: continue
mpk = m.group(1)
if mpk != self.storage.get('master_public_key'): continue
for_change = int(m.group(2))
num = int(m.group(3))
account = self.accounts[0]
addr = account.get_address(for_change, num)
txin['address'] = addr # fixme: side effect
pk = account.get_private_key(seed, (for_change, num))
pubkey = public_key_from_private_key(pk)
keypairs[pubkey] = pk
continue
roots = []
for s in keyid.split('&'):
m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
@ -921,7 +855,7 @@ class Wallet:
def create_pending_accounts(self):
for account_type in ['1','2of2','2of3']:
for account_type in ['1of1','2of2','2of3']:
if not self.has_master_public_keys(account_type):
continue
k, a = self.new_account_address(account_type)
@ -1050,29 +984,23 @@ class Wallet:
def get_account_name(self, k):
if k == 0:
if self.seed_version == 4:
name = 'Main account'
default = "Unnamed account"
m = re.match("m/0'/(\d+)", k)
if m:
num = m.group(1)
if num == '0':
default = "Main account"
else:
name = 'Old account'
else:
default = "Unnamed account"
m = re.match("m/0'/(\d+)", k)
if m:
num = m.group(1)
if num == '0':
default = "Main account"
else:
default = "Account %s"%num
default = "Account %s"%num
m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
if m:
num = m.group(1)
default = "2of2 account %s"%num
name = self.labels.get(k, default)
m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
if m:
num = m.group(1)
default = "2of2 account %s"%num
name = self.labels.get(k, default)
return name
def get_account_names(self):
accounts = {}
for k, account in self.accounts.items():
@ -1081,6 +1009,7 @@ class Wallet:
accounts[-1] = 'Imported keys'
return accounts
def get_account_addresses(self, a, include_change=True):
if a is None:
o = self.addresses(True)
@ -1537,7 +1466,7 @@ class Wallet:
def start_threads(self, network):
from verifier import TxVerifier
self.network = network
if self.network:
if self.network is not None:
self.verifier = TxVerifier(self.network, self.storage)
self.verifier.start()
self.set_verifier(self.verifier)
@ -1622,12 +1551,12 @@ class WalletSynchronizer(threading.Thread):
if not self.network.is_connected():
self.network.wait_until_connected()
self.run_interface(self.network.interface)
self.run_interface()
def run_interface(self, interface):
def run_interface(self):
print_error("synchronizer: connected to", interface.server)
print_error("synchronizer: connected to", self.network.main_server())
requested_tx = []
missing_tx = []
@ -1657,12 +1586,12 @@ class WalletSynchronizer(threading.Thread):
# request missing transactions
for tx_hash, tx_height in missing_tx:
if (tx_hash, tx_height) not in requested_tx:
interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
self.network.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
requested_tx.append( (tx_hash, tx_height) )
missing_tx = []
# detect if situation has changed
if interface.is_up_to_date() and self.queue.empty():
if self.network.is_up_to_date() and self.queue.empty():
if not self.wallet.is_up_to_date():
self.wallet.set_up_to_date(True)
self.was_updated = True
@ -1672,7 +1601,7 @@ class WalletSynchronizer(threading.Thread):
self.was_updated = True
if self.was_updated:
self.wallet.network.trigger_callback('updated')
self.network.trigger_callback('updated')
self.was_updated = False
# 2. get a response
@ -1681,8 +1610,9 @@ class WalletSynchronizer(threading.Thread):
except Queue.Empty:
continue
if interface != self.network.interface:
break
# see if it changed
#if interface != self.network.interface:
# break
if not r:
continue
@ -1700,7 +1630,7 @@ class WalletSynchronizer(threading.Thread):
addr = params[0]
if self.wallet.get_status(self.wallet.get_history(addr)) != result:
if requested_histories.get(addr) is None:
interface.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
self.network.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
requested_histories[addr] = result
elif method == 'blockchain.address.get_history':
@ -1750,7 +1680,163 @@ class WalletSynchronizer(threading.Thread):
print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
if self.was_updated and not requested_tx:
self.wallet.network.trigger_callback('updated')
self.wallet.network.trigger_callback("new_transaction") # Updated gets called too many times from other places as well; if we use that signal we get the notification three times
self.network.trigger_callback('updated')
# Updated gets called too many times from other places as well; if we use that signal we get the notification three times
self.network.trigger_callback("new_transaction")
self.was_updated = False
class OldWallet(NewWallet):
def init_seed(self, seed):
import mnemonic
if self.seed:
raise Exception("a seed exists")
if not seed:
seed = random_seed(128)
self.seed_version = OLD_SEED_VERSION
# see if seed was entered as hex
seed = seed.strip()
try:
assert seed
seed.decode('hex')
self.seed = str(seed)
return
except Exception:
pass
words = seed.split()
try:
mnemonic.mn_decode(words)
except Exception:
raise
self.seed = mnemonic.mn_decode(words)
if not self.seed:
raise Exception("Invalid seed")
def get_master_public_key(self):
return self.storage.get("master_public_key")
def create_accounts(self, password):
seed = pw_decode(self.seed, password)
mpk = OldAccount.mpk_from_seed(seed)
self.create_account(mpk)
def create_account(self, mpk):
self.storage.put('master_public_key', mpk, True)
self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
self.save_accounts()
def create_watching_only_wallet(self, K0):
self.seed_version = OLD_SEED_VERSION
self.storage.put('seed_version', self.seed_version, True)
self.create_account(K0)
def get_seed(self, password):
seed = pw_decode(self.seed, password)
self.accounts[0].check_seed(seed)
return seed
def get_mnemonic(self, password):
import mnemonic
s = pw_decode(self.seed, password)
return ' '.join(mnemonic.mn_encode(s))
def add_keypairs_from_KeyID(self, tx, keypairs, password):
# first check the provided password
seed = self.get_seed(password)
for txin in tx.inputs:
keyid = txin.get('KeyID')
if keyid:
m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
if not m: continue
mpk = m.group(1)
if mpk != self.storage.get('master_public_key'): continue
for_change = int(m.group(2))
num = int(m.group(3))
account = self.accounts[0]
addr = account.get_address(for_change, num)
txin['address'] = addr # fixme: side effect
pk = account.get_private_key(seed, (for_change, num))
pubkey = public_key_from_private_key(pk)
keypairs[pubkey] = pk
def get_account_name(self, k):
assert k == 0
return 'Main account'
# former WalletFactory
class Wallet(object):
def __new__(self, storage):
config = storage.config
if config.get('bitkey', False):
# if user requested support for Bitkey device,
# import Bitkey driver
from wallet_bitkey import WalletBitkey
return WalletBitkey(config)
if not storage.file_exists:
seed_version = NEW_SEED_VERSION if config.get('bip32') is True else OLD_SEED_VERSION
else:
seed_version = storage.get('seed_version')
if seed_version == OLD_SEED_VERSION:
return OldWallet(storage)
elif seed_version == NEW_SEED_VERSION:
return NewWallet(storage)
else:
msg = "This wallet seed is not supported."
if seed_version in [5]:
msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
print msg
sys.exit(1)
@classmethod
def from_seed(self, seed, storage):
import mnemonic
if not seed:
return
words = seed.strip().split()
try:
mnemonic.mn_decode(words)
uses_electrum_words = True
except Exception:
uses_electrum_words = False
try:
seed.decode('hex')
is_hex = True
except Exception:
is_hex = False
if is_hex or (uses_electrum_words and len(words) != 13):
print "old style wallet", len(words), words
w = OldWallet(storage)
w.init_seed(seed) #hex
else:
#assert is_seed(seed)
w = Wallet(storage)
w.init_seed(seed)
return w

View File

@ -1,11 +0,0 @@
class WalletFactory(object):
def __new__(cls, config):
if config.get('bitkey', False):
# if user requested support for Bitkey device,
# import Bitkey driver
from wallet_bitkey import WalletBitkey
return WalletBitkey(config)
# Load standard wallet
from wallet import Wallet
return Wallet(config)

View File

@ -5,7 +5,7 @@
import time, electrum
# 1. start the interface and wait for connection
interface = electrum.Interface('electrum.no-ip.org:50002:s')
interface = electrum.Interface('ecdsa.net:50002:s')
interface.start(wait = True)
if not interface.is_connected:
print "not connected"

View File

@ -1,17 +1,79 @@
#!/usr/bin/env python
import time, electrum
import time, electrum, Queue
from electrum import Interface, SimpleConfig
from electrum.network import filter_protocol, parse_servers
from collections import defaultdict
electrum.set_verbosity(False) # default is True
network = electrum.Network({'auto_cycle':True})
network.register_callback('peers',lambda: electrum.print_json(network.irc_servers.keys()))
# 1. start interface and wait for connection
interface = electrum.Interface('ecdsa.net:50002:s')
interface.start(wait = True)
if not interface.is_connected:
print "not connected"
exit()
if not network.start(wait=True):
print "Not connected"
exit(1)
# 2. get list of peers
q = Queue.Queue()
interface.send([('server.peers.subscribe',[])], lambda i,x: q.put(x))
r = q.get(timeout=10000)
peers = parse_servers(r.get('result'))
peers = filter_protocol(peers,'s')
print "Connected to", network.interface.server
while not network.irc_servers:
time.sleep(1)
# start interfaces
config = SimpleConfig()
interfaces = map ( lambda server: Interface(server, config), peers )
results_queue = Queue.Queue()
reached_servers = []
for i in interfaces: i.start(q)
while peers:
i = q.get(timeout=1)
peers.remove(i.server)
if i.is_connected:
i.send([('blockchain.headers.subscribe',[])], lambda i,x: results_queue.put((i,x)))
reached_servers.append(i.server)
def analyze(results):
out = {}
dd = {}
for k, v in results.items():
height = v.get('block_height')
merkle = v.get('merkle_root')
utxo = v.get('utxo_root')
d = dd.get(merkle, defaultdict(int))
d[utxo] += 1
dd[merkle] = d
refs = {}
for merkle, d in dd.items():
v = d.values()
m = max(v)
ref = d.keys()[v.index(m)]
refs[merkle] = ref, m
for k, v in results.items():
height = v.get('block_height')
merkle = v.get('merkle_root')
utxo = v.get('utxo_root')
ref_utxo, num = refs.get(merkle)
if ref_utxo != utxo and num > 1:
out[k] = height, merkle, utxo
return out
results = {}
while reached_servers:
i, r = results_queue.get(timeout=10000)
results[i.server] = r.get('result')
reached_servers.remove(i.server)
electrum.print_json(results)
out = analyze(results)
if out:
print "faulty servers:"
electrum.print_json(out)
else:
print "ok"

View File

@ -22,7 +22,7 @@ while servers:
i = q.get(timeout=1000)
servers.remove(i.server)
if i.is_connected:
i.send([('blockchain.numblocks.subscribe',[])], lambda i,x: results_queue.put((i,x)))
i.send([('blockchain.headers.subscribe',[])], lambda i,x: results_queue.put((i,x)))
reached_servers.append(i.server)
i.status = "ok"
else:
@ -32,7 +32,7 @@ d = defaultdict(int)
while reached_servers:
i, r = results_queue.get(timeout=1000)
i.blocks = r.get('result')
i.blocks = r.get('result').get('block_height')
d[i.blocks] += 1
reached_servers.remove(i.server)