|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# Copyright (c) 2018-2019 tecnovert
|
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import decimal
|
|
|
|
import subprocess
|
|
|
|
import json
|
|
|
|
import traceback
|
|
|
|
import hashlib
|
|
|
|
import urllib
|
|
|
|
from xmlrpc.client import (
|
|
|
|
Transport,
|
|
|
|
Fault,
|
|
|
|
)
|
|
|
|
from .segwit_addr import bech32_decode, convertbits, bech32_encode
|
|
|
|
|
|
|
|
COIN = 100000000
|
|
|
|
DCOIN = decimal.Decimal(COIN)
|
|
|
|
|
|
|
|
|
|
|
|
def makeInt(v):
|
|
|
|
return int(dquantize(decimal.Decimal(v) * DCOIN).quantize(decimal.Decimal(1)))
|
|
|
|
|
|
|
|
|
|
|
|
def format8(i):
|
|
|
|
n = abs(i)
|
|
|
|
quotient = n // COIN
|
|
|
|
remainder = n % COIN
|
|
|
|
rv = "%d.%08d" % (quotient, remainder)
|
|
|
|
if i < 0:
|
|
|
|
rv = '-' + rv
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
|
|
|
def toBool(s):
|
|
|
|
return s.lower() in ["1", "true"]
|
|
|
|
|
|
|
|
|
|
|
|
def dquantize(n, places=8):
|
|
|
|
return n.quantize(decimal.Decimal(10) ** -places)
|
|
|
|
|
|
|
|
|
|
|
|
def jsonDecimal(obj):
|
|
|
|
if isinstance(obj, decimal.Decimal):
|
|
|
|
return str(obj)
|
|
|
|
raise TypeError
|
|
|
|
|
|
|
|
|
|
|
|
def dumpj(jin, indent=4):
|
|
|
|
return json.dumps(jin, indent=indent, default=jsonDecimal)
|
|
|
|
|
|
|
|
|
|
|
|
def dumpje(jin):
|
|
|
|
return json.dumps(jin, default=jsonDecimal).replace('"', '\\"')
|
|
|
|
|
|
|
|
|
|
|
|
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
|
|
|
|
|
|
|
|
|
|
def b58decode(v, length=None):
|
|
|
|
long_value = 0
|
|
|
|
for (i, c) in enumerate(v[::-1]):
|
|
|
|
ofs = __b58chars.find(c)
|
|
|
|
if ofs < 0:
|
|
|
|
return None
|
|
|
|
long_value += ofs * (58**i)
|
|
|
|
result = bytes()
|
|
|
|
while long_value >= 256:
|
|
|
|
div, mod = divmod(long_value, 256)
|
|
|
|
result = bytes((mod,)) + result
|
|
|
|
long_value = div
|
|
|
|
result = bytes((long_value,)) + result
|
|
|
|
nPad = 0
|
|
|
|
for c in v:
|
|
|
|
if c == __b58chars[0]:
|
|
|
|
nPad += 1
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
pad = bytes((0,)) * nPad
|
|
|
|
result = pad + result
|
|
|
|
if length is not None and len(result) != length:
|
|
|
|
return None
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def b58encode(v):
|
|
|
|
long_value = 0
|
|
|
|
for (i, c) in enumerate(v[::-1]):
|
|
|
|
long_value += (256**i) * c
|
|
|
|
|
|
|
|
result = ''
|
|
|
|
while long_value >= 58:
|
|
|
|
div, mod = divmod(long_value, 58)
|
|
|
|
result = __b58chars[mod] + result
|
|
|
|
long_value = div
|
|
|
|
result = __b58chars[long_value] + result
|
|
|
|
|
|
|
|
# leading 0-bytes in the input become leading-1s
|
|
|
|
nPad = 0
|
|
|
|
for c in v:
|
|
|
|
if c == 0:
|
|
|
|
nPad += 1
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
return (__b58chars[0] * nPad) + result
|
|
|
|
|
|
|
|
|
|
|
|
def decodeWif(network_key):
|
|
|
|
key = b58decode(network_key)[1:-4]
|
|
|
|
if len(key) == 33:
|
|
|
|
return key[:-1]
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
|
|
def toWIF(prefix_byte, b, compressed=True):
|
|
|
|
b = bytes((prefix_byte, )) + b
|
|
|
|
if compressed:
|
|
|
|
b += bytes((0x01, ))
|
|
|
|
b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
|
|
|
|
return b58encode(b)
|
|
|
|
|
|
|
|
|
|
|
|
def bech32Decode(hrp, addr):
|
|
|
|
hrpgot, data = bech32_decode(addr)
|
|
|
|
if hrpgot != hrp:
|
|
|
|
return None
|
|
|
|
decoded = convertbits(data, 5, 8, False)
|
|
|
|
if decoded is None or len(decoded) < 2 or len(decoded) > 40:
|
|
|
|
return None
|
|
|
|
return bytes(decoded)
|
|
|
|
|
|
|
|
|
|
|
|
def bech32Encode(hrp, data):
|
|
|
|
ret = bech32_encode(hrp, convertbits(data, 8, 5))
|
|
|
|
if bech32Decode(hrp, ret) is None:
|
|
|
|
return None
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def decodeAddress(address_str):
|
|
|
|
b58_addr = b58decode(address_str)
|
|
|
|
if b58_addr is not None:
|
|
|
|
address = b58_addr[:-4]
|
|
|
|
checksum = b58_addr[-4:]
|
|
|
|
assert(hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] == checksum), 'Checksum mismatch'
|
|
|
|
return b58_addr[:-4]
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def encodeAddress(address):
|
|
|
|
checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest()
|
|
|
|
return b58encode(address + checksum[0:4])
|
|
|
|
|
|
|
|
|
|
|
|
def getKeyID(bytes):
|
|
|
|
data = hashlib.sha256(bytes).digest()
|
|
|
|
return hashlib.new("ripemd160", data).digest()
|
|
|
|
|
|
|
|
|
|
|
|
def pubkeyToAddress(prefix, pubkey):
|
|
|
|
return encodeAddress(bytes((prefix,)) + getKeyID(pubkey))
|
|
|
|
|
|
|
|
|
|
|
|
def SerialiseNum(n):
|
|
|
|
if n == 0:
|
|
|
|
return bytes([0x00])
|
|
|
|
if n > 0 and n <= 16:
|
|
|
|
return bytes([0x50 + n])
|
|
|
|
rv = bytearray()
|
|
|
|
neg = n < 0
|
|
|
|
absvalue = -n if neg else n
|
|
|
|
while(absvalue):
|
|
|
|
rv.append(absvalue & 0xff)
|
|
|
|
absvalue >>= 8
|
|
|
|
if rv[-1] & 0x80:
|
|
|
|
rv.append(0x80 if neg else 0)
|
|
|
|
elif neg:
|
|
|
|
rv[-1] |= 0x80
|
|
|
|
return bytes([len(rv)]) + rv
|
|
|
|
|
|
|
|
|
|
|
|
def DeserialiseNum(b, o=0):
|
|
|
|
if b[o] == 0:
|
|
|
|
return 0
|
|
|
|
if b[o] > 0x50 and b[o] <= 0x50 + 16:
|
|
|
|
return b[o] - 0x50
|
|
|
|
v = 0
|
|
|
|
nb = b[o]
|
|
|
|
o += 1
|
|
|
|
for i in range(0, nb):
|
|
|
|
v |= b[o + i] << (8 * i)
|
|
|
|
# If the input vector's most significant byte is 0x80, remove it from the result's msb and return a negative.
|
|
|
|
if b[o + nb - 1] & 0x80:
|
|
|
|
return -(v & ~(0x80 << (8 * (nb - 1))))
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
class Jsonrpc():
|
|
|
|
# __getattr__ complicates extending ServerProxy
|
|
|
|
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
|
|
|
allow_none=False, use_datetime=False, use_builtin_types=False,
|
|
|
|
*, context=None):
|
|
|
|
# establish a "logical" server connection
|
|
|
|
|
|
|
|
# get the url
|
|
|
|
type, uri = urllib.parse.splittype(uri)
|
|
|
|
if type not in ("http", "https"):
|
|
|
|
raise OSError("unsupported XML-RPC protocol")
|
|
|
|
self.__host, self.__handler = urllib.parse.splithost(uri)
|
|
|
|
if not self.__handler:
|
|
|
|
self.__handler = "/RPC2"
|
|
|
|
|
|
|
|
if transport is None:
|
|
|
|
handler = Transport
|
|
|
|
extra_kwargs = {}
|
|
|
|
transport = handler(use_datetime=use_datetime,
|
|
|
|
use_builtin_types=use_builtin_types,
|
|
|
|
**extra_kwargs)
|
|
|
|
self.__transport = transport
|
|
|
|
|
|
|
|
self.__encoding = encoding or 'utf-8'
|
|
|
|
self.__verbose = verbose
|
|
|
|
self.__allow_none = allow_none
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
if self.__transport is not None:
|
|
|
|
self.__transport.close()
|
|
|
|
|
|
|
|
def json_request(self, method, params):
|
|
|
|
try:
|
|
|
|
connection = self.__transport.make_connection(self.__host)
|
|
|
|
headers = self.__transport._extra_headers[:]
|
|
|
|
|
|
|
|
request_body = {
|
|
|
|
'method': method,
|
|
|
|
'params': params,
|
|
|
|
'id': 2
|
|
|
|
}
|
|
|
|
|
|
|
|
connection.putrequest("POST", self.__handler)
|
|
|
|
headers.append(("Content-Type", "application/json"))
|
|
|
|
headers.append(("User-Agent", 'jsonrpc'))
|
|
|
|
self.__transport.send_headers(connection, headers)
|
|
|
|
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8'))
|
|
|
|
|
|
|
|
resp = connection.getresponse()
|
|
|
|
return resp.read()
|
|
|
|
|
|
|
|
except Fault:
|
|
|
|
raise
|
|
|
|
except Exception:
|
|
|
|
# All unexpected errors leave connection in
|
|
|
|
# a strange state, so we clear it.
|
|
|
|
self.__transport.close()
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
def callrpc(rpc_port, auth, method, params=[], wallet=None):
|
|
|
|
|
|
|
|
try:
|
|
|
|
url = 'http://%s@127.0.0.1:%d/' % (auth, rpc_port)
|
|
|
|
if wallet:
|
|
|
|
url += 'wallet/' + wallet
|
|
|
|
x = Jsonrpc(url)
|
|
|
|
|
|
|
|
v = x.json_request(method, params)
|
|
|
|
x.close()
|
|
|
|
r = json.loads(v.decode('utf-8'))
|
|
|
|
except Exception as ex:
|
|
|
|
traceback.print_exc()
|
|
|
|
raise ValueError('RPC Server Error')
|
|
|
|
|
|
|
|
if 'error' in r and r['error'] is not None:
|
|
|
|
raise ValueError('RPC error ' + str(r['error']))
|
|
|
|
|
|
|
|
return r['result']
|
|
|
|
|
|
|
|
|
|
|
|
def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli'):
|
|
|
|
cli_bin = os.path.join(bindir, cli_bin)
|
|
|
|
|
|
|
|
args = cli_bin + ('' if chain == 'mainnet' else ' -' + chain) + ' -datadir=' + datadir + ' ' + cmd
|
|
|
|
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
|
|
out = p.communicate()
|
|
|
|
|
|
|
|
if len(out[1]) > 0:
|
|
|
|
raise ValueError('RPC error ' + str(out[1]))
|
|
|
|
|
|
|
|
r = out[0].decode('utf-8').strip()
|
|
|
|
try:
|
|
|
|
r = json.loads(r)
|
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
return r
|