diff --git a/milasargawi/api/__init__.py b/milasargawi/api/__init__.py new file mode 100644 index 000000000..22fe21257 --- /dev/null +++ b/milasargawi/api/__init__.py @@ -0,0 +1,3 @@ +from urllib3.exceptions import InsecureRequestWarning +import warnings +warnings.simplefilter('ignore', InsecureRequestWarning) \ No newline at end of file diff --git a/milasargawi/api/__pycache__/__init__.cpython-36.pyc b/milasargawi/api/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 000000000..5cac193ba Binary files /dev/null and b/milasargawi/api/__pycache__/__init__.cpython-36.pyc differ diff --git a/milasargawi/api/__pycache__/__init__.cpython-38.pyc b/milasargawi/api/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 000000000..77249d409 Binary files /dev/null and b/milasargawi/api/__pycache__/__init__.cpython-38.pyc differ diff --git a/milasargawi/api/__pycache__/cline.cpython-36.pyc b/milasargawi/api/__pycache__/cline.cpython-36.pyc new file mode 100644 index 000000000..8ef0907b1 Binary files /dev/null and b/milasargawi/api/__pycache__/cline.cpython-36.pyc differ diff --git a/milasargawi/api/__pycache__/cline.cpython-38.pyc b/milasargawi/api/__pycache__/cline.cpython-38.pyc new file mode 100644 index 000000000..482c0ef4d Binary files /dev/null and b/milasargawi/api/__pycache__/cline.cpython-38.pyc differ diff --git a/milasargawi/api/__pycache__/keys.cpython-38.pyc b/milasargawi/api/__pycache__/keys.cpython-38.pyc new file mode 100644 index 000000000..f2edc2b49 Binary files /dev/null and b/milasargawi/api/__pycache__/keys.cpython-38.pyc differ diff --git a/milasargawi/api/__pycache__/types.cpython-38.pyc b/milasargawi/api/__pycache__/types.cpython-38.pyc new file mode 100644 index 000000000..93c8b420c Binary files /dev/null and b/milasargawi/api/__pycache__/types.cpython-38.pyc differ diff --git a/milasargawi/api/__pycache__/utils.cpython-38.pyc b/milasargawi/api/__pycache__/utils.cpython-38.pyc new file mode 100644 index 000000000..25168b23e Binary files /dev/null and b/milasargawi/api/__pycache__/utils.cpython-38.pyc differ diff --git a/milasargawi/api/cline.py b/milasargawi/api/cline.py new file mode 100644 index 000000000..9dd1d2bab --- /dev/null +++ b/milasargawi/api/cline.py @@ -0,0 +1,380 @@ +import json, requests +from binascii import hexlify + + +from api.types import * +from api.utils import * +from api.keys import * + + +class DynamicUrl: + + def __init__(self, url, version='v1', cache=None): + self._cache = cache or [] + self._baseurl = url + self._version = version + + def __getattr__(self, name): + return self._(name) + + def __del__(self): + pass + + def _(self, name): + return DynamicUrl(url=self._baseurl, version=self._version, cache=self._cache + [name]) + + def method(self): + return self._cache + + def create_url(self): + url_str = '{0}/{1}'.format(self._baseurl, self._version) + for obj in self.method(): + url_str = '{0}/{1}'.format(url_str, obj) + return url_str + + def get_url(self, url, params=None, json=None, timeout=30): + # get request + r = requests.get(url, params=params, json=json, timeout=timeout, verify=False) + r.raise_for_status() + return r.json() + + def post_url(self, url, params=None, json=None, data=None, timeout=30): + # post request + r = requests.post(url, params=params, json=json, data=data, timeout=timeout, verify=False) + try: + r.raise_for_status() + except: + raise requests.exceptions.HTTPError('Error: {}'.format(r.json())) + return r.json() + + + +class Cline: + + def __init__(self, url, version='v1'): + ''' ''' + self._prod_url = url + self._version = version + self._dynurl = DynamicUrl(url=self._prod_url, version=self._version) + + ##### + # private functions + ##### + + def get(self, func='', **kwargs): + ''' ''' + cmd = eval('self._dynurl.{0}'.format(func)) + url = cmd.create_url() + return cmd.get_url(url, **kwargs) + + def post(self, func='', **kwargs): + ''' ''' + cmd = eval('self._dynurl.{0}'.format(func)) + url = cmd.create_url() + return cmd.post_url(url, **kwargs) + + ##### + # get methods + ##### + + def get_info(self, timeout=30): + ''' ''' + return self.get('chain.get_info', timeout=timeout) + + def get_chain_lib_info(self, timeout=30): + ''' ''' + chain_info = self.get('chain.get_info', timeout=timeout) + lib_info = self.get_block(chain_info['last_irreversible_block_num'], timeout=timeout) + return chain_info, lib_info + + def get_block(self, block_num, timeout=30): + ''' ''' + return self.post('chain.get_block', params=None, json={'block_num_or_id': block_num}, timeout=timeout) + + def get_account(self, acct_name, timeout=30): + ''' ''' + return self.post('chain.get_account', params=None, json={'account_name': acct_name}, timeout=timeout) + + def get_code(self, acct_name, code_as_wasm=True, timeout=30): + ''' ''' + return self.post('chain.get_code', params=None, json={'account_name': acct_name, 'code_as_wasm': code_as_wasm}, timeout=timeout) + + def get_accounts(self, public_key, timeout=30): + ''' ''' + return self.post('history.get_key_accounts', params=None, json={'public_key': public_key}, timeout=timeout) + + def get_abi(self, acct_name, timeout=30): + ''' ''' + return self.post('chain.get_abi', params=None, json={'account_name': acct_name}, timeout=timeout) + + def get_raw_abi(self, acct_name, timeout=30): + ''' ''' + return self.post('chain.get_raw_abi', params=None, json={'account_name': acct_name}, timeout=timeout) + + def get_actions(self, acct_name, pos=-1, offset=-20, timeout=30): + ''' + POST /v1/history/get_actions + {"account_name":"inerynewyorkio","pos":-1,"offset":-20} + ''' + json = {'account_name': acct_name, "pos": pos, "offset": offset} + return self.post('history.get_actions', params=None, json=json, timeout=timeout) + + def get_currency(self, code='inery.token', symbol='INR', timeout=30): + ''' + POST /v1/chain/get_currency_stats HTTP/1.0 + {"json":false,"code":"inery.token","symbol":"INR"} + ''' + json = {'json': False, 'code': code, 'symbol': symbol} + return self.post('chain.get_currency_stats', params=None, json=json, timeout=timeout) + + def get_currency_balance(self, account, code='inery.token', symbol='INR', timeout=30): + ''' + POST /v1/chain/get_currency_balance HTTP/1.0 + {"account":"inery","code":"inery.token","symbol":"INR"} + ''' + json = {'account': account, 'code': code, 'symbol': symbol} + return self.post('chain.get_currency_balance', params=None, json=json, timeout=timeout) + + def get_currency_stats(self, code, symbol, timeout=30): + return self.post('chain.get_currency_stats', json={'code': code, 'symbol': symbol}) + + def get_servants(self, acct_name, timeout=30): + ''' ''' + return self.post('account_history.get_controlled_accounts', params=None, json={'controlling_account': acct_name}, timeout=timeout) + + def get_transaction(self, trans_id, timeout=30): + ''' + POST /v1/history/get_transaction + {"id":"abcd1234"} + ''' + return self.post('history.get_transaction', params=None, json={'id': trans_id}, timeout=timeout) + + def get_table(self, code, scope, table, index_position='', key_type='', lower_bound='', upper_bound='', limit=10, timeout=30): + ''' + POST /v1/chain/get_table_rows + {"json":true,"code":"inery","scope":"inery","table":"masters","index_position":"","key_type":"name","lower_bound":"","upper_bound":"","limit":10} + ''' + json = {"json": True, "code": code, "scope": scope, "table": table, "key_type": key_type, "index_position": index_position, "lower_bound": lower_bound, "upper_bound": upper_bound, "limit": limit} + return self.post('chain.get_table_rows', params=None, json=json, timeout=timeout) + + def get_masters(self, lower_bound='', limit=50, timeout=30): + ''' + POST /v1/chain/get_masters HTTP/1.0 + {"json":true,"lower_bound":"","limit":50} + ''' + return self.post('chain.get_masters', params=None, json={'json': True, 'lower_bound': lower_bound, 'limit': limit}, timeout=timeout) + + ##### + # set + ##### + + def set_abi(self, account, permission, abi_file, key, broadcast=True, timeout=30): + #current_abi = Abi(self.get_abi(account)['abi']) + #current_sha = sha256(current_abi.get_raw().encode('utf-8')) + with open(abi_file) as rf: + abi = json.load(rf) + new_abi = Abi(abi) + + arguments = { + "account": account, + "abi": new_abi.get_raw() + } + payload = { + "account": "inery", + "name": "setabi", + "authorization": [{ + "actor": account, + "permission": permission, + }], + } + # Converting payload to binary + data = self.abi_json_to_bin(payload['account'], payload['name'], arguments) + # Inserting payload binary form as "data" field in original payload + payload['data'] = data['binargs'] + trx = {"actions": [payload]} + sign_key = INRKey(key) + return self.push_transaction(trx, sign_key, broadcast=broadcast) + + def set_code(self, account, permission, code_file, key, broadcast=True, timeout=30): + current_code = self.get_code(account) + current_sha = current_code['code_hash'] + with open(code_file, 'rb') as rf: + wasm = rf.read() + hex_wasm = hexlify(wasm) + new_sha = sha256(hex_wasm) + if current_sha == new_sha: + raise INRSetSameCode() + # generate trx + arguments = { + "account": account, + "vmtype": 0, + "vmversion": 0, + "code": hex_wasm.decode('utf-8') + } + payload = { + "account": "inery", + "name": "setcode", + "authorization": [{ + "actor": account, + "permission": permission, + }], + } + # Converting payload to binary + data = self.abi_json_to_bin(payload['account'], payload['name'], arguments) + # Inserting payload binary form as "data" field in original payload + payload['data'] = data['binargs'] + trx = {"actions": [payload]} + sign_key = INRKey(key) + return self.push_transaction(trx, sign_key, broadcast=broadcast) + + + + + def push_transaction(self, transaction, keys, broadcast=True, compression='none', timeout=30): + ''' ''' + + chain_info, lib_info = self.get_chain_lib_info() + trx = Transaction(transaction, chain_info, lib_info) + + # create digest for signing + digest = sig_digest(trx.encode(), chain_info['chain_id']) + + # sign the transaction + signatures = [] + + if not isinstance(keys, list): + + keys = [keys] + + for key in keys: + signatures.append(key.sign(digest)) + + # build final trx + final_trx = { + 'compression': compression, + 'transaction': trx.__dict__, + 'signatures': signatures + } + + data = json.dumps(final_trx, cls=INREncoder) + if broadcast: + return self.post('chain.push_transaction', params=None, data=data, timeout=timeout) + print(data) + return data + + def push_block(self, timeout=30): + raise NotImplementedError + + ##### + # bin/json + ##### + + def abi_bin_to_json(self, code, action, binargs, timeout=30): + ''' ''' + json = {'code': code, 'action': action, 'binargs': binargs} + return self.post('chain.abi_bin_to_json', params=None, json=json, timeout=timeout) + + def abi_json_to_bin(self, code, action, args, timeout=30): + ''' ''' + json = {'code': code, 'action': action, 'args': args} + return self.post('chain.abi_json_to_bin', params=None, json=json, timeout=timeout) + + ##### + # create keys + ##### + + def create_key(self): + ''' ''' + k = INRKey() + return k +## 5JfcRV4p4cLcHnURfM9ajzsPpfeETZeReFE9FN1qCVTFtQ1cPNc + + def create_account(self, acct_name, + creator= "", + creator_privkey="", + owner_key="", + active_key='', permission='active', + transfer=False, broadcast=True, timeout=30): + ''' ''' + + # check account doesn't exist + try: + self.get_account(acct_name) + #print('{} already exists.'.format(acct_name)) + raise ValueError('{} already exists.'.format(acct_name)) + except: + pass + if not active_key: + active_key = owner_key + # create newaccount trx + owner_auth = { + "threshold": 1, + "keys": [{ + "key": owner_key, + "weight": 1 + }], + "accounts": [], + "waits": [] + } + active_auth = { + "threshold": 1, + "keys": [{ + "key": active_key, + "weight": 1 + }], + "accounts": [], + "waits": [] + } + print({ + 'creator': creator, + 'name': acct_name, + 'owner': owner_auth, + 'active': active_auth + }) + + newaccount_data = self.abi_json_to_bin('inery', 'newaccount', {'creator': creator, 'name': acct_name, 'owner': owner_auth, 'active': active_auth}) + print(newaccount_data) + newaccount_json = { + 'account': 'inery', + 'name': 'newaccount', + 'authorization': [ + { + 'actor': creator, + 'permission': permission + }], + 'data': newaccount_data['binargs'] + } + # create buyrambytes trx + buy_mem_bytes = self.abi_json_to_bin('inery', 'buymembytes', {'payer': creator, 'receiver': acct_name, 'bytes': 1048576}) + buymem_json = { + 'account': 'inery', + 'name': 'buymembytes', + 'authorization': [ + { + 'actor': creator, + 'permission': permission + }], + 'data': buy_mem_bytes['binargs'] + } + # create delegatebw + delegate_data = self.abi_json_to_bin('inery', 'delegatebw', + {'from': creator, 'receiver': acct_name, 'stake_net_quantity': "1.0000 INR", 'stake_cpu_quantity': "1.0000 INR", 'transfer': transfer}) + delegate_json = { + 'account': 'inery', + 'name': 'delegatebw', + 'authorization': [ + { + 'actor': creator, + 'permission': permission + }], + 'data': delegate_data['binargs'] + } + + trx = {"actions": + [newaccount_json, buymem_json, delegate_json] + } + # push transaction + key = INRKey(creator_privkey) + return self.push_transaction(trx, key, broadcast=broadcast, timeout=timeout) + + diff --git a/milasargawi/api/keys.py b/milasargawi/api/keys.py new file mode 100644 index 000000000..45c02533d --- /dev/null +++ b/milasargawi/api/keys.py @@ -0,0 +1,239 @@ +import base58 +import os +import ecdsa +import re +from binascii import hexlify, unhexlify +from api.utils import sha256, ripemd160 +import hashlib +import struct +import array + +def get_curve(key_type) : + if key_type == 'R1' : + return ecdsa.NIST256p + return ecdsa.SECP256k1 + +def check_wif(key) : + if isinstance(key, str) : + try : + INRKey(key) + return True + except Exception as ex: + pass + return False + + +class INRKey(): + + def __init__(self, private_str=''): + ''' ''' + if private_str: + private_key, format, key_type = self._parse_key(private_str) + self._key_type = key_type + self._curve = get_curve(key_type) + self._sk = ecdsa.SigningKey.from_string(unhexlify(private_key), curve=self._curve) + else : + prng = self._create_entropy() + self._key_type = 'K1' + self._curve = get_curve(self._key_type) + self._sk = ecdsa.SigningKey.generate(curve=self._curve, entropy=prng) + self._vk = self._sk.get_verifying_key() + + def __str__(self): + return self.to_public() + + def _parse_key(self, private_str): + ''' Check if is valid and recognise key type, then ''' + match = re.search('^PVT_([A-Za-z0-9]+)_([A-Za-z0-9]+)$', private_str) + if not match: + + version_key = self._check_decode(private_str, 'sha256x2') + version = int(version_key[0:2], 16) + + if not version == 0x80: + + raise ValueError('Expected version 0x80, instead got {0}', version) + + private_key = version_key[2:] + key_type = 'K1' + format = 'WIF' + + else: + + key_type, key_string = match.groups() + private_key = self._check_decode(key_string, key_type) + format = 'PVT' + + return (private_key, format, key_type) + + def _create_entropy(self): + ''' ''' + ba = bytearray(os.urandom(32)) + seed = sha256(ba) + return ecdsa.util.PRNG(seed) + + def _check_encode(self, key_buffer, key_type=None): + ''' ''' + if isinstance(key_buffer, bytes): + key_buffer = key_buffer.decode() + check = key_buffer + if key_type == 'sha256x2': + first_sha = sha256(unhexlify(check)) + chksum = sha256(unhexlify(first_sha))[:8] + else: + if key_type: + check += hexlify(bytearray(key_type, 'utf-8')).decode() + chksum = ripemd160(unhexlify(check))[:8] + return base58.b58encode(unhexlify(key_buffer + chksum)) + + def _check_decode(self, key_string, key_type=None): + ''' ''' + buffer = hexlify(base58.b58decode(key_string)).decode() + chksum = buffer[-8:] + key = buffer[:-8] + + if key_type == 'sha256x2': + # legacy + first_sha = sha256(unhexlify(key)) + newChk = sha256(unhexlify(first_sha))[:8] + + else: + check = key + if key_type: + check += hexlify(bytearray(key_type, 'utf-8')).decode() + newChk = ripemd160(unhexlify(check))[:8] + + if chksum != newChk: + raise ValueError('checksums do not match: {0} != {1}'.format(chksum, newChk)) + return key + + def _recover_key(self, digest, signature, i): + ''' Recover the public key from the signature''' + + curve = self._curve.curve + G = self._curve.generator + order = self._curve.order + yp = (i %2) + r, s = ecdsa.util.sigdecode_string(signature, order) + x = r + (i // 2) * order + alpha = ((x * x * x) + (curve.a() * x) + curve.b()) % curve.p() + beta = ecdsa.numbertheory.square_root_mod_prime(alpha, curve.p()) + y = beta if (beta - yp) % 2 == 0 else curve.p() - beta + # generate R + R = ecdsa.ellipticcurve.Point(curve, x, y, order) + e = ecdsa.util.string_to_number(digest) + # compute Q + Q = ecdsa.numbertheory.inverse_mod(r, order) * (s * R + (-e % order) * G) + # verify message + if not ecdsa.VerifyingKey.from_public_point(Q, curve=self._curve).verify_digest(signature, digest, + sigdecode=ecdsa.util.sigdecode_string) : + return None + return ecdsa.VerifyingKey.from_public_point(Q, curve=self._curve) + + def _recovery_pubkey_param(self, digest, signature) : + ''' Use to derive a number that will allow for the easy recovery + of the public key from the signature + ''' + for i in range(0, 4): + p = self._recover_key(digest, signature, i) + if (p.to_string() == self._vk.to_string()): + return i + + def _compress_pubkey(self): + ''' ''' + order = self._sk.curve.generator.order() + p = self._vk.pubkey.point + x_str = ecdsa.util.number_to_string(p.x(), order) + hex_data = bytearray(chr(2 + (p.y() & 1)), 'utf-8') + compressed = hexlify(hex_data + x_str).decode() + return compressed + + def _is_canonical(self, sig): + print("sig: " + str(sig)) + t1 = (sig[1] & 0x80) == 0 + t2 = not (sig[1] == 0 and ((sig[2] & 0x80) == 0)) + t3 = (sig[33] & 0x80) == 0 + t4 = not (sig[33] == 0 and ((sig[34] & 0x80) == 0)) + return t1 and t2 and t3 and t4 + + def to_public(self): + ''' ''' + cmp = self._compress_pubkey() + return 'INR' + self._check_encode(cmp).decode() + + def to_wif(self): + ''' ''' + pri_key = '80' + hexlify(self._sk.to_string()).decode() + return self._check_encode(pri_key, 'sha256x2').decode() + + def sign_string(self, data, encoding="utf-8"): + ''' ''' + digest = sha256(bytearray(data, encoding)) + return self.sign(digest) + + def sign(self, digest): + ''' ''' + cnt = 0 + # convert digest to hex string + digest = unhexlify(digest) + if len(digest) != 32: + raise ValueError("32 byte buffer required") + while 1: + # get deterministic k + if cnt: + sha_digest = hashlib.sha256(digest + bytearray(cnt)).digest() + else: + sha_digest = hashlib.sha256(digest).digest() + k = ecdsa.rfc6979.generate_k(self._sk.curve.generator.order(), + self._sk.privkey.secret_multiplier, + hashlib.sha256, + # hashlib.sha256(digest + struct.pack('d', time.time())).digest() # use time to randomize + sha_digest + ) + # sign the message + sigder = self._sk.sign_digest(digest, sigencode=ecdsa.util.sigencode_der, k=k) + + # reformat sig + r, s = ecdsa.util.sigdecode_der(sigder, self._sk.curve.generator.order()) + sig = ecdsa.util.sigencode_string(r, s, self._sk.curve.generator.order()) + + sigder = array.array('B', sigder) + # ensure signature is canonical + lenR = sigder[3] + lenS = sigder[5 + lenR] + + if lenR == 32 and lenS == 32: + # derive recover parameter + i = self._recovery_pubkey_param(digest, sig) + # compact + i += 27 + # compressed + i += 4 + sigstr = struct.pack('>= 7 + buf |= (((val > 0) if 1 else 0) << 7) + self._push_byte(buf) + while val: + buf = int((val) & 0x7f) + val >>= 7 + buf |= (((val > 0) if 1 else 0) << 7) + self._push_byte(buf) + return self._b_arr + + def _pop(self, buf, length): + return buf[:length], buf[length:] + + def decode(self, buf): + ''' ''' + shift = 0 + result = 0 + while True: + tmp, buf = self._pop(buf, 2) + i = hex_to_int(tmp) + result |= (i & 0x7f) << shift + shift += 7 + if not(i & 0x80): + break + return result, buf + + +class BaseObject(object): + def __init__(self, d): + ''' ''' + try: + self._obj = self._validator.deserialize(d) + except Invalid: + raise INRInvalidSchema('Unable to process schema for {}'.format(type(self))) + # instantiate the class + for k, v in self._obj.items(): + setattr(self, k, v) + # clean up + del self._obj + del self._validator + + def __repr__(self): + ''' ''' + return '{}({})'.format(self.__class__, self.__dict__) + + def _encode_buffer(self, value): + ''' ''' + return INRBuffer(value).encode() + + def _create_obj_array(self, arr, class_type): + ''' ''' + new_arr = [] + for item in arr: + new_arr.append(class_type(item)) + return new_arr + + +class Action(BaseObject): + def __init__(self, d): + ''' ''' + self._validator = ActionSchema() + super(Action, self).__init__(d) + # setup permissions + self.authorization = self._create_obj_array(self.authorization, Authorization) + + def encode(self): + ''' ''' + acct = self._encode_buffer(AccountName(self.account)) + name = self._encode_buffer(Name(self.name)) + auth = self._encode_buffer(self.authorization) + # need to figure out how to process data + # get length + data_len = self._encode_buffer(VarUInt(len(self.data) / 2)) + data = data_len + self.data + return '{}{}{}{}'.format(acct, name, auth, data) + + +class Asset: + def __init__(self, value, precision=4): + # self.amount = amt + # self.symbol = sym + # self.precision = precision + self.from_string(value) + + def __str__(self): + return '{amount:.{precision}f} {symbol}'.format(amount=self.amount, symbol=self.symbol, precision=self.precision) + + def __add__(self, other): + if self.symbol != other.symbol: + raise TypeError('Symbols must match: {} != {}', self.symbol, other.symbol) + return Asset(self.amount + other.amount, self.symbol) + + def __sub__(self, other): + if self.amount - other.amount < 0: + raise ValueError('Subtraction would result in a negative.') + if self.symbol != other.symbol: + raise TypeError('Symbols must match: {} != {}', self.symbol, other.symbol) + return Asset(self.amount - other.amount, self.symbol) + + def from_string(self, s): + splt = s.split() + try: + self.amount = float(splt[0]) + self.symbol = splt[1] + self.precision = len(splt[0].split(".")[1]) + except IndexError: + raise IndexError('Invalid string format given. Must be in the formst ') + + def _string_to_symbol(self): + ''' ''' + rslt = 0 + cnt = 0 + while cnt < len(self.symbol): + letter = self.symbol[cnt] + if letter >= 'A' or letter <= 'Z': + l = ord(letter) + rslt |= (UInt64(l) << (8 * (cnt + 1))) + else: + raise ValueError("{} contains an invalid symbol. Must be [A-Z].".format(self.symbol)) + + cnt += 1 + rslt |= UInt64(self.precision) + return INRBuffer(UInt64(rslt)).encode() + + def encode(self): + ''' ''' + power = '1'.ljust(self.precision + len('1'), '0') + amount = INRBuffer(UInt64(self.amount * UInt64(power))).encode() + symbol = self._string_to_symbol() + return '{amount}{symbol}'.format(amount=amount, symbol=symbol) + + +class AbiType(BaseObject): + def __init__(self, d): + self._validator = AbiTypeSchema() + super(AbiType, self).__init__(d) + + def encode(self): + new_type_name = self._encode_buffer(self.new_type_name) + type = self._encode_buffer(self.type) + return '{}{}'.format(new_type_name, type) + + +class AbiStructField(BaseObject): + def __init__(self, d): + self._validator = AbiStructFieldSchema() + super(AbiStructField, self).__init__(d) + + def encode(self): + name = self._encode_buffer(self.name) + type = self._encode_buffer(self.type) + return '{}{}'.format(name, type) + + +class AbiStruct(BaseObject): + def __init__(self, d): + self._validator = AbiStructSchema() + super(AbiStruct, self).__init__(d) + self.fields = self._create_obj_array(self.fields, AbiStructField) + + def encode(self): + name = self._encode_buffer(self.name) + base = self._encode_buffer(self.base) + fields = self._encode_buffer(self.fields) + return '{}{}{}'.format(name, base, fields) + + +class AbiAction(BaseObject): + def __init__(self, d): + self._validator = AbiActionSchema() + super(AbiAction, self).__init__(d) + + def encode(self): + name = self._encode_buffer(Name(self.name)) + type = self._encode_buffer(self.type) + ricardian_contract = self._encode_buffer(self.ricardian_contract) + return '{}{}{}'.format(name, type, ricardian_contract) + + +class AbiTable(BaseObject): + def __init__(self, d): + self._validator = AbiTableSchema() + super(AbiTable, self).__init__(d) + + def encode(self): + name = self._encode_buffer(Name(self.name)) + index_type = self._encode_buffer(self.index_type) + key_names = self._encode_buffer(self.key_names) + key_types = self._encode_buffer(self.key_types) + type = self._encode_buffer(self.type) + return '{}{}{}{}{}'.format(name, index_type, key_names, key_types, type) + + +class AbiRicardianClauses(BaseObject): + def __init__(self, d): + self._validator = AbiRicardianClauseSchema() + super(AbiRicardianClauses, self).__init__(d) + + def encode(self): + id = self._encode_buffer(self.id) + body = self._encode_buffer(self.body) + return '{}{}'.format(id, body) + + +class AbiErrorMessages(BaseObject): + # TODO implement encode + def __init__(self, d): + self._validator = AbiErrorMessagesSchema() + super(AbiErrorMessages, self).__init__(d) + + def encode(): + raise NotImplementedError + + +class AbiExtensions(BaseObject): + # TODO implement encode + def __init__(self, d): + self._validator = AbiExtensionsSchema() + super(AbiExtensions, self).__init__(d) + + def encode(): + raise NotImplementedError + + +class AbiVariants(BaseObject): + # TODO implement encode + def __init__(self, d): + self._validator = AbiVariantsSchema() + super(AbiVariants, self).__init__(d) + + def encode(): + raise NotImplementedError + + +class Abi(BaseObject): + _abi_map = { + # name + 'name': Name(), + 'string': str(), + # numbers + 'bool': Byte(), + 'uint8': Byte(), + 'uint16': UInt16(), + 'uint32': UInt32(), + 'uint64': UInt64(), + 'int8': Byte(), # NotImplemented + 'int16': Int16(), # NotImplemented + 'int32': Int32(), # NotImplemented + 'int64': Int64(), # NotImplemented + 'float64': Float(), # NotImplemented + # 'varuint32': VarUInt # NotImplemented + # complex + 'asset': Asset("1.0000 INR"), + # 'checksum256': str, # NotImplemented + # 'block_timestamp_type': UInt64, # NotImplemented + # 'time_point': UInt64, # NotImplemented + # 'connector': str, # NotImplemented + # 'public_key': str, # NotImplemented + # 'authority': str, # NotImplemented + # 'block_header': str, # NotImplemented + # 'bytes': str, # NotImplemented + # 'permission_level': str, # NotImplemented + # 'permission_level_weight': str, #NotImplemented + } + + def __init__(self, d): + ''' ''' + self._validator = AbiSchema() + super(Abi, self).__init__(d) + self.types = self._create_obj_array(self.types, AbiType) + self.structs = self._create_obj_array(self.structs, AbiStruct) + self.actions = self._create_obj_array(self.actions, AbiAction) + self.tables = self._create_obj_array(self.tables, AbiTable) + self.ricardian_clauses = self._create_obj_array(self.ricardian_clauses, AbiRicardianClauses) + self.error_messages = self._create_obj_array(self.error_messages, AbiErrorMessages) + self.abi_extensions = self._create_obj_array(self.abi_extensions, AbiExtensions) + self.variants = self._create_obj_array(self.variants, AbiVariants) + + def get_action(self, name): + ''' ''' + for act in self.actions: + if act.name == name: + return act + raise INRUnknownObj('{} is not a valid action for this contract'.format(name)) + + def get_actions(self): + actions = [] + for act in self.actions: + actions.append(act.name) + return actions + + def get_struct(self, name): + ''' ''' + for struct in self.structs: + if struct.name == name: + return struct + raise INRUnknownObj('{} is not a valid struct for this contract'.format(name)) + + def get_action_parameters(self, name): + ''' ''' + parameters = OrderedDict() + # get the struct + struct = self.get_struct(name) + for field in struct.fields: + f = field.type.strip('[]') + if(f in self._abi_map): + field_type = self._abi_map[f] + # check if the field is a list + if '[]' in field.type: + field_type = [field_type] + parameters[field.name] = field_type + else: + raise INRUnknownObj("{} is not a known abi type".format(field.type)) + return parameters + + def get_raw(self): + version = self._encode_buffer(self.version) + # encode types + types = self._encode_buffer(self.types) + structs = self._encode_buffer(self.structs) + actions = self._encode_buffer(self.actions) + tables = self._encode_buffer(self.tables) + ricardian_clauses = self._encode_buffer(self.ricardian_clauses) + error_messages = self._encode_buffer(self.error_messages) + abi_extensions = self._encode_buffer(self.abi_extensions) + variants = self._encode_buffer(self.variants) + return '{}{}{}{}{}{}{}{}{}'.format(version, types, structs, actions, tables, + ricardian_clauses, error_messages, abi_extensions, + variants) + + def encode(self): + ''' ''' + raw_abi = self.get_raw() + # divide by two because it is hex + length = self._encode_buffer(VarUInt(len(raw_abi) / 2)) + return length + raw_abi + + def json_to_bin(self, name, data): + # act = self.get_action(name) + params = self.get_action_parameters(name) + bin_buffer = '' + for field in data: + # create INRBuffer with value as a type of field + if isinstance(params[field], list): + field_type = type(params[field][0]) + arr = [] + for f in data[field]: + print(f) + arr.append(field_type(f)) + field_buffer = INRBuffer(arr) + else: + field_type = type(params[field]) + field_buffer = INRBuffer(field_type(data[field])) + + bin_buffer += field_buffer.encode() + return bin_buffer + + +class Authorization(BaseObject): + def __init__(self, d): + ''' ''' + # create validator + self._validator = PermissionLevelSchema() + super(Authorization, self).__init__(d) + + def encode(self): + ''' ''' + actor = self._encode_buffer(AccountName(self.actor)) + perms = self._encode_buffer(PermissionName(self.permission)) + return '{}{}'.format(actor, perms) + + +class ChainInfo(BaseObject): + def __init__(self, d): + ''' ''' + self._validator = ChainInfoSchema() + super(ChainInfo, self).__init__(d) + + +class BlockInfo(BaseObject): + def __init__(self, d): + ''' ''' + self._validator = BlockInfoSchema() + super(BlockInfo, self).__init__(d) + + +class Transaction(BaseObject): + def __init__(self, d, chain_info, lib_info): + ''' ''' + # add defaults + if 'expiration' not in d: + d['expiration'] = str((dt.datetime.utcnow() + dt.timedelta(seconds=30)).replace(tzinfo=pytz.UTC)) + if 'ref_block_num' not in d: + d['ref_block_num'] = chain_info['last_irreversible_block_num'] & 0xFFFF + if 'ref_block_prefix' not in d: + d['ref_block_prefix'] = lib_info['ref_block_prefix'] + # validate + self._validator = TransactionSchema() + super(Transaction, self).__init__(d) + # parse actions + self.actions = self._create_obj_array(self.actions, Action) + + def _encode_hdr(self): + ''' ''' + # convert + exp_ts = (self.expiration - dt.datetime(1970, 1, 1, tzinfo=self.expiration.tzinfo)).total_seconds() + exp = self._encode_buffer(UInt32(exp_ts)) + ref_blk = self._encode_buffer(UInt16(self.ref_block_num & 0xffff)) + ref_block_prefix = self._encode_buffer(UInt32(self.ref_block_prefix)) + net_usage_words = self._encode_buffer(VarUInt(self.net_usage_words)) + max_cpu_usage_ms = self._encode_buffer(Byte(self.max_cpu_usage_ms)) + delay_sec = self._encode_buffer(VarUInt(self.delay_sec)) + # create hdr buffer + hdr = '{}{}{}{}{}{}'.format(exp, ref_blk, ref_block_prefix, net_usage_words, max_cpu_usage_ms, delay_sec) + return hdr + + def encode(self): + ''' ''' + hdr_buf = self._encode_hdr() + context_actions = self._encode_buffer(self.context_free_actions) + actions = self._encode_buffer(self.actions) + trans_exts = self._encode_buffer(self.transaction_extensions) + return bytearray.fromhex(hdr_buf + context_actions + actions + trans_exts) + + def get_id(self): + return sha256(self.encode()) + + +class PackedTransaction: + def __init__(self, trx, ce): + self._cline = ce + self._packed_trx = trx + # empty header + self._is_unpacked = False + self._unpacked_trx = OrderedDict() + + def _decode_buffer(self, objType, buf): + ''' ''' + eBuf = INRBuffer("") + return eBuf.decode(objType, buf) + + def _decode_header(self, buf): + ''' ''' + buf = self._packed_trx + # get expiration buffer + (exp, buf) = self._decode_buffer(UInt32(), buf) + # get expiration in UTC + exp_dt = dt.datetime.utcfromtimestamp(exp) + self._unpacked_trx['expiration'] = exp_dt.strftime("%Y-%m-%dT%H:%M:%S") + # get ref_block + (ref_blk, buf) = self._decode_buffer(UInt16(), buf) + self._unpacked_trx['ref_block_num'] = ref_blk + # get ref_block_prefix + (ref_blk_pre, buf) = self._decode_buffer(UInt32(), buf) + self._unpacked_trx['ref_block_prefix'] = ref_blk_pre + # get net usage + (max_net_usage, buf) = self._decode_buffer(VarUInt(), buf) + self._unpacked_trx['max_net_usage_words'] = max_net_usage + # get cpu usage + (max_cpu_usage, buf) = self._decode_buffer(Byte(), buf) + self._unpacked_trx['max_cpu_usage_ms'] = max_cpu_usage + # get delay sec + (delay_sec, buf) = self._decode_buffer(VarUInt(), buf) + self._unpacked_trx['delay_sec'] = delay_sec + return buf + + def decode_actions(self, buf): + ''' ''' + # get length of action array + actions = [] + (length, act_buf) = self._decode_buffer(VarUInt(), buf) + cnt = 0 + # loop through array + while cnt < length and length: + # process action account/name + (acct_name, act_buf) = self._decode_buffer(AccountName(), act_buf) + (action_name, act_buf) = self._decode_buffer(ActionName(), act_buf) + # get authorizations + (auth, act_buf) = self.decode_authorizations(act_buf) + # get data length + (hex_data_len, act_buf) = self._decode_buffer(VarUInt(), act_buf) + # get abi information + contract_abi = self._cline.get_abi(acct_name) + abi = Abi(contract_abi["abi"]) + abi_act = abi.get_action(action_name) + # temp check need to handle this better + if abi_act["type"] != action_name: + raise INRAbiProcessingError("Error processing the {} action".format(action_name)) + abi_struct = abi.get_action_parameters(action_name) + data = OrderedDict() + # save data for hex_data + data_diff = act_buf + for a in abi_struct: + (act_data, act_buf) = self._decode_buffer(abi_struct[a], act_buf) + data[a] = act_data + act = OrderedDict({ + 'account': acct_name, + 'name': action_name, + "authorization": auth, + "data": data, + "hex_data": data_diff.rstrip(act_buf), + }) + actions.append(act) + # increment count + cnt += 1 + + return (actions, act_buf) + + def decode_authorizations(self, buf): + ''' ''' + auths = [] + (length, auth_buf) = self._decode_buffer(VarUInt(), buf) + cnt = 0 + while cnt < length and length: + # process action account/name + (acct_name, auth_buf) = self._decode_buffer(AccountName(), auth_buf) + (perm, auth_buf) = self._decode_buffer(ActionName(), auth_buf) + auth = OrderedDict({ + 'actor': acct_name, + 'permission': perm, + }) + auths.append(auth) + cnt += 1 + return (auths, auth_buf) + + # placeholder until context_free_actions are implemented. Might be able to use self.decode_actions + def decode_context_actions(self, buf): + ''' ''' + (length, ctx_buf) = self._decode_buffer(VarUInt(), buf) + if length > 0: + raise NotImplementedError("Currently inerypy does not support context_free_actions") + # get length of action array + return (length, ctx_buf) + + # placeholder until context_free_actions are implemented. Might be able to use self.decode_actions + def decode_trx_extensions(self, buf): + ''' ''' + trx_ext = [] + (length, ctx_buf) = self._decode_buffer(VarUInt(), buf) + if length > 0: + raise NotImplementedError("Currently inerypy does not support transaction extensions") + # get length of action array + return (trx_ext, ctx_buf) + + def get_id(self): + ''' ''' + return sha256(bytearray.fromhex(self._packed_trx)) + + def get_transaction(self): + ''' ''' + # only unpack once + if not self._is_unpacked: + # decode the header and get the rest of the trx back + trx_buf = self._decode_header(self._packed_trx) + # process list of context free actions + (context_actions, trx_buf) = self.decode_context_actions(trx_buf) + self._unpacked_trx['context_free_actions'] = context_actions + # process actions + (actions, trx_buf) = self.decode_actions(trx_buf) + self._unpacked_trx['actions'] = actions + # process transaction extensions + (trx_ext, trx_buf) = self.decode_trx_extensions(trx_buf) + self._unpacked_trx['transaction_extensions'] = trx_ext + # set boolean + self._is_unpacked = True + return self._unpacked_trx + + +class INRBuffer: + def __init__(self, v): + self._value = v + self._count = 0 + + def _decode_number(self, val, format='L'): + byte_val = binascii.unhexlify(val) + return convert_big_endian(byte_val, format) + + def _decode_float(self, val, format='f'): + byte_val = binascii.unhexlify(val) + return struct.unpack(">{}".format(format), byte_val) + + def _decode_name(self, val, format='Q'): + ''' ''' + num = self._decode_number(val, format) + return name_to_string(num) + + def _decode_str(self, val): + ''' ''' + # get length + vu = VarUInt() + (length, val) = vu.decode(val) + string = '' + leftover = val + # if there is data parse it + if length > 0: + (str_data, leftover) = self._splice_buf(val, length * 2) + string = binascii.unhexlify(str_data).decode() + return (string, leftover) + + def _splice_buf(self, buf, length): + return buf[:length], buf[length:] + + def _write_number(self, val, format='q'): + ''' ''' + le = convert_little_endian(val, format) + return binascii.hexlify(le).decode() + + def _write_name(self, w_str): + ''' ''' + val = string_to_name(w_str) + le = convert_little_endian(val, 'Q') + return binascii.hexlify(le).decode() + + def _write_str(self, w_str): + b = bytearray() + length = VarUInt(len(w_str)).encode() + b.extend(map(ord, w_str)) + return binascii.hexlify(length + b).decode() + + def _write_varuint(self, vuint): + buf = vuint.encode() + return binascii.hexlify(buf).decode() + + def decode(self, objType, buf=None): + leftover = "" + if not buf: + buf = self._value + if isinstance(objType, UInt32): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_number(val, 'I') + elif isinstance(objType, UInt16): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_number(val, 'H') + elif isinstance(objType, VarUInt): + (val, leftover) = objType.decode(buf) + elif(isinstance(objType, Byte) or + isinstance(objType, bool)): + (hex_str, leftover) = self._splice_buf(buf, 2) + val = hex_to_int(hex_str) + elif isinstance(objType, Float): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_float(val, 'f') + elif(isinstance(objType, int) or + isinstance(objType, long)): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_number(val, 'q') + elif (isinstance(objType, Name) or + isinstance(objType, AccountName) or + isinstance(objType, PermissionName) or + isinstance(objType, ActionName) or + isinstance(objType, TableName) or + isinstance(objType, ScopeName)): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_name(val) + elif isinstance(objType, str): + (val, leftover) = self._decode_str(buf) + elif(isinstance(objType, list)): + # get count(VarUint) + val = [] + (length, leftover) = VarUInt("").decode(buf) + while len(val) < length: + (out, leftover) = self.decode(objType[0], leftover) + val.append(out) + else: + raise INRBufferInvalidType("Cannot decode type: {}".format(type(objType))) + + return (val, leftover) + + def encode(self, val=None): + if not val: + val = self._value + if (isinstance(val, Name) or + isinstance(val, AccountName) or + isinstance(val, PermissionName) or + isinstance(val, ActionName) or + isinstance(val, TableName) or + isinstance(val, ScopeName)): + val = self._write_name(val) + return val + elif(isinstance(val, str)): + return self._write_str(val) + elif(isinstance(val, Byte) or + isinstance(val, bool)): + # return self._write_number(val, '?') + return int_to_hex(val) + elif(isinstance(val, UInt16)): + return self._write_number(val, 'H') + elif(isinstance(val, UInt32)): + return self._write_number(val, 'I') + elif(isinstance(val, UInt64)): + return self._write_number(val, 'q') + elif(isinstance(val, Float)): + return self._write_number(val, 'f') + elif(isinstance(val, VarUInt)): + # temp encoding + return self._write_varuint(val) + elif(isinstance(val, int) or + isinstance(val, long)): + return self._write_number(val, 'l') + elif(isinstance(val, Action) or + isinstance(val, AbiStruct) or + isinstance(val, AbiStructField) or + isinstance(val, AbiType) or + isinstance(val, AbiAction) or + isinstance(val, AbiTable) or + isinstance(val, AbiRicardianClauses) or + isinstance(val, AbiErrorMessages) or + isinstance(val, AbiExtensions) or + isinstance(val, AbiVariants) or + isinstance(val, Asset) or + isinstance(val, Authorization)): + return val.encode() + elif(isinstance(val, list)): + buf = self._write_varuint(VarUInt(len(val))) + for item in val: + e_item = self.encode(item) + buf = '{}{}'.format(buf, e_item) + return buf + else: + raise INRBufferInvalidType('Cannot encode type: {}'.format(type(val))) + +class InvalidKeyFile(Exception): + ''' Raised when the key file format is invalid ''' + pass + + +class INRKeyError(Exception): + ''' Raised when there is an INRKey error ''' + pass + + +class INRMsigInvalidProposal(Exception): + ''' Raised when an invalid proposal is queried''' + pass + + +class INRBufferInvalidType(Exception): + ''' Raised when trying to encode/decode an invalid type ''' + pass + + +class INRInvalidSchema(Exception): + ''' Raised when trying to process a schema ''' + pass + + +class INRUnknownObj(Exception): + ''' Raised when an object is not found in the ABI ''' + pass + + +class INRAbiProcessingError(Exception): + ''' Raised when the abi action cannot be processed ''' + pass + + +class INRSetSameCode(Exception): + ''' Raised when the code would not change on a set''' + pass + + +class INRSetSameAbi(Exception): + ''' Raised when the abi would not change on a set''' + pass + + + + +def convert_little_endian(buf, format='q'): + ''' ''' + return struct.pack('<{}'.format(format), buf) + + +def convert_big_endian(buf, format="I"): + ''' ''' + # return the first value of the tuple that is returned by unpack + return struct.unpack('<{}'.format(format), buf)[0] + +# json encoder + + +class INREncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, Action): + return o.__dict__ + if isinstance(o, Authorization): + return o.__dict__ + if isinstance(o, dt.datetime): + return o.isoformat() + + +class Name(str): + hex_str_len = 16 + + +class AccountName(Name): + pass + + +class PermissionName(Name): + pass + + +class ActionName(Name): + pass + + +class TableName(Name): + pass + + +class ScopeName(Name): + pass + + +class Byte(int): + # length of hex str + hex_str_len = 2 + + +class UInt16(int): + # length of hex str + hex_str_len = 4 + + +class UInt32(int): + # length of hex str + hex_str_len = 8 + + +class UInt64(int): + # length of hex str + hex_str_len = 16 + + +class Int16(int): + # length of hex str + hex_str_len = 4 + + +class Int32(int): + # length of hex str + hex_str_len = 8 + + +class Int64(int): + # length of hex str + hex_str_len = 16 + + +class Float(float): + # length of hex str + hex_str_len = 8 + + +if six.PY3: + class long(int): + pass + + +class VarUInt: + def __init__(self, val=""): + ''' ''' + self._val = val + self._b_arr = bytearray() + + def _push_byte(self, val): + self._b_arr.append(int(val)) + + def encode(self): + ''' ''' + # ensure value is an int + val = int(self._val) + buf = int((val) & 0x7f) + val >>= 7 + buf |= (((val > 0) if 1 else 0) << 7) + self._push_byte(buf) + while val: + buf = int((val) & 0x7f) + val >>= 7 + buf |= (((val > 0) if 1 else 0) << 7) + self._push_byte(buf) + return self._b_arr + + def _pop(self, buf, length): + return buf[:length], buf[length:] + + def decode(self, buf): + ''' ''' + shift = 0 + result = 0 + while True: + tmp, buf = self._pop(buf, 2) + i = hex_to_int(tmp) + result |= (i & 0x7f) << shift + shift += 7 + if not(i & 0x80): + break + return result, buf + + +class BaseObject(object): + def __init__(self, d): + ''' ''' + try: + self._obj = self._validator.deserialize(d) + except Invalid: + raise INRInvalidSchema('Unable to process schema for {}'.format(type(self))) + # instantiate the class + for k, v in self._obj.items(): + setattr(self, k, v) + # clean up + del self._obj + del self._validator + + def __repr__(self): + ''' ''' + return '{}({})'.format(self.__class__, self.__dict__) + + def _encode_buffer(self, value): + ''' ''' + return INRBuffer(value).encode() + + def _create_obj_array(self, arr, class_type): + ''' ''' + new_arr = [] + for item in arr: + new_arr.append(class_type(item)) + return new_arr + + +class Action(BaseObject): + def __init__(self, d): + ''' ''' + self._validator = ActionSchema() + super(Action, self).__init__(d) + # setup permissions + self.authorization = self._create_obj_array(self.authorization, Authorization) + + def encode(self): + ''' ''' + acct = self._encode_buffer(AccountName(self.account)) + name = self._encode_buffer(Name(self.name)) + auth = self._encode_buffer(self.authorization) + # need to figure out how to process data + # get length + data_len = self._encode_buffer(VarUInt(len(self.data) / 2)) + data = data_len + self.data + return '{}{}{}{}'.format(acct, name, auth, data) + + +class Asset: + def __init__(self, value, precision=4): + # self.amount = amt + # self.symbol = sym + # self.precision = precision + self.from_string(value) + + def __str__(self): + return '{amount:.{precision}f} {symbol}'.format(amount=self.amount, symbol=self.symbol, precision=self.precision) + + def __add__(self, other): + if self.symbol != other.symbol: + raise TypeError('Symbols must match: {} != {}', self.symbol, other.symbol) + return Asset(self.amount + other.amount, self.symbol) + + def __sub__(self, other): + if self.amount - other.amount < 0: + raise ValueError('Subtraction would result in a negative.') + if self.symbol != other.symbol: + raise TypeError('Symbols must match: {} != {}', self.symbol, other.symbol) + return Asset(self.amount - other.amount, self.symbol) + + def from_string(self, s): + splt = s.split() + try: + self.amount = float(splt[0]) + self.symbol = splt[1] + self.precision = len(splt[0].split(".")[1]) + except IndexError: + raise IndexError('Invalid string format given. Must be in the formst ') + + def _string_to_symbol(self): + ''' ''' + rslt = 0 + cnt = 0 + while cnt < len(self.symbol): + letter = self.symbol[cnt] + if letter >= 'A' or letter <= 'Z': + l = ord(letter) + rslt |= (UInt64(l) << (8 * (cnt + 1))) + else: + raise ValueError("{} contains an invalid symbol. Must be [A-Z].".format(self.symbol)) + + cnt += 1 + rslt |= UInt64(self.precision) + return INRBuffer(UInt64(rslt)).encode() + + def encode(self): + ''' ''' + power = '1'.ljust(self.precision + len('1'), '0') + amount = INRBuffer(UInt64(self.amount * UInt64(power))).encode() + symbol = self._string_to_symbol() + return '{amount}{symbol}'.format(amount=amount, symbol=symbol) + + +class AbiType(BaseObject): + def __init__(self, d): + self._validator = AbiTypeSchema() + super(AbiTypesSchema, self).__init__(d) + + def encode(self): + new_type_name = self._encode_buffer(self.new_type_name) + type = self._encode_buffer(self.type) + return '{}{}'.format(new_type_name, type) + + +class AbiStructField(BaseObject): + def __init__(self, d): + self._validator = AbiStructFieldSchema() + super(AbiStructField, self).__init__(d) + + def encode(self): + name = self._encode_buffer(self.name) + type = self._encode_buffer(self.type) + return '{}{}'.format(name, type) + + +class AbiStruct(BaseObject): + def __init__(self, d): + self._validator = AbiStructSchema() + super(AbiStruct, self).__init__(d) + self.fields = self._create_obj_array(self.fields, AbiStructField) + + def encode(self): + name = self._encode_buffer(self.name) + base = self._encode_buffer(self.base) + fields = self._encode_buffer(self.fields) + return '{}{}{}'.format(name, base, fields) + + +class AbiAction(BaseObject): + def __init__(self, d): + self._validator = AbiActionSchema() + super(AbiAction, self).__init__(d) + + def encode(self): + name = self._encode_buffer(Name(self.name)) + type = self._encode_buffer(self.type) + ricardian_contract = self._encode_buffer(self.ricardian_contract) + return '{}{}{}'.format(name, type, ricardian_contract) + + +class AbiTable(BaseObject): + def __init__(self, d): + self._validator = AbiTableSchema() + super(AbiTable, self).__init__(d) + + def encode(self): + name = self._encode_buffer(Name(self.name)) + index_type = self._encode_buffer(self.index_type) + key_names = self._encode_buffer(self.key_names) + key_types = self._encode_buffer(self.key_types) + type = self._encode_buffer(self.type) + return '{}{}{}{}{}'.format(name, index_type, key_names, key_types, type) + + +class AbiRicardianClauses(BaseObject): + def __init__(self, d): + self._validator = AbiRicardianClauseSchema() + super(AbiRicardianClauses, self).__init__(d) + + def encode(self): + id = self._encode_buffer(self.id) + body = self._encode_buffer(self.body) + return '{}{}'.format(id, body) + + +class AbiErrorMessages(BaseObject): + # TODO implement encode + def __init__(self, d): + self._validator = AbiErrorMessagesSchema() + super(AbiErrorMessages, self).__init__(d) + + +class AbiExtensions(BaseObject): + # TODO implement encode + def __init__(self, d): + self._validator = AbiExtensionsSchema() + super(AbiExtensions, self).__init__(d) + + + +class AbiVariants(BaseObject): + # TODO implement encode + def __init__(self, d): + self._validator = AbiVariantsSchema() + super(AbiVariants, self).__init__(d) + + + +class Abi(BaseObject): + _abi_map = { + # name + 'name': Name(), + 'string': str(), + # numbers + 'bool': Byte(), + 'uint8': Byte(), + 'uint16': UInt16(), + 'uint32': UInt32(), + 'uint64': UInt64(), + 'int8': Byte(), # NotImplemented + 'int16': Int16(), # NotImplemented + 'int32': Int32(), # NotImplemented + 'int64': Int64(), # NotImplemented + 'float64': Float(), # NotImplemented + # 'varuint32': VarUInt # NotImplemented + # complex + 'asset': Asset("1.0000 INR"), + # 'checksum256': str, # NotImplemented + # 'block_timestamp_type': UInt64, # NotImplemented + # 'time_point': UInt64, # NotImplemented + # 'connector': str, # NotImplemented + # 'public_key': str, # NotImplemented + # 'authority': str, # NotImplemented + # 'block_header': str, # NotImplemented + # 'bytes': str, # NotImplemented + # 'permission_level': str, # NotImplemented + # 'permission_level_weight': str, #NotImplemented + } + + def __init__(self, d): + ''' ''' + self._validator = AbiSchema() + super(Abi, self).__init__(d) + self.types = self._create_obj_array(self.types, AbiType) + self.structs = self._create_obj_array(self.structs, AbiStruct) + self.actions = self._create_obj_array(self.actions, AbiAction) + self.tables = self._create_obj_array(self.tables, AbiTable) + self.ricardian_clauses = self._create_obj_array(self.ricardian_clauses, AbiRicardianClauses) + self.error_messages = self._create_obj_array(self.error_messages, AbiErrorMessages) + self.abi_extensions = self._create_obj_array(self.abi_extensions, AbiExtensions) + self.variants = self._create_obj_array(self.variants, AbiVariants) + + def get_action(self, name): + ''' ''' + for act in self.actions: + if act.name == name: + return act + raise INRUnknownObj('{} is not a valid action for this contract'.format(name)) + + def get_actions(self): + actions = [] + for act in self.actions: + actions.append(act.name) + return actions + + def get_struct(self, name): + ''' ''' + for struct in self.structs: + if struct.name == name: + return struct + raise INRUnknownObj('{} is not a valid struct for this contract'.format(name)) + + def get_action_parameters(self, name): + ''' ''' + parameters = OrderedDict() + # get the struct + struct = self.get_struct(name) + for field in struct.fields: + f = field.type.strip('[]') + if(f in self._abi_map): + field_type = self._abi_map[f] + # check if the field is a list + if '[]' in field.type: + field_type = [field_type] + parameters[field.name] = field_type + else: + raise INRUnknownObj("{} is not a known abi type".format(field.type)) + return parameters + + def get_raw(self): + version = self._encode_buffer(self.version) + # encode types + types = self._encode_buffer(self.types) + structs = self._encode_buffer(self.structs) + actions = self._encode_buffer(self.actions) + tables = self._encode_buffer(self.tables) + ricardian_clauses = self._encode_buffer(self.ricardian_clauses) + error_messages = self._encode_buffer(self.error_messages) + abi_extensions = self._encode_buffer(self.abi_extensions) + variants = self._encode_buffer(self.variants) + return '{}{}{}{}{}{}{}{}{}'.format(version, types, structs, actions, tables, + ricardian_clauses, error_messages, abi_extensions, + variants) + + def encode(self): + ''' ''' + raw_abi = self.get_raw() + # divide by two because it is hex + length = self._encode_buffer(VarUInt(len(raw_abi) / 2)) + return length + raw_abi + + def json_to_bin(self, name, data): + # act = self.get_action(name) + params = self.get_action_parameters(name) + bin_buffer = '' + for field in data: + # create INRBuffer with value as a type of field + if isinstance(params[field], list): + field_type = type(params[field][0]) + arr = [] + for f in data[field]: + print(f) + arr.append(field_type(f)) + field_buffer = INRBuffer(arr) + else: + field_type = type(params[field]) + field_buffer = INRBuffer(field_type(data[field])) + + bin_buffer += field_buffer.encode() + return bin_buffer + + +class Authorization(BaseObject): + def __init__(self, d): + ''' ''' + # create validator + self._validator = PermissionLevelSchema() + super(Authorization, self).__init__(d) + + def encode(self): + ''' ''' + actor = self._encode_buffer(AccountName(self.actor)) + perms = self._encode_buffer(PermissionName(self.permission)) + return '{}{}'.format(actor, perms) + + +class ChainInfo(BaseObject): + def __init__(self, d): + ''' ''' + self._validator = ChainInfoSchema() + super(ChainInfo, self).__init__(d) + + +class BlockInfo(BaseObject): + def __init__(self, d): + ''' ''' + self._validator = BlockInfoSchema() + super(BlockInfo, self).__init__(d) + + +class Transaction(BaseObject): + def __init__(self, d, chain_info, lib_info): + ''' ''' + # add defaults + if 'expiration' not in d: + d['expiration'] = str((dt.datetime.utcnow() + dt.timedelta(seconds=30)).replace(tzinfo=pytz.UTC)) + if 'ref_block_num' not in d: + d['ref_block_num'] = chain_info['last_irreversible_block_num'] & 0xFFFF + if 'ref_block_prefix' not in d: + d['ref_block_prefix'] = lib_info['ref_block_prefix'] + # validate + self._validator = TransactionSchema() + super(Transaction, self).__init__(d) + # parse actions + self.actions = self._create_obj_array(self.actions, Action) + + def _encode_hdr(self): + ''' ''' + # convert + exp_ts = (self.expiration - dt.datetime(1970, 1, 1, tzinfo=self.expiration.tzinfo)).total_seconds() + exp = self._encode_buffer(UInt32(exp_ts)) + ref_blk = self._encode_buffer(UInt16(self.ref_block_num & 0xffff)) + ref_block_prefix = self._encode_buffer(UInt32(self.ref_block_prefix)) + net_usage_words = self._encode_buffer(VarUInt(self.net_usage_words)) + max_cpu_usage_ms = self._encode_buffer(Byte(self.max_cpu_usage_ms)) + delay_sec = self._encode_buffer(VarUInt(self.delay_sec)) + # create hdr buffer + hdr = '{}{}{}{}{}{}'.format(exp, ref_blk, ref_block_prefix, net_usage_words, max_cpu_usage_ms, delay_sec) + return hdr + + def encode(self): + ''' ''' + hdr_buf = self._encode_hdr() + context_actions = self._encode_buffer(self.context_free_actions) + actions = self._encode_buffer(self.actions) + trans_exts = self._encode_buffer(self.transaction_extensions) + return bytearray.fromhex(hdr_buf + context_actions + actions + trans_exts) + + def get_id(self): + return sha256(self.encode()) + + +class PackedTransaction: + def __init__(self, trx, ce): + self._cline = ce + self._packed_trx = trx + # empty header + self._is_unpacked = False + self._unpacked_trx = OrderedDict() + + def _decode_buffer(self, objType, buf): + ''' ''' + eBuf = INRBuffer("") + return eBuf.decode(objType, buf) + + def _decode_header(self, buf): + ''' ''' + buf = self._packed_trx + # get expiration buffer + (exp, buf) = self._decode_buffer(UInt32(), buf) + # get expiration in UTC + exp_dt = dt.datetime.utcfromtimestamp(exp) + self._unpacked_trx['expiration'] = exp_dt.strftime("%Y-%m-%dT%H:%M:%S") + # get ref_block + (ref_blk, buf) = self._decode_buffer(UInt16(), buf) + self._unpacked_trx['ref_block_num'] = ref_blk + # get ref_block_prefix + (ref_blk_pre, buf) = self._decode_buffer(UInt32(), buf) + self._unpacked_trx['ref_block_prefix'] = ref_blk_pre + # get net usage + (max_net_usage, buf) = self._decode_buffer(VarUInt(), buf) + self._unpacked_trx['max_net_usage_words'] = max_net_usage + # get cpu usage + (max_cpu_usage, buf) = self._decode_buffer(Byte(), buf) + self._unpacked_trx['max_cpu_usage_ms'] = max_cpu_usage + # get delay sec + (delay_sec, buf) = self._decode_buffer(VarUInt(), buf) + self._unpacked_trx['delay_sec'] = delay_sec + return buf + + def decode_actions(self, buf): + ''' ''' + # get length of action array + actions = [] + (length, act_buf) = self._decode_buffer(VarUInt(), buf) + cnt = 0 + # loop through array + while cnt < length and length: + # process action account/name + (acct_name, act_buf) = self._decode_buffer(AccountName(), act_buf) + (action_name, act_buf) = self._decode_buffer(ActionName(), act_buf) + # get authorizations + (auth, act_buf) = self.decode_authorizations(act_buf) + # get data length + (hex_data_len, act_buf) = self._decode_buffer(VarUInt(), act_buf) + # get abi information + contract_abi = self._cline.get_abi(acct_name) + abi = Abi(contract_abi["abi"]) + abi_act = abi.get_action(action_name) + # temp check need to handle this better + if abi_act["type"] != action_name: + raise INRAbiProcessingError("Error processing the {} action".format(action_name)) + abi_struct = abi.get_action_parameters(action_name) + data = OrderedDict() + # save data for hex_data + data_diff = act_buf + for a in abi_struct: + (act_data, act_buf) = self._decode_buffer(abi_struct[a], act_buf) + data[a] = act_data + act = OrderedDict({ + 'account': acct_name, + 'name': action_name, + "authorization": auth, + "data": data, + "hex_data": data_diff.rstrip(act_buf), + }) + actions.append(act) + # increment count + cnt += 1 + + return (actions, act_buf) + + def decode_authorizations(self, buf): + ''' ''' + auths = [] + (length, auth_buf) = self._decode_buffer(VarUInt(), buf) + cnt = 0 + while cnt < length and length: + # process action account/name + (acct_name, auth_buf) = self._decode_buffer(AccountName(), auth_buf) + (perm, auth_buf) = self._decode_buffer(ActionName(), auth_buf) + auth = OrderedDict({ + 'actor': acct_name, + 'permission': perm, + }) + auths.append(auth) + cnt += 1 + return (auths, auth_buf) + + def decode_trx_extensions(self, buf): + ''' ''' + trx_ext = [] + (length, ctx_buf) = self._decode_buffer(VarUInt(), buf) + if length > 0: + raise NotImplementedError("Currently inerypy does not support transaction extensions") + # get length of action array + return (trx_ext, ctx_buf) + + def get_id(self): + ''' ''' + return sha256(bytearray.fromhex(self._packed_trx)) + + def get_transaction(self): + ''' ''' + # only unpack once + if not self._is_unpacked: + # decode the header and get the rest of the trx back + trx_buf = self._decode_header(self._packed_trx) + # process list of context free actions + (context_actions, trx_buf) = self.decode_context_actions(trx_buf) + self._unpacked_trx['context_free_actions'] = context_actions + # process actions + (actions, trx_buf) = self.decode_actions(trx_buf) + self._unpacked_trx['actions'] = actions + # process transaction extensions + (trx_ext, trx_buf) = self.decode_trx_extensions(trx_buf) + self._unpacked_trx['transaction_extensions'] = trx_ext + # set boolean + self._is_unpacked = True + return self._unpacked_trx + + +class INRBuffer: + def __init__(self, v): + self._value = v + self._count = 0 + + def _decode_number(self, val, format='L'): + byte_val = binascii.unhexlify(val) + return convert_big_endian(byte_val, format) + + def _decode_float(self, val, format='f'): + byte_val = binascii.unhexlify(val) + return struct.unpack(">{}".format(format), byte_val) + + def _decode_name(self, val, format='Q'): + ''' ''' + num = self._decode_number(val, format) + return name_to_string(num) + + def _decode_str(self, val): + ''' ''' + # get length + vu = VarUInt() + (length, val) = vu.decode(val) + string = '' + leftover = val + # if there is data parse it + if length > 0: + (str_data, leftover) = self._splice_buf(val, length * 2) + string = binascii.unhexlify(str_data).decode() + return (string, leftover) + + def _splice_buf(self, buf, length): + return buf[:length], buf[length:] + + def _write_number(self, val, format='q'): + ''' ''' + le = convert_little_endian(val, format) + return binascii.hexlify(le).decode() + + def _write_name(self, w_str): + ''' ''' + val = string_to_name(w_str) + le = convert_little_endian(val, 'Q') + return binascii.hexlify(le).decode() + + def _write_str(self, w_str): + b = bytearray() + length = VarUInt(len(w_str)).encode() + b.extend(map(ord, w_str)) + return binascii.hexlify(length + b).decode() + + def _write_varuint(self, vuint): + buf = vuint.encode() + return binascii.hexlify(buf).decode() + + def decode(self, objType, buf=None): + leftover = "" + if not buf: + buf = self._value + if isinstance(objType, UInt32): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_number(val, 'I') + elif isinstance(objType, UInt16): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_number(val, 'H') + elif isinstance(objType, VarUInt): + (val, leftover) = objType.decode(buf) + elif(isinstance(objType, Byte) or + isinstance(objType, bool)): + (hex_str, leftover) = self._splice_buf(buf, 2) + val = hex_to_int(hex_str) + elif isinstance(objType, Float): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_float(val, 'f') + elif(isinstance(objType, int) or + isinstance(objType, long)): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_number(val, 'q') + elif (isinstance(objType, Name) or + isinstance(objType, AccountName) or + isinstance(objType, PermissionName) or + isinstance(objType, ActionName) or + isinstance(objType, TableName) or + isinstance(objType, ScopeName)): + (val, leftover) = self._splice_buf(buf, objType.hex_str_len) + val = self._decode_name(val) + elif isinstance(objType, str): + (val, leftover) = self._decode_str(buf) + elif(isinstance(objType, list)): + # get count(VarUint) + val = [] + (length, leftover) = VarUInt("").decode(buf) + while len(val) < length: + (out, leftover) = self.decode(objType[0], leftover) + val.append(out) + else: + raise INRBufferInvalidType("Cannot decode type: {}".format(type(objType))) + + return (val, leftover) + + def encode(self, val=None): + if not val: + val = self._value + if (isinstance(val, Name) or + isinstance(val, AccountName) or + isinstance(val, PermissionName) or + isinstance(val, ActionName) or + isinstance(val, TableName) or + isinstance(val, ScopeName)): + val = self._write_name(val) + return val + elif(isinstance(val, str)): + return self._write_str(val) + elif(isinstance(val, Byte) or + isinstance(val, bool)): + # return self._write_number(val, '?') + return int_to_hex(val) + elif(isinstance(val, UInt16)): + return self._write_number(val, 'H') + elif(isinstance(val, UInt32)): + return self._write_number(val, 'I') + elif(isinstance(val, UInt64)): + return self._write_number(val, 'q') + elif(isinstance(val, Float)): + return self._write_number(val, 'f') + elif(isinstance(val, VarUInt)): + # temp encoding + return self._write_varuint(val) + elif(isinstance(val, int) or + isinstance(val, long)): + return self._write_number(val, 'l') + elif(isinstance(val, Action) or + isinstance(val, AbiStruct) or + isinstance(val, AbiStructField) or + isinstance(val, AbiType) or + isinstance(val, AbiAction) or + isinstance(val, AbiTable) or + isinstance(val, AbiRicardianClauses) or + isinstance(val, AbiErrorMessages) or + isinstance(val, AbiExtensions) or + isinstance(val, AbiVariants) or + isinstance(val, Asset) or + isinstance(val, Authorization)): + return val.encode() + elif(isinstance(val, list)): + buf = self._write_varuint(VarUInt(len(val))) + for item in val: + e_item = self.encode(item) + buf = '{}{}'.format(buf, e_item) + return buf + else: + raise INRBufferInvalidType('Cannot encode type: {}'.format(type(val))) diff --git a/milasargawi/api/utils.py b/milasargawi/api/utils.py new file mode 100644 index 000000000..19aec7a9b --- /dev/null +++ b/milasargawi/api/utils.py @@ -0,0 +1,88 @@ +from binascii import hexlify +import hashlib + + +def sha256(data): + ''' ''' + return hashlib.sha256(data).hexdigest() + + +def ripemd160(data): + ''' ''' + #h = hashlib.new('ripemd160') + h = hashlib.new('rmd160') + h.update(data) + return h.hexdigest() + + +def sig_digest(payload, chain_id=None): + ''' ''' + if chain_id: + buf = bytearray.fromhex(chain_id) + else: + buf = bytearray(32) + # already a bytearray + buf.extend(payload) + buf.extend(bytearray(32)) + + return sha256(buf) + + +def int_to_hex(i): + return '{:02x}'.format(i) + + +def hex_to_int(i): + return int(i, 16) + +def str_to_hex(c): + hex_data = hexlify(bytearray(c, 'ascii')).decode() + return int(hex_data, 16) + + +def char_subtraction(a, b, add): + x = str_to_hex(a) + y = str_to_hex(b) + ans = str((x - y) + add) + if len(ans) % 2 == 1: + ans = '0' + ans + return int(ans) + + + +def char_to_symbol(c): + ''' ''' + if c >= 'a' and c <= 'z': + return char_subtraction(c, 'a', 6) + if c >= '1' and c <= '5': + return char_subtraction(c, '1', 1) + return 0 + + +def string_to_name(s): + ''' ''' + i = 0 + name = 0 + while i < len(s): + #sym = char_to_symbol(s[i]) + name += (char_to_symbol(s[i]) & 0x1F) << (64 - 5 * (i + 1)) + i += 1 + if i > 12: + name |= char_to_symbol(s[11]) & 0x0F + return name + + +def name_to_string(n): + ''' ''' + charmap = '.12345abcdefghijklmnopqrstuvwxyz' + name = ['.'] * 13 + i = 0 + while i <= 12: + c = charmap[n & (0x0F if i == 0 else 0x1F)] + name[12 - i] = c + n >>= 4 if i == 0 else 5 + i += 1 + return ''.join(name).rstrip('.') + + + diff --git a/milasargawi/env-example b/milasargawi/env-example new file mode 100644 index 000000000..6ad620a90 --- /dev/null +++ b/milasargawi/env-example @@ -0,0 +1,2 @@ +node_url="your_ip_address" +priv_key="your_inery_private_key" \ No newline at end of file diff --git a/milasargawi/inerytransfer.py b/milasargawi/inerytransfer.py new file mode 100644 index 000000000..3df4d1cb2 --- /dev/null +++ b/milasargawi/inerytransfer.py @@ -0,0 +1,33 @@ +from api.cline import Cline +from api import keys +import json + + +node_url = os.environ["node_url"] +priv_key = os.environ["priv_key"] + +action_data = { + "from": "milasargawi", + "to": "inery", + "quantity": '1.0000 CRT', + "memo": "CRT Token transfer", +} + +action_payload = { + "account": "milasargawi", + "name": "transfer", + "authorization": [{ + "actor": "milasargawi", + "permission": "active" + }] +} +data = cli.abi_json_to_bin(action_payload['account'], action_payload['name'], action_data) +action_payload['data'] = data['binargs'] + +trx = {"actions": [action_payload]} + +key = INRKey(priv_key) +out = cli.push_transaction(trx, key, broadcast=True) +print('================================================') +print(out) +print('================================================') \ No newline at end of file diff --git a/milasargawi/readme.md b/milasargawi/readme.md new file mode 100644 index 000000000..973e4c37d --- /dev/null +++ b/milasargawi/readme.md @@ -0,0 +1,35 @@ +**Requirements** +Python3 + + +**Change Directory** + +``` +cd ./milasargawi +``` + +**Install Requirements** + +``` +pip install -r requirements.txt +``` + +**Create .env and change its value** + +``` +cp .env-example .env +``` + +**change env variable with your info** + +``` +node_url="your_ip_address" +priv_key="your_inery_private_key" +``` + +**run solution** + +``` +python3 ./inerytransfer.py +``` + diff --git a/milasargawi/requirements.txt b/milasargawi/requirements.txt new file mode 100644 index 000000000..16e0b3156 --- /dev/null +++ b/milasargawi/requirements.txt @@ -0,0 +1,5 @@ +python-dotenv +pytz +colander +base58 +ecdsa \ No newline at end of file