def coerce_to_hex(x): if isinstance(x, int): return util.hexlify(zpad(rlp.int_to_big_endian(x), 20)) elif len(x) == 40 or len(x) == 0: return x else: return util.hexlify(zpad(x, 20)[-20:])
def sign_tx (proxy, unsigned_tx_hex, private_key_wif=None): """Sign unsigned transaction serialisation.""" if private_key_wif: for char in private_key_wif: if char not in util.b58_digits: raise exceptions.TransactionError('invalid private key') # TODO: Hack! (pybitcointools is Python 2 only) import subprocess i = 0 tx_hex = unsigned_tx_hex while True: # pybtctool doesn’t implement `signall` try: tx_hex = subprocess.check_output(['pybtctool', 'sign', tx_hex, str(i), private_key_wif], stderr=subprocess.DEVNULL) except Exception as e: break if tx_hex != unsigned_tx_hex: signed_tx_hex = tx_hex.decode('utf-8') return signed_tx_hex[:-1] # Get rid of newline. else: raise exceptions.TransactionError('Could not sign transaction with pybtctool.') else: # Assume source is in wallet and wallet is unlocked. result = backend.signrawtransaction(proxy, backend.deserialize(unsigned_tx_hex)) if result['complete']: signed_tx_hex = util.hexlify(backend.serialize(result['tx'])) else: raise exceptions.TransactionError('Could not sign transaction with Bitcoin Core.') return signed_tx_hex
def base58_check_decode(s, version): # Convert the string to an integer n = 0 for c in s: n *= 58 if c not in b58_digits: raise exceptions.InvalidBase58Error('Not a valid Base58 character: ‘{}’'.format(c)) digit = b58_digits.index(c) n += digit # Convert the integer to bytes h = '%x' % n if len(h) % 2: h = '0' + h res = binascii.unhexlify(h.encode('utf8')) # Add padding back. pad = 0 for c in s[:-1]: if c == b58_digits[0]: pad += 1 else: break k = version * pad + res addrbyte, data, chk0 = k[0:1], k[1:-4], k[-4:] if addrbyte != version: raise exceptions.VersionByteError('incorrect version byte') chk1 = util.dhash(addrbyte + data)[:4] if chk0 != chk1: raise exceptions.Base58ChecksumError('Checksum mismatch: 0x{} ≠ 0x{}'.format(util.hexlify(chk0), util.hexlify(chk1))) return data
def hexprint(x): assert type(x) in (bytes, list) if not x: return '<None>' if x != -1: return ('0x' + util.hexlify(bytes(x))) else: return 'OUT OF GAS'
def base58_encode(binary): # Convert big‐endian bytes to integer n = int('0x0' + util.hexlify(binary), 16) # Divide that integer into base58 res = [] while n > 0: n, r = divmod(n, 58) res.append(b58_digits[r]) res = ''.join(res[::-1]) return res
def parse (db, tx, message): if not config.TESTNET: # TODO return try: gasprice, startgas, endowment = struct.unpack(FORMAT, message[:LENGTH]) except struct.error: gasprice, startgas, endowment = 0, 0, 0 # TODO: Is this ideal code = util.hexlify(message[LENGTH:]) source, destination, data = execute.compose(db, tx['source'], '', gasprice, startgas, endowment, code) message = data[4:] # Execute transaction upon publication, for actual creation of contract. execute.parse(db, tx, message)
def base58_check_encode(original, version): b = binascii.unhexlify(bytes(original, 'utf-8')) d = version + b binary = d + util.dhash(d)[:4] res = base58_encode(binary) # Encode leading zeros as base58 zeros czero = 0 pad = 0 for c in d: if c == czero: pad += 1 else: break address = b58_digits[0] * pad + res if original != util.hexlify(base58_check_decode(address, version)): raise AddressError('encoded address does not decode properly') return address
def sign_tx(proxy, unsigned_tx_hex, private_key_wif=None): """Sign unsigned transaction serialisation.""" if private_key_wif: for char in private_key_wif: if char not in util.b58_digits: raise exceptions.TransactionError('invalid private key') # TODO: Hack! (pybitcointools is Python 2 only) import subprocess i = 0 tx_hex = unsigned_tx_hex while True: # pybtctool doesn’t implement `signall` try: tx_hex = subprocess.check_output( ['pybtctool', 'sign', tx_hex, str(i), private_key_wif], stderr=subprocess.DEVNULL) except Exception as e: break if tx_hex != unsigned_tx_hex: signed_tx_hex = tx_hex.decode('utf-8') return signed_tx_hex[:-1] # Get rid of newline. else: raise exceptions.TransactionError( 'Could not sign transaction with pybtctool.') else: # Assume source is in wallet and wallet is unlocked. result = backend.signrawtransaction( proxy, backend.deserialize(unsigned_tx_hex)) if result['complete']: signed_tx_hex = util.hexlify(backend.serialize(result['tx'])) else: raise exceptions.TransactionError( 'Could not sign transaction with Bitcoin Core.') return signed_tx_hex
def get_tx_info(tx_hex): source, destination, btc_amount, fee, data = blocks.get_tx_info(tx_hex, util.last_block(db)['block_index']) return source, destination, btc_amount, fee, util.hexlify(data)
def henc(n): return util.hexlify(enc(n))
def memprint(data): line = util.hexlify(bytes(data)) line = ' '.join([line[i:i + 2] for i in range(0, len(line), 2)]) return line
def parse (db, tx, message): if not config.TESTNET: # TODO return status = 'valid' output, gas_cost, gas_remained = None, None, None try: # TODO: Use unpack function. # Unpack message. curr_format = FORMAT + '{}s'.format(len(message) - LENGTH) try: contract_id, gasprice, startgas, value, payload = struct.unpack(curr_format, message) if gasprice > config.MAX_INT or startgas > config.MAX_INT: # TODO: define max for gasprice and startgas raise exceptions.UnpackError() except (struct.error) as e: raise exceptions.UnpackError() gas_remained = startgas contract_id = util.hexlify(contract_id) if contract_id == '0000000000000000000000000000000000000000': contract_id = '' # ‘Apply transaction’! tx_obj = Transaction(tx, contract_id, gasprice, startgas, value, payload) block_obj = blocks.Block(db, tx['block_hash']) success, output, gas_remained = processblock.apply_transaction(db, tx_obj, block_obj) if not success and output == '': status = 'out of gas' gas_cost = gasprice * (startgas - gas_remained) # different definition from pyethereum’s except exceptions.UnpackError as e: contract_id, gasprice, startgas, value, payload = None, None, None, None, None status = 'invalid: could not unpack' output = None except processblock.ContractError as e: status = 'invalid: no such contract' contract_id = None output = None except processblock.InsufficientStartGas as e: have, need = e.args logger.debug('Insufficient start gas: have {} and need {}'.format(have, need)) status = 'invalid: insufficient start gas' output = None except processblock.InsufficientBalance as e: have, need = e.args logger.debug('Insufficient balance: have {} and need {}'.format(have, need)) status = 'invalid: insufficient balance' output = None except processblock.OutOfGas as e: logger.debug('TX OUT_OF_GAS (startgas: {}, gas_remained: {})'.format(startgas, gas_remained)) status = 'out of gas' output = None finally: if status == 'valid': logger.debug('TX FINISHED (gas_remained: {})'.format(gas_remained)) # Add parsed transaction to message-type–specific table. bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'contract_id': contract_id, 'gasprice': gasprice, 'startgas': startgas, 'gas_cost': gas_cost, 'gas_remained': gas_remained, 'value': value, 'payload': payload, 'output': output, 'status': status } sql='insert into executions values(:tx_index, :tx_hash, :block_index, :source, :contract_id, :gasprice, :startgas, :gas_cost, :gas_remained, :value, :data, :output, :status)' cursor = db.cursor() cursor.execute(sql, bindings)
def apply_transaction(db, tx, block): ### Make fees proportional to money supply. ### # Set initial values. Calculate price multiplier. # Multiply prices by multiplier, then by 100; make global variables. prices = { 'GDEFAULT': 1, 'GMEMORY': 1, 'GSTORAGE': 100, 'GTXDATA': 5, 'GTXCOST': 500 } if config.TESTNET: supply = 2600001 * config.UNIT else: supply = util.xcp_supply(db) MULTIPLIER = fractions.Fraction(supply, 2700000 * config.UNIT) * MULTIPLIER_CONSTANT_FACTOR for key in prices.keys(): prices[key] = fractions.Fraction(prices[key]) * MULTIPLIER prices[key] = math.floor(prices[key].__round__(2)) exec('''global {}; {} = prices['{}']'''.format(key, key, key)) # (3) the gas limit is no smaller than the intrinsic gas, # g0, used by the transaction; intrinsic_gas_used = GTXDATA * len(tx.data) + GTXCOST if tx.startgas < intrinsic_gas_used: raise InsufficientStartGas(tx.startgas, intrinsic_gas_used) # (4) the sender account balance contains at least the # cost, v0, required in up-front payment. total_cost = tx.value + tx.gasprice * tx.startgas if block.get_balance(tx.sender) < total_cost: raise InsufficientBalance(block.get_balance(tx.sender), total_cost) pblogger.log('TX NEW', tx=tx.hex_hash(), tx_dict=tx.to_dict()) # log('TX NEW', tx_dict) # start transacting ################# block.increment_nonce(tx.sender) # buy startgas success = block.transfer_value(tx, tx.sender, None, tx.gasprice * tx.startgas) assert success message_gas = tx.startgas - intrinsic_gas_used message = Message(tx.sender, tx.to, tx.value, message_gas, tx.data) primary_result = None # Postqueue block.postqueue_delete() block.postqueue_insert(message) while block.postqueue_get(): message = block.postqueue_pop() # MESSAGE if tx.to and tx.to != CREATE_CONTRACT_ADDRESS: result, gas_remained, data = apply_msg_send(db, block, tx, message) else: # CREATE result, gas_remained, data = create_contract(db, block, tx, message) if not primary_result: primary_result = result, gas_remained, data result, gas_remained, data = primary_result assert gas_remained >= 0 pblogger.log("TX APPLIED", result=result, gas_remained=gas_remained, data=util.hexlify(bytes(data))) # if pblogger.log_block: # pblogger.log('BLOCK', block=block.to_dict(with_state=True, full_transactions=True)) if not result: # 0 = OOG failure in both cases # pblogger.log('TX FAILED', reason='out of gas', startgas=tx.startgas, gas_remained=gas_remained) output = OUT_OF_GAS else: pblogger.log('TX SUCCESS') assert gas_remained == int(gas_remained) gas_remained = int(gas_remained) # sell remaining gas block.transfer_value( tx, None, tx.sender, tx.gasprice * gas_remained) if tx.to: # output = ''.join(map(chr, data)) output = bytes(data) else: output = result # block.commit_state() # Kill suicidal contract. for s in block.suicides_get(): block.del_account(s) block.suicides_delete() # success = output is not OUT_OF_GAS # return success, output if success else '' if output == OUT_OF_GAS: success = False output = '' else: success = True return success, output, gas_remained
def apply_op(db, block, tx, msg, processed_code, compustate): # Does not include paying opfee. if compustate.pc >= len(processed_code): return [] op, in_args, out_args, mem_grabs, fee, opcode = processed_code[compustate.pc] # print('APPLYING OP', op) # print('INARGS', in_args) # print('COMPUSTATE.STACK', compustate.stack) # empty stack error if in_args > len(compustate.stack): logging.debug('INSUFFICIENT STACK ERROR (op: {}, needed: {}, available: {})'.format(op, in_args, len(compustate.stack))) return [] # out of gas error if fee > compustate.gas: return out_of_gas_exception('base_gas', fee, compustate, op) pblogger.log('STK', stk=list(reversed(compustate.stack))) for i in range(0, len(compustate.memory), 16): memblk = compustate.memory[i:i+16] # logging.debug('MEM {}'.format(memprint(memblk))) # logging.debug('\tSTORAGE\n\t\t' + '\n\t\t'.join(['{}: {}'.format(utils.hexprint(storage['key']), utils.hexprint(storage['value'])) for storage in block.get_storage_data(msg.to)])) # Log operation log_args = dict(pc=str(compustate.pc), op=op, stackargs=compustate.stack[-1:-in_args-1:-1], # stack=list(reversed(compustate.stack)), gas=compustate.gas) if op[:4] == 'PUSH': ind = compustate.pc + 1 log_args['value'] = \ utils.bytearray_to_int([x[-1] for x in processed_code[ind: ind + int(op[4:])]]) elif op == 'CALLDATACOPY': log_args['data'] = binascii.hexlify(msg.data) # log('OP', log_args) pblogger.log('OP', **log_args) # Apply operation compustate.gas -= fee compustate.pc += 1 stk = compustate.stack mem = compustate.memory if op == 'STOP' or op == 'INVALID': return [] elif op == 'ADD': stk.append((stk.pop() + stk.pop()) % TT256) elif op == 'SUB': stk.append((stk.pop() - stk.pop()) % TT256) elif op == 'MUL': stk.append((stk.pop() * stk.pop()) % TT256) elif op == 'DIV': s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 // s1) elif op == 'MOD': s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 % s1) elif op == 'SDIV': s0, s1 = to_signed(stk.pop()), to_signed(stk.pop()) stk.append(0 if s1 == 0 else (s0 // s1) % TT256) elif op == 'SMOD': s0, s1 = to_signed(stk.pop()), to_signed(stk.pop()) stk.append(0 if s1 == 0 else (s0 % s1) % TT256) elif op == 'EXP': stk.append(pow(stk.pop(), stk.pop(), TT256)) elif op == 'NEG': stk.append(-stk.pop() % TT256) elif op == 'LT': stk.append(1 if stk.pop() < stk.pop() else 0) elif op == 'GT': stk.append(1 if stk.pop() > stk.pop() else 0) elif op == 'SLT': s0, s1 = to_signed(stk.pop()), to_signed(stk.pop()) stk.append(1 if s0 < s1 else 0) elif op == 'SGT': s0, s1 = to_signed(stk.pop()), to_signed(stk.pop()) stk.append(1 if s0 > s1 else 0) elif op == 'EQ': stk.append(1 if stk.pop() == stk.pop() else 0) elif op == 'NOT': stk.append(0 if stk.pop() else 1) elif op == 'AND': stk.append(stk.pop() & stk.pop()) elif op == 'OR': stk.append(stk.pop() | stk.pop()) elif op == 'XOR': stk.append(stk.pop() ^ stk.pop()) elif op == 'BYTE': s0, s1 = stk.pop(), stk.pop() if s0 >= 32: stk.append(0) else: stk.append((s1 // 256 ** (31 - s0)) % 256) elif op == 'ADDMOD': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 + s1) % s2 if s2 else 0) elif op == 'MULMOD': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 * s1) % s2 if s2 else 0) elif op == 'SHA3': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + s1): return OUT_OF_GAS data = bytes(mem[s0: s0 + s1]) stk.append(rlp.big_endian_to_int(utils.sha3(data))) elif op == 'ADDRESS': stk.append(utils.coerce_to_int(msg.to)) elif op == 'BALANCE': addr = stk.pop() addr = utils.coerce_to_hex(addr) stk.append(block.get_balance(addr)) elif op == 'ASSET_BALANCE': addr, asset_id = stk.pop(), stk.pop() addr = utils.coerce_to_hex(addr) asset_name = util.asset_name(asset_id) stk.append(block.get_balance(addr, asset=asset_name)) elif op == 'SEND': # TODO: You can’t send BTC to a contract address. addr, quantity, asset_id = stk.pop(), stk.pop(), stk.pop() asset_name = util.asset_name(asset_id) # TODO: Check balance first. block.transfer_value(tx, msg.to, addr, quantity, asset=asset_name) elif op == 'ORIGIN': stk.append(utils.coerce_to_int(tx.sender)) elif op == 'CALLER': stk.append(utils.coerce_to_int(msg.sender)) elif op == 'CALLVALUE': stk.append(msg.value) elif op == 'CALLDATALOAD': s0 = stk.pop() if s0 >= len(msg.data): stk.append(0) else: dat = msg.data[s0: s0 + 32] stk.append(rlp.big_endian_to_int(dat + b'\x00' * (32 - len(dat)))) elif op == 'CALLDATASIZE': stk.append(len(msg.data)) elif op == 'CALLDATACOPY': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + s2): return OUT_OF_GAS for i in range(s2): if s1 + i < len(msg.data): mem[s0 + i] = ord(msg.data[s1 + i]) else: mem[s0 + i] = 0 elif op == 'GASPRICE': stk.append(tx.gasprice) elif op == 'CODECOPY': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + s2): return OUT_OF_GAS for i in range(s2): if s1 + i < len(processed_code): mem[s0 + i] = processed_code[s1 + i][-1] else: mem[s0 + i] = 0 elif op == 'EXTCODESIZE': stk.append(len(block.get_code(stk.pop()) or '')) elif op == 'EXTCODECOPY': addr, s1, s2, s3 = stk.pop(), stk.pop(), stk.pop(), stk.pop() extcode = block.get_code(addr) or '' if not mem_extend(mem, compustate, op, s1 + s3): return OUT_OF_GAS for i in range(s3): if s2 + i < len(extcode): mem[s1 + i] = ord(extcode[s2 + i]) else: mem[s1 + i] = 0 elif op == 'PREVHASH': stk.append(rlp.big_endian_to_int(block.prevhash)) # elif op == 'COINBASE': # stk.append(rlp.big_endian_to_int(binascii.unhexlify(block.coinbase))) elif op == 'TIMESTAMP': stk.append(block.timestamp) elif op == 'NUMBER': stk.append(block.number) elif op == 'DIFFICULTY': stk.append(block.difficulty) # elif op == 'GASLIMIT': # stk.append(block.gas_limit) elif op == 'POP': stk.pop() elif op == 'MLOAD': s0 = stk.pop() if not mem_extend(mem, compustate, op, s0 + 32): return OUT_OF_GAS data = bytes(mem[s0: s0 + 32]) stk.append(rlp.big_endian_to_int(data)) elif op == 'MSTORE': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + 32): return OUT_OF_GAS v = s1 for i in range(31, -1, -1): mem[s0 + i] = v % 256 v //= 256 elif op == 'MSTORE8': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + 1): return OUT_OF_GAS mem[s0] = s1 % 256 elif op == 'SLOAD': stk.append(block.get_storage_data(msg.to, stk.pop())) elif op == 'SSTORE': s0, s1 = stk.pop(), stk.pop() pre_occupied = GSTORAGE if block.get_storage_data(msg.to, s0) else 0 post_occupied = GSTORAGE if s1 else 0 gascost = GSTORAGE + post_occupied - pre_occupied if compustate.gas < gascost: out_of_gas_exception('sstore trie expansion', gascost, compustate, op) compustate.gas -= gascost block.set_storage_data(msg.to, s0, s1) elif op == 'JUMP': compustate.pc = stk.pop() elif op == 'JUMPI': s0, s1 = stk.pop(), stk.pop() if s1: compustate.pc = s0 elif op == 'PC': stk.append(compustate.pc) elif op == 'MSIZE': stk.append(len(mem)) elif op == 'GAS': stk.append(compustate.gas) # AFTER subtracting cost 1 elif op[:4] == 'PUSH': pushnum = int(op[4:]) dat = [x[-1] for x in processed_code[compustate.pc: compustate.pc + pushnum]] compustate.pc += pushnum stk.append(utils.bytearray_to_int(dat)) elif op[:3] == 'DUP': depth = int(op[3:]) # DUP POP POP Debug hint is_debug = 1 for i in range(depth): if compustate.pc + i < len(processed_code) and \ processed_code[compustate.pc + i][0] != 'POP': is_debug = 0 break if is_debug: stackargs = [stk.pop() for i in range(depth)] stk.extend(reversed(stackargs)) stk.append(stackargs[-1]) else: stk.append(stk[-depth]) elif op[:4] == 'SWAP': depth = int(op[4:]) temp = stk[-depth-1] stk[-depth-1] = stk[-1] stk[-1] = temp elif op == 'CREATE': value, mstart, msz = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart + msz): return OUT_OF_GAS data = bytes(mem[mstart: mstart + msz]) # log('SUB CONTRACT NEW', {'sender': msg.to, 'value': value, 'data': util.hexlify(data)}) pblogger.log('SUB CONTRACT NEW', sender=msg.to, value=value, data=util.hexlify(data)) create_msg = Message(msg.to, '', value, compustate.gas, data) address, gas, code = create_contract(db, block, tx, create_msg) # log('SUB CONTRACT OUT', {'address': address, 'code': block.get_code(address)}) addr = utils.coerce_to_int(address) pblogger.log('SUB CONTRACT OUT', address=addr, code=code) if addr: stk.append(addr) compustate.gas = gas else: stk.append(0) compustate.gas = 0 elif op == 'CALL': # TODO: Check that this allows for the sending of XCP to Counterparty addresses, as well as contract addresses. gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() new_memsize = max(meminstart + meminsz, memoutstart + memoutsz) if not mem_extend(mem, compustate, op, new_memsize): return OUT_OF_GAS if compustate.gas < gas: return out_of_gas_exception('subcall gas', gas, compustate, op) compustate.gas -= gas to = utils.encode_int(to) to = util.hexlify(((b'\x00' * (32 - len(to))) + to)[12:]) data = bytes(mem[meminstart: meminstart + meminsz]) # log('SUB CALL NEW', {'sender': msg.to, 'to': to, 'value': value, 'gas': gas, 'data': util.hexlify(data)}) pblogger.log('SUB CALL NEW', sender=msg.to, to=to, value=value, gas=gas, data=util.hexlify(data)) call_msg = Message(msg.to, to, value, gas, data) result, gas, data = apply_msg_send(db, block, tx, call_msg) # log('SUB CALL OUT', {'result': result, 'data': data, 'length': data, 'expected': memoutsz}) pblogger.log('SUB CALL OUT', result=result, data=data, length=len(data), expected=memoutsz) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] elif op == 'RETURN': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + s1): return OUT_OF_GAS return mem[s0: s0 + s1] elif op == 'POST': gas, to, value, meminstart, meminsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, meminstart + meminsz): return OUT_OF_GAS if compustate.gas < gas: return out_of_gas_exception('subcall gas', gas, compustate, op) compustate.gas -= gas to = utils.encode_int(to) to = util.hexlify(((b'\x00' * (32 - len(to))) + to)[12:]) data = bytes(mem[meminstart: meminstart + meminsz]) post_dict = {'sender': msg.to, 'to': to, 'value': value, 'gas': gas, 'data': util.hexlify(data)} log('POST NEW', post_dict) post_msg = Message(msg.to, to, value, gas, data) block.postqueue_append(post_msg) elif op == 'CALL_STATELESS': gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() new_memsize = max(meminstart + meminsz, memoutstart + memoutsz) if not mem_extend(mem, compustate, op, new_memsize): return OUT_OF_GAS if compustate.gas < gas: return out_of_gas_exception('subcall gas', gas, compustate, op) compustate.gas -= gas to = utils.encode_int(to) to = util.hexlify(((b'\x00' * (32 - len(to))) + to)[12:]) data = bytes(mem[meminstart: meminstart + meminsz]) # logging.debug('SUB CALL NEW (sender: {}, to: {}, value: {}, gas: {}, data: {})'.format(msg.to, to, value, gas, util.hexlify(data))) pblogger.log('SUB CALL NEW', sender=msg.to, to=msg.to, value=value, gas=gas, data=util.hexlify(data)) call_msg = Message(msg.to, msg.to, value, gas, data) result, gas, data = apply_msg(db, block, tx, call_msg, block.get_code(to)) # logging.debug('SUB CALL OUT (result: {}, data: {}, length: {}, expected: {}'.format(result, data, len(data), memoutsz)) pblogger.log('SUB CALL OUT', result=result, data=data, length=len(data), expected=memoutsz) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] elif op == 'SUICIDE': to = utils.encode_int(stk.pop()) to = binascii.hexlify(((b'\x00' * (32 - len(to))) + to)[12:]) block.transfer_value(tx, msg.to, to, block.get_balance(msg.to)) block.suicides_append(msg.to) return [] for a in stk: assert isinstance(a, int)
def set_options( data_dir=None, backend_rpc_connect=None, backend_rpc_port=None, backend_rpc_user=None, backend_rpc_password=None, backend_rpc_ssl=False, backend_rpc_ssl_verify=True, blockchain_service_name=None, blockchain_service_connect=None, rpc_host=None, rpc_port=None, rpc_user=None, rpc_password=None, rpc_allow_cors=None, log_file=None, config_file=None, database_file=None, testnet=False, testcoin=False, force=False, broadcast_tx_mainnet=None, backend_poll_interval=None): if force: config.FORCE = force else: config.FORCE = False # Data directory if not data_dir: config.DATA_DIR = appdirs.user_config_dir(appauthor=config.XCP_NAME, appname=config.XCP_CLIENT, roaming=True) else: config.DATA_DIR = os.path.expanduser(data_dir) if not os.path.isdir(config.DATA_DIR): os.mkdir(config.DATA_DIR) # Configuration file config_file_changed = False configfile = configparser.ConfigParser() if config_file: config_path = config_file else: config_path = os.path.join(config.DATA_DIR, '{}.conf'.format(config.XCP_CLIENT)) configfile.read(config_path) if not 'Default' in configfile: configfile['Default'] = {} # testnet if testnet: config.TESTNET = testnet elif 'testnet' in configfile['Default']: config.TESTNET = configfile['Default'].getboolean('testnet') else: config.TESTNET = False # testcoin if testcoin: config.TESTCOIN = testcoin elif 'testcoin' in configfile['Default']: config.TESTCOIN = configfile['Default'].getboolean('testcoin') else: config.TESTCOIN = False ############## # THINGS WE CONNECT TO # Backend RPC host (Bitcoin Core) if backend_rpc_connect: config.BACKEND_RPC_CONNECT = backend_rpc_connect elif 'backend-rpc-connect' in configfile['Default'] and configfile['Default']['backend-rpc-connect']: config.BACKEND_RPC_CONNECT = configfile['Default']['backend-rpc-connect'] elif 'bitcoind-rpc-connect' in configfile['Default'] and configfile['Default']['bitcoind-rpc-connect']: config.BACKEND_RPC_CONNECT = configfile['Default']['bitcoind-rpc-connect'] else: config.BACKEND_RPC_CONNECT = 'localhost' # Backend Core RPC port (Bitcoin Core) if backend_rpc_port: config.BACKEND_RPC_PORT = backend_rpc_port elif 'backend-rpc-port' in configfile['Default'] and configfile['Default']['backend-rpc-port']: config.BACKEND_RPC_PORT = configfile['Default']['backend-rpc-port'] elif 'bitcoind-rpc-port' in configfile['Default'] and configfile['Default']['bitcoind-rpc-port']: config.BACKEND_RPC_PORT = configfile['Default']['bitcoind-rpc-port'] else: if config.TESTNET: config.BACKEND_RPC_PORT = config.DEFAULT_BACKEND_RPC_PORT_TESTNET else: config.BACKEND_RPC_PORT = config.DEFAULT_BACKEND_RPC_PORT try: config.BACKEND_RPC_PORT = int(config.BACKEND_RPC_PORT) if not (int(config.BACKEND_RPC_PORT) > 1 and int(config.BACKEND_RPC_PORT) < 65535): raise ConfigurationError('invalid backend API port number') except: raise Exception("Please specific a valid port number backend-rpc-port configuration parameter") # Backend Core RPC user (Bitcoin Core) if backend_rpc_user: config.BACKEND_RPC_USER = backend_rpc_user elif 'backend-rpc-user' in configfile['Default'] and configfile['Default']['backend-rpc-user']: config.BACKEND_RPC_USER = configfile['Default']['backend-rpc-user'] elif 'bitcoind-rpc-user' in configfile['Default'] and configfile['Default']['bitcoind-rpc-user']: config.BACKEND_RPC_USER = configfile['Default']['bitcoind-rpc-user'] else: config.BACKEND_RPC_USER = '******' # Backend Core RPC password (Bitcoin Core) if backend_rpc_password: config.BACKEND_RPC_PASSWORD = backend_rpc_password elif 'backend-rpc-password' in configfile['Default'] and configfile['Default']['backend-rpc-password']: config.BACKEND_RPC_PASSWORD = configfile['Default']['backend-rpc-password'] elif 'bitcoind-rpc-password' in configfile['Default'] and configfile['Default']['bitcoind-rpc-password']: config.BACKEND_RPC_PASSWORD = configfile['Default']['bitcoind-rpc-password'] else: raise ConfigurationError('backend RPC password not set. (Use configuration file or --backend-rpc-password=PASSWORD)') # Backend Core RPC SSL if backend_rpc_ssl: config.BACKEND_RPC_SSL = backend_rpc_ssl elif 'backend-rpc-ssl' in configfile['Default'] and configfile['Default']['backend-rpc-ssl']: config.BACKEND_RPC_SSL = configfile['Default']['backend-rpc-ssl'] else: config.BACKEND_RPC_SSL = False # Default to off. # Backend Core RPC SSL Verify if backend_rpc_ssl_verify: config.BACKEND_RPC_SSL_VERIFY = backend_rpc_ssl_verify elif 'backend-rpc-ssl-verify' in configfile['Default'] and configfile['Default']['backend-rpc-ssl-verify']: config.BACKEND_RPC_SSL_VERIFY = configfile['Default']['backend-rpc-ssl-verify'] else: config.BACKEND_RPC_SSL_VERIFY = False # Default to off (support self‐signed certificates) # Backend Poll Interval if backend_poll_interval: config.BACKEND_POLL_INTERVAL = backend_poll_interval elif 'backend-poll-interval' in configfile['Default'] and configfile['Default']['backend-poll-interval']: config.BACKEND_POLL_INTERVAL = configfile['Default']['backend-poll-interval'] else: config.BACKEND_POLL_INTERVAL = 2.0 # Construct backend URL. config.BACKEND_RPC = config.BACKEND_RPC_USER + ':' + config.BACKEND_RPC_PASSWORD + '@' + config.BACKEND_RPC_CONNECT + ':' + str(config.BACKEND_RPC_PORT) if config.BACKEND_RPC_SSL: config.BACKEND_RPC = 'https://' + config.BACKEND_RPC else: config.BACKEND_RPC = 'http://' + config.BACKEND_RPC # blockchain service name if blockchain_service_name: config.BLOCKCHAIN_SERVICE_NAME = blockchain_service_name elif 'blockchain-service-name' in configfile['Default'] and configfile['Default']['blockchain-service-name']: config.BLOCKCHAIN_SERVICE_NAME = configfile['Default']['blockchain-service-name'] else: config.BLOCKCHAIN_SERVICE_NAME = 'jmcorgan' # custom blockchain service API endpoint # leave blank to use the default. if specified, include the scheme prefix and port, without a trailing slash (e.g. http://localhost:3001) if blockchain_service_connect: config.BLOCKCHAIN_SERVICE_CONNECT = blockchain_service_connect elif 'blockchain-service-connect' in configfile['Default'] and configfile['Default']['blockchain-service-connect']: config.BLOCKCHAIN_SERVICE_CONNECT = configfile['Default']['blockchain-service-connect'] else: config.BLOCKCHAIN_SERVICE_CONNECT = None #use default specified by the library ############## # THINGS WE SERVE # counterpartyd API RPC host if rpc_host: config.RPC_HOST = rpc_host elif 'rpc-host' in configfile['Default'] and configfile['Default']['rpc-host']: config.RPC_HOST = configfile['Default']['rpc-host'] else: config.RPC_HOST = 'localhost' # counterpartyd API RPC port if rpc_port: config.RPC_PORT = rpc_port elif 'rpc-port' in configfile['Default'] and configfile['Default']['rpc-port']: config.RPC_PORT = configfile['Default']['rpc-port'] else: if config.TESTNET: if config.TESTCOIN: config.RPC_PORT = config.DEFAULT_RPC_PORT_TESTNET + 1 else: config.RPC_PORT = config.DEFAULT_RPC_PORT_TESTNET else: if config.TESTCOIN: config.RPC_PORT = config.DEFAULT_RPC_PORT + 1 else: config.RPC_PORT = config.DEFAULT_RPC_PORT try: config.RPC_PORT = int(config.RPC_PORT) if not (int(config.BACKEND_RPC_PORT) > 1 and int(config.BACKEND_RPC_PORT) < 65535): raise ConfigurationError('invalid counterpartyd API port number') except: raise Exception("Please specific a valid port number rpc-port configuration parameter") # counterpartyd API RPC user if rpc_user: config.RPC_USER = rpc_user elif 'rpc-user' in configfile['Default'] and configfile['Default']['rpc-user']: config.RPC_USER = configfile['Default']['rpc-user'] else: config.RPC_USER = '******' # counterpartyd API RPC password if rpc_password: config.RPC_PASSWORD = rpc_password elif 'rpc-password' in configfile['Default'] and configfile['Default']['rpc-password']: config.RPC_PASSWORD = configfile['Default']['rpc-password'] else: config_file_changed = True config.RPC_PASSWORD = util.hexlify(util.dhash(os.urandom(16))) configfile['Default']['rpc-password'] = config.RPC_PASSWORD logger.info('Generated password for counterpartyd RPC API: {}'.format(config.RPC_PASSWORD)) logger.info('Saved in configuration file: {}'.format(config_file)) # raise ConfigurationError('RPC password not set. (Use configuration file or --rpc-password=PASSWORD)') config.RPC = 'http://' + config.RPC_USER + ':' + config.RPC_PASSWORD + '@' + config.RPC_HOST + ':' + str(config.RPC_PORT) # RPC CORS if rpc_allow_cors: config.RPC_ALLOW_CORS = rpc_allow_cors elif 'rpc-allow-cors' in configfile['Default'] and configfile['Default']['rpc-allow-cors']: config.RPC_ALLOW_CORS = configfile['Default'].getboolean('rpc-allow-cors') else: config.RPC_ALLOW_CORS = True ############## # OTHER SETTINGS # Log if log_file: config.LOG = log_file elif 'log-file' in configfile['Default'] and configfile['Default']['log-file']: config.LOG = configfile['Default']['log-file'] else: string = config.XCP_CLIENT if config.TESTNET: string += '.testnet' if config.TESTCOIN: string += '.testcoin' config.LOG = os.path.join(config.DATA_DIR, string + '.log') # Encoding if config.TESTCOIN: config.PREFIX = b'XX' # 2 bytes (possibly accidentally created) else: config.PREFIX = b'CNTRPRTY' # 8 bytes # Database if database_file: config.DATABASE = database_file elif 'database-file' in configfile['Default'] and configfile['Default']['database-file']: config.DATABASE = configfile['Default']['database-file'] else: string = '{}.'.format(config.XCP_CLIENT) + str(config.VERSION_MAJOR) if config.TESTNET: string += '.testnet' if config.TESTCOIN: string += '.testcoin' config.DATABASE = os.path.join(config.DATA_DIR, string + '.db') # (more) Testnet if config.TESTNET: config.MAGIC_BYTES = config.MAGIC_BYTES_TESTNET if config.TESTCOIN: config.ADDRESSVERSION = config.ADDRESSVERSION_TESTNET config.BLOCK_FIRST = config.BLOCK_FIRST_TESTNET_TESTCOIN config.BURN_START = config.BURN_START_TESTNET_TESTCOIN config.BURN_END = config.BURN_END_TESTNET_TESTCOIN config.UNSPENDABLE = config.UNSPENDABLE_TESTNET else: config.ADDRESSVERSION = config.ADDRESSVERSION_TESTNET config.BLOCK_FIRST = config.BLOCK_FIRST_TESTNET config.BURN_START = config.BURN_START_TESTNET config.BURN_END = config.BURN_END_TESTNET config.UNSPENDABLE = config.UNSPENDABLE_TESTNET else: config.MAGIC_BYTES = config.MAGIC_BYTES_MAINNET if config.TESTCOIN: config.ADDRESSVERSION = config.ADDRESSVERSION_MAINNET config.BLOCK_FIRST = config.BLOCK_FIRST_MAINNET_TESTCOIN config.BURN_START = config.BURN_START_MAINNET_TESTCOIN config.BURN_END = config.BURN_END_MAINNET_TESTCOIN config.UNSPENDABLE = config.UNSPENDABLE_MAINNET else: config.ADDRESSVERSION = config.ADDRESSVERSION_MAINNET config.BLOCK_FIRST = config.BLOCK_FIRST_MAINNET config.BURN_START = config.BURN_START_MAINNET config.BURN_END = config.BURN_END_MAINNET config.UNSPENDABLE = config.UNSPENDABLE_MAINNET # method used to broadcast signed transactions. bitcoind or bci (default: bitcoind) if broadcast_tx_mainnet: config.BROADCAST_TX_MAINNET = broadcast_tx_mainnet elif 'broadcast-tx-mainnet' in configfile['Default']: config.BROADCAST_TX_MAINNET = configfile['Default']['broadcast-tx-mainnet'] else: config.BROADCAST_TX_MAINNET = '{}'.format(config.BTC_CLIENT) # Save generated settings. if config_file_changed: with open(config_path, 'w') as f: configfile.write(f)
def parse (db, tx, message): if not config.TESTNET: # TODO return status = 'valid' output, gas_cost, gas_remained = None, None, None try: # TODO: Use unpack function. # Unpack message. curr_format = FORMAT + '{}s'.format(len(message) - LENGTH) try: contract_id, gasprice, startgas, value, payload = struct.unpack(curr_format, message) if gasprice > config.MAX_INT or startgas > config.MAX_INT: # TODO: define max for gasprice and startgas raise exceptions.UnpackError() except (struct.error) as e: raise exceptions.UnpackError() gas_remained = startgas contract_id = util.hexlify(contract_id) if contract_id == '0000000000000000000000000000000000000000': contract_id = '' # ‘Apply transaction’! tx_obj = Transaction(tx, contract_id, gasprice, startgas, value, payload) block_obj = blocks.Block(db, tx['block_hash']) success, output, gas_remained = processblock.apply_transaction(db, tx_obj, block_obj) gas_cost = gasprice * (startgas - gas_remained) # different definition from pyethereum’s except exceptions.UnpackError as e: contract_id, gasprice, startgas, value, payload = None, None, None, None, None status = 'invalid: could not unpack' output = None except processblock.ContractError as e: status = 'invalid: no such contract' contract_id = None output = None except processblock.InsufficientStartGas as e: have, need = e.args logging.debug('Insufficient start gas: have {} and need {}'.format(have, need)) status = 'invalid: insufficient start gas' output = None except processblock.InsufficientBalance as e: have, need = e.args logging.debug('Insufficient balance: have {} and need {}'.format(have, need)) status = 'invalid: insufficient balance' output = None except processblock.OutOfGas as e: logging.debug('TX OUT_OF_GAS (startgas: {}, gas_remained: {})'.format(startgas, gas_remained)) status = 'out of gas' output = None finally: if status == 'valid': logging.debug('TX FINISHED (gas_remained: {})'.format(gas_remained)) # Add parsed transaction to message-type–specific table. bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'contract_id': contract_id, 'gasprice': gasprice, 'startgas': startgas, 'gas_cost': gas_cost, 'gas_remained': gas_remained, 'value': value, 'payload': payload, 'output': output, 'status': status } sql='insert into executions values(:tx_index, :tx_hash, :block_index, :source, :contract_id, :gasprice, :startgas, :gas_cost, :gas_remained, :value, :data, :output, :status)' cursor = db.cursor() cursor.execute(sql, bindings)
def get_tx_info(tx_hex): source, destination, btc_amount, fee, data = blocks.get_tx_info( self.proxy, tx_hex, util.last_block(db)['block_index']) return source, destination, btc_amount, fee, util.hexlify(data)
def apply_op(db, block, tx, msg, processed_code, compustate): # Does not include paying opfee. if compustate.pc >= len(processed_code): return [] op, in_args, out_args, mem_grabs, fee, opcode = processed_code[ compustate.pc] # print('APPLYING OP', op) # print('INARGS', in_args) # print('COMPUSTATE.STACK', compustate.stack) # empty stack error if in_args > len(compustate.stack): logging.debug( 'INSUFFICIENT STACK ERROR (op: {}, needed: {}, available: {})'. format(op, in_args, len(compustate.stack))) return [] # out of gas error if fee > compustate.gas: return out_of_gas_exception('base_gas', fee, compustate, op) pblogger.log('STK', stk=list(reversed(compustate.stack))) for i in range(0, len(compustate.memory), 16): memblk = compustate.memory[i:i + 16] # logging.debug('MEM {}'.format(memprint(memblk))) # logging.debug('\tSTORAGE\n\t\t' + '\n\t\t'.join(['{}: {}'.format(utils.hexprint(storage['key']), utils.hexprint(storage['value'])) for storage in block.get_storage_data(msg.to)])) # Log operation log_args = dict( pc=str(compustate.pc), op=op, stackargs=compustate.stack[-1:-in_args - 1:-1], # stack=list(reversed(compustate.stack)), gas=compustate.gas) if op[:4] == 'PUSH': ind = compustate.pc + 1 log_args['value'] = \ utils.bytearray_to_int([x[-1] for x in processed_code[ind: ind + int(op[4:])]]) elif op == 'CALLDATACOPY': log_args['data'] = binascii.hexlify(msg.data) # log('OP', log_args) pblogger.log('OP', **log_args) # Apply operation compustate.gas -= fee compustate.pc += 1 stk = compustate.stack mem = compustate.memory if op == 'STOP' or op == 'INVALID': return [] elif op == 'ADD': stk.append((stk.pop() + stk.pop()) % TT256) elif op == 'SUB': stk.append((stk.pop() - stk.pop()) % TT256) elif op == 'MUL': stk.append((stk.pop() * stk.pop()) % TT256) elif op == 'DIV': s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 // s1) elif op == 'MOD': s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 % s1) elif op == 'SDIV': s0, s1 = to_signed(stk.pop()), to_signed(stk.pop()) stk.append(0 if s1 == 0 else (s0 // s1) % TT256) elif op == 'SMOD': s0, s1 = to_signed(stk.pop()), to_signed(stk.pop()) stk.append(0 if s1 == 0 else (s0 % s1) % TT256) elif op == 'EXP': stk.append(pow(stk.pop(), stk.pop(), TT256)) elif op == 'NEG': stk.append(-stk.pop() % TT256) elif op == 'LT': stk.append(1 if stk.pop() < stk.pop() else 0) elif op == 'GT': stk.append(1 if stk.pop() > stk.pop() else 0) elif op == 'SLT': s0, s1 = to_signed(stk.pop()), to_signed(stk.pop()) stk.append(1 if s0 < s1 else 0) elif op == 'SGT': s0, s1 = to_signed(stk.pop()), to_signed(stk.pop()) stk.append(1 if s0 > s1 else 0) elif op == 'EQ': stk.append(1 if stk.pop() == stk.pop() else 0) elif op == 'NOT': stk.append(0 if stk.pop() else 1) elif op == 'AND': stk.append(stk.pop() & stk.pop()) elif op == 'OR': stk.append(stk.pop() | stk.pop()) elif op == 'XOR': stk.append(stk.pop() ^ stk.pop()) elif op == 'BYTE': s0, s1 = stk.pop(), stk.pop() if s0 >= 32: stk.append(0) else: stk.append((s1 // 256**(31 - s0)) % 256) elif op == 'ADDMOD': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 + s1) % s2 if s2 else 0) elif op == 'MULMOD': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 * s1) % s2 if s2 else 0) elif op == 'SHA3': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + s1): return OUT_OF_GAS data = bytes(mem[s0:s0 + s1]) stk.append(rlp.big_endian_to_int(utils.sha3(data))) elif op == 'ADDRESS': stk.append(utils.coerce_to_int(msg.to)) elif op == 'BALANCE': addr = stk.pop() addr = utils.coerce_to_hex(addr) stk.append(block.get_balance(addr)) elif op == 'ASSET_BALANCE': addr, asset_id = stk.pop(), stk.pop() addr = utils.coerce_to_hex(addr) asset_name = util.asset_name(asset_id) stk.append(block.get_balance(addr, asset=asset_name)) elif op == 'SEND': # TODO: You can’t send BTC to a contract address. addr, quantity, asset_id = stk.pop(), stk.pop(), stk.pop() asset_name = util.asset_name(asset_id) # TODO: Check balance first. block.transfer_value(tx, msg.to, addr, quantity, asset=asset_name) elif op == 'ORIGIN': stk.append(utils.coerce_to_int(tx.sender)) elif op == 'CALLER': stk.append(utils.coerce_to_int(msg.sender)) elif op == 'CALLVALUE': stk.append(msg.value) elif op == 'CALLDATALOAD': s0 = stk.pop() if s0 >= len(msg.data): stk.append(0) else: dat = msg.data[s0:s0 + 32] stk.append(rlp.big_endian_to_int(dat + b'\x00' * (32 - len(dat)))) elif op == 'CALLDATASIZE': stk.append(len(msg.data)) elif op == 'CALLDATACOPY': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + s2): return OUT_OF_GAS for i in range(s2): if s1 + i < len(msg.data): mem[s0 + i] = ord(msg.data[s1 + i]) else: mem[s0 + i] = 0 elif op == 'GASPRICE': stk.append(tx.gasprice) elif op == 'CODECOPY': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + s2): return OUT_OF_GAS for i in range(s2): if s1 + i < len(processed_code): mem[s0 + i] = processed_code[s1 + i][-1] else: mem[s0 + i] = 0 elif op == 'EXTCODESIZE': stk.append(len(block.get_code(stk.pop()) or '')) elif op == 'EXTCODECOPY': addr, s1, s2, s3 = stk.pop(), stk.pop(), stk.pop(), stk.pop() extcode = block.get_code(addr) or '' if not mem_extend(mem, compustate, op, s1 + s3): return OUT_OF_GAS for i in range(s3): if s2 + i < len(extcode): mem[s1 + i] = ord(extcode[s2 + i]) else: mem[s1 + i] = 0 elif op == 'PREVHASH': stk.append(rlp.big_endian_to_int(block.prevhash)) # elif op == 'COINBASE': # stk.append(rlp.big_endian_to_int(binascii.unhexlify(block.coinbase))) elif op == 'TIMESTAMP': stk.append(block.timestamp) elif op == 'NUMBER': stk.append(block.number) elif op == 'DIFFICULTY': stk.append(block.difficulty) # elif op == 'GASLIMIT': # stk.append(block.gas_limit) elif op == 'POP': stk.pop() elif op == 'MLOAD': s0 = stk.pop() if not mem_extend(mem, compustate, op, s0 + 32): return OUT_OF_GAS data = bytes(mem[s0:s0 + 32]) stk.append(rlp.big_endian_to_int(data)) elif op == 'MSTORE': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + 32): return OUT_OF_GAS v = s1 for i in range(31, -1, -1): mem[s0 + i] = v % 256 v //= 256 elif op == 'MSTORE8': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + 1): return OUT_OF_GAS mem[s0] = s1 % 256 elif op == 'SLOAD': stk.append(block.get_storage_data(msg.to, stk.pop())) elif op == 'SSTORE': s0, s1 = stk.pop(), stk.pop() pre_occupied = GSTORAGE if block.get_storage_data(msg.to, s0) else 0 post_occupied = GSTORAGE if s1 else 0 gascost = GSTORAGE + post_occupied - pre_occupied if compustate.gas < gascost: out_of_gas_exception('sstore trie expansion', gascost, compustate, op) compustate.gas -= gascost block.set_storage_data(msg.to, s0, s1) elif op == 'JUMP': compustate.pc = stk.pop() elif op == 'JUMPI': s0, s1 = stk.pop(), stk.pop() if s1: compustate.pc = s0 elif op == 'PC': stk.append(compustate.pc) elif op == 'MSIZE': stk.append(len(mem)) elif op == 'GAS': stk.append(compustate.gas) # AFTER subtracting cost 1 elif op[:4] == 'PUSH': pushnum = int(op[4:]) dat = [ x[-1] for x in processed_code[compustate.pc:compustate.pc + pushnum] ] compustate.pc += pushnum stk.append(utils.bytearray_to_int(dat)) elif op[:3] == 'DUP': depth = int(op[3:]) # DUP POP POP Debug hint is_debug = 1 for i in range(depth): if compustate.pc + i < len(processed_code) and \ processed_code[compustate.pc + i][0] != 'POP': is_debug = 0 break if is_debug: stackargs = [stk.pop() for i in range(depth)] stk.extend(reversed(stackargs)) stk.append(stackargs[-1]) else: stk.append(stk[-depth]) elif op[:4] == 'SWAP': depth = int(op[4:]) temp = stk[-depth - 1] stk[-depth - 1] = stk[-1] stk[-1] = temp elif op == 'CREATE': value, mstart, msz = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart + msz): return OUT_OF_GAS data = bytes(mem[mstart:mstart + msz]) # log('SUB CONTRACT NEW', {'sender': msg.to, 'value': value, 'data': util.hexlify(data)}) pblogger.log('SUB CONTRACT NEW', sender=msg.to, value=value, data=util.hexlify(data)) create_msg = Message(msg.to, '', value, compustate.gas, data) address, gas, code = create_contract(db, block, tx, create_msg) # log('SUB CONTRACT OUT', {'address': address, 'code': block.get_code(address)}) addr = utils.coerce_to_int(address) pblogger.log('SUB CONTRACT OUT', address=addr, code=code) if addr: stk.append(addr) compustate.gas = gas else: stk.append(0) compustate.gas = 0 elif op == 'CALL': # TODO: Check that this allows for the sending of XCP to Counterparty addresses, as well as contract addresses. gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() new_memsize = max(meminstart + meminsz, memoutstart + memoutsz) if not mem_extend(mem, compustate, op, new_memsize): return OUT_OF_GAS if compustate.gas < gas: return out_of_gas_exception('subcall gas', gas, compustate, op) compustate.gas -= gas to = utils.encode_int(to) to = util.hexlify(((b'\x00' * (32 - len(to))) + to)[12:]) data = bytes(mem[meminstart:meminstart + meminsz]) # log('SUB CALL NEW', {'sender': msg.to, 'to': to, 'value': value, 'gas': gas, 'data': util.hexlify(data)}) pblogger.log('SUB CALL NEW', sender=msg.to, to=to, value=value, gas=gas, data=util.hexlify(data)) call_msg = Message(msg.to, to, value, gas, data) result, gas, data = apply_msg_send(db, block, tx, call_msg) # log('SUB CALL OUT', {'result': result, 'data': data, 'length': data, 'expected': memoutsz}) pblogger.log('SUB CALL OUT', result=result, data=data, length=len(data), expected=memoutsz) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] elif op == 'RETURN': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0 + s1): return OUT_OF_GAS return mem[s0:s0 + s1] elif op == 'POST': gas, to, value, meminstart, meminsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, meminstart + meminsz): return OUT_OF_GAS if compustate.gas < gas: return out_of_gas_exception('subcall gas', gas, compustate, op) compustate.gas -= gas to = utils.encode_int(to) to = util.hexlify(((b'\x00' * (32 - len(to))) + to)[12:]) data = bytes(mem[meminstart:meminstart + meminsz]) post_dict = { 'sender': msg.to, 'to': to, 'value': value, 'gas': gas, 'data': util.hexlify(data) } log('POST NEW', post_dict) post_msg = Message(msg.to, to, value, gas, data) block.postqueue_append(post_msg) elif op == 'CALL_STATELESS': gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() new_memsize = max(meminstart + meminsz, memoutstart + memoutsz) if not mem_extend(mem, compustate, op, new_memsize): return OUT_OF_GAS if compustate.gas < gas: return out_of_gas_exception('subcall gas', gas, compustate, op) compustate.gas -= gas to = utils.encode_int(to) to = util.hexlify(((b'\x00' * (32 - len(to))) + to)[12:]) data = bytes(mem[meminstart:meminstart + meminsz]) # logging.debug('SUB CALL NEW (sender: {}, to: {}, value: {}, gas: {}, data: {})'.format(msg.to, to, value, gas, util.hexlify(data))) pblogger.log('SUB CALL NEW', sender=msg.to, to=msg.to, value=value, gas=gas, data=util.hexlify(data)) call_msg = Message(msg.to, msg.to, value, gas, data) result, gas, data = apply_msg(db, block, tx, call_msg, block.get_code(to)) # logging.debug('SUB CALL OUT (result: {}, data: {}, length: {}, expected: {}'.format(result, data, len(data), memoutsz)) pblogger.log('SUB CALL OUT', result=result, data=data, length=len(data), expected=memoutsz) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] elif op == 'SUICIDE': to = utils.encode_int(stk.pop()) to = binascii.hexlify(((b'\x00' * (32 - len(to))) + to)[12:]) block.transfer_value(tx, msg.to, to, block.get_balance(msg.to)) block.suicides_append(msg.to) return [] for a in stk: assert isinstance(a, int)
def apply_transaction(db, tx, block): ### Make fees proportional to money supply. ### # Set initial values. Calculate price multiplier. # Multiply prices by multiplier, then by 100; make global variables. prices = { 'GDEFAULT': 1, 'GMEMORY': 1, 'GSTORAGE': 100, 'GTXDATA': 5, 'GTXCOST': 500 } if config.TESTNET: supply = 2600001 * config.UNIT else: supply = util.xcp_supply(db) MULTIPLIER = fractions.Fraction( supply, 2700000 * config.UNIT) * MULTIPLIER_CONSTANT_FACTOR for key in prices.keys(): prices[key] = fractions.Fraction(prices[key]) * MULTIPLIER prices[key] = math.floor(prices[key].__round__(2)) exec('''global {}; {} = prices['{}']'''.format(key, key, key)) # (3) the gas limit is no smaller than the intrinsic gas, # g0, used by the transaction; intrinsic_gas_used = GTXDATA * len(tx.data) + GTXCOST if tx.startgas < intrinsic_gas_used: raise InsufficientStartGas(tx.startgas, intrinsic_gas_used) # (4) the sender account balance contains at least the # cost, v0, required in up-front payment. total_cost = tx.value + tx.gasprice * tx.startgas if block.get_balance(tx.sender) < total_cost: raise InsufficientBalance(block.get_balance(tx.sender), total_cost) pblogger.log('TX NEW', tx=tx.hex_hash(), tx_dict=tx.to_dict()) # log('TX NEW', tx_dict) # start transacting ################# block.increment_nonce(tx.sender) # buy startgas success = block.transfer_value(tx, tx.sender, None, tx.gasprice * tx.startgas) assert success message_gas = tx.startgas - intrinsic_gas_used message = Message(tx.sender, tx.to, tx.value, message_gas, tx.data) primary_result = None # Postqueue block.postqueue_delete() block.postqueue_insert(message) while block.postqueue_get(): message = block.postqueue_pop() # MESSAGE if tx.to and tx.to != CREATE_CONTRACT_ADDRESS: result, gas_remained, data = apply_msg_send(db, block, tx, message) else: # CREATE result, gas_remained, data = create_contract( db, block, tx, message) if not primary_result: primary_result = result, gas_remained, data result, gas_remained, data = primary_result assert gas_remained >= 0 pblogger.log("TX APPLIED", result=result, gas_remained=gas_remained, data=util.hexlify(bytes(data))) # if pblogger.log_block: # pblogger.log('BLOCK', block=block.to_dict(with_state=True, full_transactions=True)) if not result: # 0 = OOG failure in both cases # pblogger.log('TX FAILED', reason='out of gas', startgas=tx.startgas, gas_remained=gas_remained) output = OUT_OF_GAS else: pblogger.log('TX SUCCESS') assert gas_remained == int(gas_remained) gas_remained = int(gas_remained) # sell remaining gas block.transfer_value(tx, None, tx.sender, tx.gasprice * gas_remained) if tx.to: # output = ''.join(map(chr, data)) output = bytes(data) else: output = result # block.commit_state() # Kill suicidal contract. for s in block.suicides_get(): block.del_account(s) block.suicides_delete() # success = output is not OUT_OF_GAS # return success, output if success else '' if output == OUT_OF_GAS: success = False output = '' else: success = True return success, output, gas_remained