diff --git a/oss_server/base/v1/forms.py b/oss_server/base/v1/forms.py index 39ceea6..ea4558b 100644 --- a/oss_server/base/v1/forms.py +++ b/oss_server/base/v1/forms.py @@ -129,3 +129,44 @@ class CreateLicenseTransferRawTxForm(forms.Form): 'min_value': '`color_id` should be greater than or equal to %(limit_value)s', 'max_value': '`color_id` should be less than or equal to %(limit_value)s' }) + + +class ExchangeRawTxForm(forms.Form): + fee_address = AddressField(error_messages={ + 'required': '`fee_address` is required', + 'invalid': '`fee_address` is not an address' + }) + address1 = AddressField(error_messages={ + 'required': '`address1` is required', + 'invalid': '`address1` is not an address' + }) + address2 = AddressField(error_messages={ + 'required': '`address2` is required', + 'invalid': '`address2` is not an address' + }) + color_id1 = ColorField(error_messages={ + 'required': '`color1` is required', + 'invalid': '`color1` is invalid', + 'min_value': '`color1` should be greater than or equal to %(limit_value)s', + 'max_value': '`color1` should be less than or equal to %(limit_value)s' + }) + color_id2 = ColorField(error_messages={ + 'required': '`color2` is required', + 'invalid': '`color2` is invalid', + 'min_value': '`color2` should be greater than or equal to %(limit_value)s', + 'max_value': '`color2` should be less than or equal to %(limit_value)s' + }) + amount1 = TxAmountField(error_messages={ + 'required': '`amount1` is required', + 'invalid': '`amount1` is invalid', + 'min_value': '`amount1` should be greater than or equal to %(limit_value)s', + 'max_value': '`amount1` should be less than or equal to %(limit_value)s', + 'max_decimal_places': '`amount1` only allow up to %(max)s decimal digits' + }) + amount2 = TxAmountField(error_messages={ + 'required': '`amount2` is required', + 'invalid': '`amount2` is invalid', + 'min_value': '`amount2` should be greater than or equal to %(limit_value)s', + 'max_value': '`amount2` should be less than or equal to %(limit_value)s', + 'max_decimal_places': '`amount2` only allow up to %(max)s decimal digits' + }) diff --git a/oss_server/base/v1/views.py b/oss_server/base/v1/views.py index 6984268..858061c 100644 --- a/oss_server/base/v1/views.py +++ b/oss_server/base/v1/views.py @@ -1,7 +1,5 @@ import httplib -import json import logging -from decimal import Decimal from django.conf import settings from django.http import JsonResponse @@ -13,8 +11,7 @@ from gcoinrpc import connect_to_remote from gcoinrpc.exceptions import InvalidAddressOrKey, InvalidParameter -from .forms import (CreateLicenseRawTxForm, CreateLicenseTransferRawTxForm, - CreateSmartContractRawTxForm, MintRawTxForm, RawTxForm) +from .forms import * from ..utils import balance_from_utxos, select_utxo, utxo_to_txin logger = logging.getLogger(__name__) @@ -319,72 +316,64 @@ def _get_license_utxo(self, utxos, color): return None -class CreateExchangeRawTxView(CsrfExemptMixin, View): +class CreateExchangeRawTxView(View): - def post(self, request, *args, **kwargs): - if request.META['CONTENT_TYPE'] != 'application/json': - return JsonResponse({'error': "only accept 'application/json' as Content-Type"}) + def get(self, request, *args, **kwargs): + form = ExchangeRawTxForm(request.GET) - json_obj = json.loads(request.body, parse_float=Decimal) - print(json_obj) - try: - self._validate_json_obj(json_obj) - except KeyError as e: - return JsonResponse({'error': e.message}) + if not form.is_valid(): + errors = ', '.join(reduce(lambda x, y: x + y, form.errors.values())) + response = {'error': errors} + return JsonResponse(response, status=httplib.BAD_REQUEST) - addr_in_map = {} - addr_out_map = {} + fee_address = form.cleaned_data['fee_address'] + address1 = form.cleaned_data['address1'] + address2 = form.cleaned_data['address2'] + color_id1 = form.cleaned_data['color_id1'] + color_id2 = form.cleaned_data['color_id2'] + amount1 = form.cleaned_data['amount1'] + amount2 = form.cleaned_data['amount2'] ins = [] outs = [] + fee_included = False + + for address, color_id, amount in [(address1, color_id1, amount1), (address2, color_id2, amount2)]: + utxos = get_rpc_connection().gettxoutaddress(address) + + inputs = select_utxo(utxos, color_id, amount) + if not inputs: + return JsonResponse({'error': 'insufficient funds'}, status=httplib.BAD_REQUEST) - # check if fee_address has enough fee. - utxos = get_rpc_connection().gettxoutaddress(json_obj['fee_address']) - inputs = select_utxo(utxos=utxos, color=1, sum=1) - if not inputs: - return JsonResponse({'error': 'insufficient fee in address {}'.format(json_obj['fee_address'])}) - ins += [utxo_to_txin(utxo) for utxo in inputs] - # the exchange part - for tx in json_obj['txs']: - addr1_in = addr_in_map.setdefault(tx['address1'], {}) - addr1_in[tx['color1']] = addr1_in.get(tx['color1'], 0) + tx['amount1'] - addr2_in = addr_in_map.setdefault(tx['address2'], {}) - addr2_in[tx['color2']] = addr1_in.get(tx['color2'], 0) + tx['amount2'] - - addr1_out = addr_out_map.setdefault(tx['address1'], {}) - addr1_out[tx['color2']] = addr1_in.get(tx['color2'], 0) + tx['amount2'] - addr2_out = addr_out_map.setdefault(tx['address2'], {}) - addr2_out[tx['color1']] = addr1_in.get(tx['color1'], 0) + tx['amount1'] - - for addr, color_amount_map in addr_in_map.items(): - utxos = get_rpc_connection().gettxoutaddress(addr) - for color, amount in color_amount_map.items(): - inputs = select_utxo(utxos=utxos, color=color, sum=amount) + inputs_value = balance_from_utxos(inputs)[color_id] + change = inputs_value - amount + + if color_id == 1 and address == fee_address: + inputs = select_utxo(utxos, color_id, amount + 1) if not inputs: - return JsonResponse({ - 'error': 'address {} does not have enough color {} to exchange'.format(addr, color) - }) - ins += [utxo_to_txin(utxo) for utxo in inputs] + return JsonResponse({'error': 'insufficient fee'}, status=httplib.BAD_REQUEST) + fee_included = True + inputs_value = balance_from_utxos(inputs)[color_id] + change = inputs_value - amount - 1 - for addr, color_amount_map in addr_out_map.items(): - for color, amount in color_amount_map.items(): - outs = [{'address': addr, 'value': int(amount * 10**8), 'color': color}] + if change: + outs.append({'address': address, + 'value': int(change * 10**8), 'color': color_id}) + + ins += [utxo_to_txin(utxo) for utxo in inputs] + + if not fee_included: + fee_address_utxos = get_rpc_connection().gettxoutaddress(fee_address) + inputs = select_utxo(fee_address_utxos, 1, 1) + if not inputs: + return JsonResponse({'error': 'insufficient fee in fee_address'}, status=httplib.BAD_REQUEST) + ins += [utxo_to_txin(utxo) for utxo in inputs] + inputs_value = balance_from_utxos(inputs)[1] + change = inputs_value - 1 + + if change: + outs.append({'address': fee_address, + 'value': int(change * 10**8), 'color': 1}) raw_tx = make_raw_tx(ins, outs) return JsonResponse({'raw_tx': raw_tx}) - - def _validate_json_obj(self, json_obj): - for key in ('fee_address', 'txs'): - if key not in json_obj: - raise KeyError('missing key: {}'.format(key)) - for tx in json_obj['txs']: - for key in ('address1', 'color1', 'amount1', 'address2', 'color2', 'amount2'): - if key not in tx: - raise KeyError('missing key in tx: {}'.format(key)) - - def _extract_addr_in_json(self, json_obj): - addr_set = set(json_obj['fee_address']) - for tx in json_obj['txs']: - addr_set.add(tx['address1']) - addr_set.add(tx['address2']) - return addr_set diff --git a/oss_server/oss_server/settings/base.py b/oss_server/oss_server/settings/base.py index 58068ca..bf66da7 100644 --- a/oss_server/oss_server/settings/base.py +++ b/oss_server/oss_server/settings/base.py @@ -71,10 +71,10 @@ # Gcoin RPC GCOIN_RPC = { - 'user': 'gcoin', - 'password': 'abc123', - 'host': 'store1.diqi.us', - 'port': '9876', + 'user': '', + 'password': '', + 'host': '', + 'port': '', }