def render_POST(self, request): """ Creates a nano contract tx and returns it in hexadecimal format. Post data should be a json with the following items: values: List[{'address', 'value'}], with bet address and value fallback_address: if none of the addresses above is the winner, this address can execute the contract oracle_pubkey_hash: oracle's public key hashed oracle_data_id: oracle's id about this nano contract total_value: nano contract total value input_value: amount this wallet should stake in the nano contract :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'POST') try: data = json.loads(request.content.read().decode('utf-8')) except json.JSONDecodeError: return json.dumps({'success': False, 'message': 'Invalid format for post data'}).encode('utf-8') for param in PARAMS_POST: if param not in data: return get_missing_params_msg(param) try: decoded_params = self.decode_post_params(data) except ValueError as e: return json.dumps({'success': False, 'message': e.message}).encode('utf-8') nano_contract = NanoContractMatchValues( decoded_params.oracle_pubkey_hash, decoded_params.min_timestamp, decoded_params.oracle_data_id, decoded_params.value_dict, decoded_params.fallback_address ) tx_outputs = [] tx_outputs.append(TxOutput(decoded_params.total_value, nano_contract.create_output_script())) inputs, total_inputs_amount = self.manager.wallet.get_inputs_from_amount( decoded_params.input_value, self.manager.tx_storage ) change_tx = self.manager.wallet.handle_change_tx(total_inputs_amount, decoded_params.input_value) if change_tx: tx_outputs.append(TxOutput(change_tx.value, P2PKH.create_output_script(change_tx.address))) tx_inputs = [TxInput(txin.tx_id, txin.index, b'') for txin in inputs] tx = Transaction(inputs=tx_inputs, outputs=tx_outputs) ret = {'success': True, 'hex_tx': tx.get_struct().hex()} return json.dumps(ret).encode('utf-8')
def test_match_values(self): pubkey_hash = '6o6ul2c+sqAariBVW+CwNaSJb9w=' pubkey = 'Awmloohhey8WhajdDURgvbk1z3JHX2vxDSBjz9uG9wEp' # ./hathor-cli oracle-encode-data str:some_id int:1543974403 int:100 oracle_data = 'B3NvbWVfaWQEXAcuAwFk' oracle_signature = 'MEYCIQC5cyg1tOY4oyPZ5KY7ugWJGRShrsSPxr8AxxyuvO5PYwIhAOxHBDMid7aRXe' \ '+85rIaDPI2ussIcw54avaFWfT9svSp' address = base58.b58decode(self.get_address(0)) # they should be the same nc = NanoContractMatchValues(base64.b64decode(pubkey_hash), 1543970403, 'some_id'.encode('utf-8'), {address: 100}) script = nc.create_output_script() nc2 = NanoContractMatchValues.parse_script(script) self.assertIsNotNone(nc2) self.assertEqual(json.dumps(nc.to_human_readable()), json.dumps(nc2.to_human_readable())) # if we add some more bytes, parsing should not match script2 = script + b'00' nc3 = NanoContractMatchValues.parse_script(script2) self.assertIsNone(nc3) # test script eval is true input_data = NanoContractMatchValues.create_input_data( base64.b64decode(oracle_data), base64.b64decode(oracle_signature), base64.b64decode(pubkey)) txin = TxInput(b'aa', 0, input_data) spent_tx = Transaction(outputs=[TxOutput(20, script)]) tx = Transaction( outputs=[TxOutput(20, P2PKH.create_output_script(address))]) script_eval(tx, txin, spent_tx)
def render_GET(self, request): """ Get request /wallet/nano-contract/decode/ that returns the tx decoded, if success Expects 'hex_tx' as GET parameter :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'GET') if b'hex_tx' in request.args: requested_decode = request.args[b'hex_tx'][0].decode('utf-8') else: return get_missing_params_msg('hex_tx') pattern = r'[a-fA-F\d]+' if re.match(pattern, requested_decode) and len(requested_decode) % 2 == 0: tx_bytes = bytes.fromhex(requested_decode) try: tx = Transaction.create_from_struct(tx_bytes) except struct.error: data = {'success': False, 'message': 'Invalid transaction'} return json.dumps(data).encode('utf-8') outputs = [] nano_contract = None for _output in tx.outputs: _nano_contract = NanoContractMatchValues.parse_script( _output.script) if _nano_contract: nano_contract = _nano_contract.to_human_readable() nano_contract['value'] = _output.value continue else: outputs.append(_output.to_human_readable()) my_inputs, other_inputs = self.manager.wallet.separate_inputs( tx.inputs, self.manager.tx_storage) my_inputs = [_in.to_human_readable() for _in in my_inputs] other_inputs = [_in.to_human_readable() for _in in other_inputs] data = { 'success': True, 'nano_contract': nano_contract, 'outputs': outputs, 'my_inputs': my_inputs, 'other_inputs': other_inputs } else: data = {'success': False, 'message': 'Invalid transaction'} return json.dumps(data).encode('utf-8')
def to_human_readable(self) -> Dict[str, Any]: """Checks what kind of script this is and returns it in human readable form """ from hathor.transaction.scripts import parse_address_script, NanoContractMatchValues script_type = parse_address_script(self.script) if script_type: ret = script_type.to_human_readable() ret['value'] = self.value ret['token_data'] = self.token_data return ret nano_contract = NanoContractMatchValues.parse_script(self.script) if nano_contract: return nano_contract.to_human_readable() return {}
def render_PUT(self, request): """ Updates a nano contract tx and returns it in hexadecimal format. Post data should be a json with the following items: hex_tx: tx being updated, in hex value new_values: List[{'address', 'value'}], with bet address and value input_value: amount this wallet should stake in the nano contract :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'PUT') try: data = json.loads(request.content.read().decode('utf-8')) except json.JSONDecodeError: return json.dumps({'success': False, 'message': 'Invalid format for post data'}).encode('utf-8') for param in PARAMS_PUT: if param not in data: return get_missing_params_msg(param) try: decoded_params = self.decode_put_params(data) except ValueError as e: return json.dumps({'success': False, 'message': e.message}).encode('utf-8') try: tx = Transaction.create_from_struct(decoded_params.tx_bytes) except struct.error: return json.dumps({'success': False, 'message': 'Could not decode hex transaction'}).encode('utf-8') tx_outputs = [] nano_contract = None for _output in tx.outputs: _nano_contract = NanoContractMatchValues.parse_script(_output.script) if _nano_contract: total_value = _output.value nano_contract = _nano_contract else: tx_outputs.append(_output) if not nano_contract: return json.dumps({'success': False, 'message': 'Nano contract not found'}).encode('utf-8') for address, value in decoded_params.new_value_dict.items(): nano_contract.value_dict[address] = value tx.outputs = tx_outputs inputs, total_inputs_amount = self.manager.wallet.get_inputs_from_amount( decoded_params.input_value, self.manager.tx_storage ) change_tx = self.manager.wallet.handle_change_tx(total_inputs_amount, decoded_params.input_value) if change_tx: tx.outputs.append(TxOutput(change_tx.value, P2PKH.create_output_script(change_tx.address))) tx.outputs.insert(0, TxOutput(total_value, nano_contract.create_output_script())) for txin in inputs: tx.inputs.append(TxInput(txin.tx_id, txin.index, b'')) ret = {'success': True, 'hex_tx': tx.get_struct().hex()} return json.dumps(ret).encode('utf-8')
def render_POST(self, request): """ Creates and propagates a tx to spend a nano contract output. Post data should be a json with the following items: spent_tx_id: tx id being spent spent_tx_index: tx index being spent oracle_data: the data provided by the oracle oracle_signature: signature of the oracle data oracle_pubkey: oracle's public key address: the winning address value: nano contract total value :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'POST') content = request.content.read() if not content: return json.dumps({ 'success': False, 'message': 'No post data received' }).encode('utf-8') try: data = json.loads(content.decode('utf-8')) except json.JSONDecodeError: return json.dumps({ 'success': False, 'message': 'Invalid format for post data' }).encode('utf-8') for param in PARAMS: if param not in data: return get_missing_params_msg(param) try: decoded_data = self.decode_params(data) except ValueError as e: return json.dumps({ 'success': False, 'message': e.message }).encode('utf-8') tx_outputs = [] tx_outputs.append( TxOutput(decoded_data.value, P2PKH.create_output_script(decoded_data.address))) input_data = NanoContractMatchValues.create_input_data( decoded_data.oracle_data, decoded_data.oracle_signature, decoded_data.oracle_pubkey) tx_input = TxInput(decoded_data.spent_tx_id, decoded_data.spent_tx_index, input_data) tx = Transaction(inputs=[tx_input], outputs=tx_outputs) tx.storage = self.manager.tx_storage tx.parents = self.manager.get_new_tx_parents() tx.update_timestamp(int(self.manager.reactor.seconds())) tx.weight = self.manager.minimum_tx_weight(tx) tx.resolve() success = self.manager.propagate_tx(tx) ret = {'success': success, 'hex_tx': tx.get_struct().hex()} return json.dumps(ret).encode('utf-8')