From 5131540a7a8059599312ee0057e8f62421874b47 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Tue, 28 Feb 2017 13:07:36 +0100 Subject: [PATCH 01/10] rest api initial commit --- hackfest/rest_api.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 hackfest/rest_api.py diff --git a/hackfest/rest_api.py b/hackfest/rest_api.py new file mode 100644 index 0000000..6b32d03 --- /dev/null +++ b/hackfest/rest_api.py @@ -0,0 +1,26 @@ +from flask import Flask, jsonify, make_response, abort, request + +app = Flask(__name__) + + +@app.route('/') +def index(): + return "Hello, World!" + + +@app.route('/tomtom', methods=['POST']) +def tomtom_data(): + if not request.json: + abort(404) + print(request.json) + return jsonify({'ok': 1}) + + +@app.errorhandler(404) +def not_found(error): + """jsonify 404""" + return make_response(jsonify({'error': 'not found'}), 404) + + +if __name__ == "__main__": + app.run(debug=True) From 358fb6feca5ac2ef2c67dd2606ed2c4dc0558764 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Tue, 28 Feb 2017 13:29:27 +0100 Subject: [PATCH 02/10] change host --- hackfest/rest_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hackfest/rest_api.py b/hackfest/rest_api.py index 6b32d03..54c6513 100644 --- a/hackfest/rest_api.py +++ b/hackfest/rest_api.py @@ -23,4 +23,4 @@ def not_found(error): if __name__ == "__main__": - app.run(debug=True) + app.run(debug=True, host='0.0.0.0') From aca506243c6b15c72c1858f1e0d6d154c1a3f8fa Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Tue, 28 Feb 2017 14:27:34 +0100 Subject: [PATCH 03/10] pubkey endpoint --- hackfest/rest_api.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/hackfest/rest_api.py b/hackfest/rest_api.py index 54c6513..245196e 100644 --- a/hackfest/rest_api.py +++ b/hackfest/rest_api.py @@ -9,13 +9,24 @@ def index(): @app.route('/tomtom', methods=['POST']) -def tomtom_data(): +def post_tomtom_data(): if not request.json: abort(404) print(request.json) return jsonify({'ok': 1}) +@app.route('/pubkey', methods=['POST']) +def post_pubkey(): + device_id = request.form.get('device_id', None) + pubkey = request.form.get('pubkey', None) + if not pubkey or not device_id: + return jsonify({'error': 'invalid request'}), 404 + + print(device_id, pubkey) + return make_response(jsonify({'ok': 1})) + + @app.errorhandler(404) def not_found(error): """jsonify 404""" From 57d02a9ea8379b32e7623729fc4766501e834b30 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Tue, 28 Feb 2017 14:33:36 +0100 Subject: [PATCH 04/10] add message arg to pubkey endpoint --- hackfest/rest_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hackfest/rest_api.py b/hackfest/rest_api.py index 245196e..a92c226 100644 --- a/hackfest/rest_api.py +++ b/hackfest/rest_api.py @@ -20,10 +20,11 @@ def post_tomtom_data(): def post_pubkey(): device_id = request.form.get('device_id', None) pubkey = request.form.get('pubkey', None) + message = request.form.get('message', None) if not pubkey or not device_id: return jsonify({'error': 'invalid request'}), 404 - print(device_id, pubkey) + print(device_id, pubkey, message) return make_response(jsonify({'ok': 1})) From 28493e8c9e0cc5bf9fb30eb5832b3b726407bd3a Mon Sep 17 00:00:00 2001 From: "krish7919 (Krish)" Date: Tue, 28 Feb 2017 16:05:33 +0100 Subject: [PATCH 05/10] Punt vehicle telemetry data into bigchaindb - Assumptions: * We create an initial asset with ``` 'data': { 'asset_class': 'vehicle_telemetry', 'asset_author': 'hackfest-berlin-2017-team' } ``` * Every subsequent call to push telemetry data is a transfer operation, with the metadata being modifued with the new telemetry data. * The script expects the host and port where bdb is running and the keypair for the service to push data into bdb. --- hackfest/rest_api.py | 162 ++++++++++++++++++++++++++++++++++++++++++- log_assets.py | 1 - 2 files changed, 160 insertions(+), 3 deletions(-) diff --git a/hackfest/rest_api.py b/hackfest/rest_api.py index 54c6513..09b1842 100644 --- a/hackfest/rest_api.py +++ b/hackfest/rest_api.py @@ -1,4 +1,10 @@ -from flask import Flask, jsonify, make_response, abort, request +from flask import Flask, jsonify, make_response, abort, request, current_app +from argparse import ArgumentParser +from logging import getLogger, Formatter, DEBUG +from logging.handlers import SysLogHandler +from bigchaindb_driver import BigchainDB +from bigchaindb_driver.exceptions import NotFoundError +from time import sleep, strftime, gmtime app = Flask(__name__) @@ -22,5 +28,157 @@ def not_found(error): return make_response(jsonify({'error': 'not found'}), 404) -if __name__ == "__main__": +@app.route('/telemetry', methods=['POST']) +def handle_telemetry_data(): + if not request.json: + abort(404) + print(request.json) + # parse the request? + # should be signed req + # can't be mangled here + # send it to bdb as is (?!) + send_data_to_bdb(request.json) +# end handle_telemetry_data + + +def init_system(app, bdb_ip, bdb_port, pub_key, pr_key): + bdb = BigchainDB('http://{0}:{1}'.format(bdb_ip, bdb_port)) + keypair = { + 'private_key': pr_key, + 'public_key': pub_key + } + + asset_data = { + 'data': { + 'asset_class': 'vehicle_telemetry', + 'asset_author': 'hackfest-berlin-2017-team' + } + } + + app.config['bdb'] = bdb + app.config['keypair'] = keypair + app.config['asset'] = asset_data +# end init_system + + +def record_data(bdb_conn, data, metadata, keypair, tx_id): + fulfilled_tx = None + if tx_id != '': + creation_tx = bdb_conn.transactions.retrieve(tx_id) + if 'id' in creation_tx['asset']: + asset_id = creation_tx['asset']['id'] + else: + asset_id = creation_tx['id'] + # end if + transfer_asset = { + 'id': asset_id + } + output_index = 0 + output = creation_tx['outputs'][output_index] + transfer_input = { + 'fulfillment': output['condition']['details'], + 'fulfills': { + 'output': output_index, + 'txid': creation_tx['id'] + }, + 'owners_before': output['public_keys'] + } + prepared_transfer_tx = bdb_conn.transactions.prepare( + operation='TRANSFER', + asset=transfer_asset, + inputs=transfer_input, + recipients=keypair['public_key'], + metadata=metadata + ) + fulfilled_tx = bdb_conn.transactions.fulfill( + prepared_transfer_tx, + private_keys=keypair['private_key'] + ) + bdb_conn.transactions.send(fulfilled_tx) + else: + prepared_creation_tx = bdb_conn.transactions.prepare( + operation='CREATE', + signers=keypair['public_key'], + asset=data, + metadata=metadata + ) + fulfilled_tx = bdb_conn.transactions.fulfill( + prepared_creation_tx, + private_keys=keypair['private_key'] + ) + bdb_conn.transactions.send(fulfilled_tx) + # end if + + # verify if the tx was registered in the bigchain + trials = 0 + while trials < 10: + try: + if bdb_conn.transactions.status( + fulfilled_tx['id'] + ).get('status') == 'valid': + print('Tx valid in:', trials, 'secs') + break + except NotFoundError: + trials += 1 + sleep(1) + # end try + # end while + + if trials == 10: + print('Cannot connect to backend... Exiting!') + exit(0) + # end if + return fulfilled_tx['id'] +# end record_data + + +def send_data_to_bdb(telemetry_data): + bdb = current_app.config['bdb'] + keypair = current_app.config['keypair'] + tx_id = current_app.config['tx_id'] + asset_data = current_app.config['asset'] + + asset_metadata = { + 'company': 'vw', + 'financer': 'commerzbank', + 'owner': 'microsoft', + 'source': 'riddle_and_code', + 'dest': 'bdb', + 'timestamp': strftime('%Y-%m-%d_%H:%M:%S', gmtime()), + 'data': telemetry_data + } + + # record data to bigchain + tx_id = record_data(bdb, asset_data, asset_metadata, keypair, tx_id) + current_app.config['tx_id'] = tx_id +# end send_data_to_bdb + + +if __name__ == '__main__': + # argument parsing + parser = ArgumentParser(description='BDB/IoT') + req_group = parser.add_argument_group('required arguments') + req_group.add_argument('--bdb-ip', type=str, required=True, + help='bdb hostname/ip address') + req_group.add_argument('--bdb-port', type=int, required=True, + help='bdb port number') + req_group.add_argument('--public-key', type=str, required=True, + help='api service public key') + req_group.add_argument('--private-key', type=str, required=True, + help='api service private key') + args = parser.parse_args() + # set up logging + logger = getLogger('update_client') + logger.setLevel(DEBUG) + # local syslog + local_formatter = Formatter( + "%(name)s %(threadName)s %(levelname)s -- %(message)s", + datefmt='%Y-%m-%d %H:%M:%S') + local_syslog = SysLogHandler(address='/dev/log', + facility=SysLogHandler.LOG_SYSLOG) + local_syslog.setFormatter(local_formatter) + logger.addHandler(local_syslog) + init_system(app, args.bdb_ip, args.bdb_port, args.public_key, + args.private_key) app.run(debug=True, host='0.0.0.0') +# end main diff --git a/log_assets.py b/log_assets.py index 4d96181..d8ee615 100644 --- a/log_assets.py +++ b/log_assets.py @@ -39,7 +39,6 @@ def init_system(bosun_ip, bosun_port, bdb_ip, bdb_port): # record data to bigchain tx_id = record_data(asset_data, keypair, tx_id, bdb_ip, bdb_port) time.sleep(5) - init_done = True # end while From c59cab024315a3ba0d3dfa100e1843cb0b62f6ad Mon Sep 17 00:00:00 2001 From: "krish7919 (Krish)" Date: Tue, 28 Feb 2017 17:14:27 +0100 Subject: [PATCH 06/10] Bugfix and enhancements --- hackfest/rest_api.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hackfest/rest_api.py b/hackfest/rest_api.py index b00df2b..d9291d4 100644 --- a/hackfest/rest_api.py +++ b/hackfest/rest_api.py @@ -50,6 +50,8 @@ def handle_telemetry_data(): # can't be mangled here # send it to bdb as is (?!) send_data_to_bdb(request.json) + # TODO - bubble up any errors + return make_response(jsonify({'ok': 1})) # end handle_telemetry_data @@ -70,12 +72,14 @@ def init_system(app, bdb_ip, bdb_port, pub_key, pr_key): app.config['bdb'] = bdb app.config['keypair'] = keypair app.config['asset'] = asset_data + app.config['tx_id'] = '' # end init_system def record_data(bdb_conn, data, metadata, keypair, tx_id): fulfilled_tx = None if tx_id != '': + logger.debug('Transfer tx!') creation_tx = bdb_conn.transactions.retrieve(tx_id) if 'id' in creation_tx['asset']: asset_id = creation_tx['asset']['id'] @@ -108,6 +112,7 @@ def record_data(bdb_conn, data, metadata, keypair, tx_id): ) bdb_conn.transactions.send(fulfilled_tx) else: + logger.debug('Create tx!') prepared_creation_tx = bdb_conn.transactions.prepare( operation='CREATE', signers=keypair['public_key'], @@ -162,6 +167,7 @@ def send_data_to_bdb(telemetry_data): # record data to bigchain tx_id = record_data(bdb, asset_data, asset_metadata, keypair, tx_id) + logger.debug('tx_id: ' + tx_id) current_app.config['tx_id'] = tx_id # end send_data_to_bdb @@ -180,7 +186,7 @@ def send_data_to_bdb(telemetry_data): help='api service private key') args = parser.parse_args() # set up logging - logger = getLogger('update_client') + logger = getLogger('telemetry_service') logger.setLevel(DEBUG) # local syslog local_formatter = Formatter( From 0f19ce3a110b16301c7aee962c82b8dc11f34f88 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 1 Mar 2017 15:28:45 +0100 Subject: [PATCH 07/10] added multisig transactions --- hackfest/transactions.py | 115 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 hackfest/transactions.py diff --git a/hackfest/transactions.py b/hackfest/transactions.py new file mode 100644 index 0000000..83b9820 --- /dev/null +++ b/hackfest/transactions.py @@ -0,0 +1,115 @@ +import json +import sha3 + +from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment +from cryptoconditions.crypto import Ed25519SigningKey + +from bigchaindb.models import Transaction +from bigchaindb.common.crypto import generate_key_pair +from bigchaindb.common.transaction import Output + +VW_SK, VW_PK = generate_key_pair() +TEL_SK, TEL_PK = generate_key_pair() + + +def create_asset(): + # Create asset VW -> [VW, TEL] + # Custom crypto condition multisig 1-2 + threshold_fulfillment = ThresholdSha256Fulfillment(threshold=1) + + vw_fulfillment = Ed25519Fulfillment(public_key=VW_PK) + tel_fulfillment = Ed25519Fulfillment(public_key=TEL_PK) + + threshold_fulfillment.add_subfulfillment(vw_fulfillment) + threshold_fulfillment.add_subfulfillment(tel_fulfillment) + + output = { + 'amount': 1, + 'condition': { + 'details': threshold_fulfillment.to_dict(), + 'uri': threshold_fulfillment.condition.serialize_uri() + }, + 'public_keys': [VW_PK, TEL_PK] + } + + # Create the transaction + tx_create = Transaction.create([VW_PK], [([VW_PK, TEL_PK], 1)]) + # Override the condition we our custom build one + tx_create.outputs[0] = Output.from_dict(output) + + # Sign the transaction + tx_create_signed = tx_create.sign([VW_SK]) + + return tx_create_signed + + +def create_transfer(prev_tx): + # Create asset VW -> [VW, TEL] + # Custom crypto condition multisig 1-2 + threshold_fulfillment = ThresholdSha256Fulfillment(threshold=1) + + vw_fulfillment = Ed25519Fulfillment(public_key=VW_PK) + tel_fulfillment = Ed25519Fulfillment(public_key=TEL_PK) + + threshold_fulfillment.add_subfulfillment(vw_fulfillment) + threshold_fulfillment.add_subfulfillment(tel_fulfillment) + + output = { + 'amount': 1, + 'condition': { + 'details': threshold_fulfillment.to_dict(), + 'uri': threshold_fulfillment.condition.serialize_uri() + }, + 'public_keys': [VW_PK, TEL_PK], + } + + # The yet to be fulfilled input: + input_ = { + 'fulfillment': None, + 'fulfills': { + 'txid': prev_tx.id, + 'output': 0, + }, + 'owners_before': [VW_PK, TEL_PK], + } + + # Craft the payload: + transfer_tx = { + 'operation': 'TRANSFER', + 'asset': {'id': prev_tx.id}, + 'metadata': None, + 'outputs': [output], + 'inputs': [input_], + 'version': '0.9', + } + + # Generate the id, by hashing the encoded json formatted string + # representation of the transaction: + json_str_tx = json.dumps( + transfer_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + + txid = sha3.sha3_256(json_str_tx.encode()).hexdigest() + + transfer_tx['id'] = txid + + # Sign the transaction: + message = json.dumps( + transfer_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + + threshold_fulfillment = ThresholdSha256Fulfillment(threshold=1) + + vw_fulfillment.sign(message.encode(), private_key=Ed25519SigningKey(VW_SK)) + threshold_fulfillment.add_subfulfillment(vw_fulfillment) + threshold_fulfillment.add_subcondition(tel_fulfillment.condition) + + fulfillment_uri = threshold_fulfillment.serialize_uri() + transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri + return Transaction.from_dict(transfer_tx) From 878da71bc8a2efb63975765ffbb2af4ec1a3a041 Mon Sep 17 00:00:00 2001 From: "krish7919 (Krish)" Date: Wed, 1 Mar 2017 16:07:05 +0100 Subject: [PATCH 08/10] Lungo Tavolo asset creation script --- hackfest/lungo_tavolo.py | 105 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 hackfest/lungo_tavolo.py diff --git a/hackfest/lungo_tavolo.py b/hackfest/lungo_tavolo.py new file mode 100644 index 0000000..3d7a76b --- /dev/null +++ b/hackfest/lungo_tavolo.py @@ -0,0 +1,105 @@ +from argparse import ArgumentParser +from logging import getLogger, Formatter, DEBUG +from logging.handlers import SysLogHandler +from bigchaindb_driver import BigchainDB +from json import dumps as json_dumps +from time import strftime, gmtime + + +class Provenance(object): + def __init__(self, log, bdb_ip, bdb_port, public_key, private_key, + file_path): + self.bdb = BigchainDB('http://{0}:{1}'.format(bdb_ip, bdb_port)) + self.keypair = { + 'private_key': private_key, + 'public_key': public_key + } + self.file_path = file_path + self.logger = log + # end __init__ + + def start(self): + # open the csv file, every file is an asset + asset = {} + asset['asset_class'] = 'lungo_tavolo_product' + with open(self.file_path) as fh: + # ignore the headers for now + fh.readline() + for line in fh: + columns = line.split(',') + # sanitize the columns, remove null strings + new_columns = [] + for cell in columns: + if cell != '': + cell = cell.rstrip('\r\n') + new_columns.append(cell) + # end if + # end for + # define the asset + # remove '.' from the column[0] as mongo does not allow '.' + # in key fields: http://stackoverflow.com/questions/28664383/mongodb-not-allowing-using-in-key + new_columns[0] = new_columns[0].replace('.', '') + asset[new_columns[0]] = ','.join(new_columns[1:]) + # end for + self.logger.debug(json_dumps(asset)) + # end with + # defin the metadata + asset_metadata = { + 'from_file': self.file_path, + 'create_at': strftime('%Y-%m-%d_%H:%M:%S', gmtime()), + } + # send to bdb + self.send_to_bdb(asset, asset_metadata) + # end start + + def send_to_bdb(self, asset, metadata): + bdb_asset = { + 'data': asset + } + prepared_creation_tx = self.bdb.transactions.prepare( + operation='CREATE', + signers=self.keypair['public_key'], + asset=bdb_asset, + metadata=metadata + ) + fulfilled_tx = self.bdb.transactions.fulfill( + prepared_creation_tx, + private_keys=self.keypair['private_key'] + ) + self.bdb.transactions.send(fulfilled_tx) + self.logger.debug(fulfilled_tx['id']) + # end send_to_bdb +# end class Provenance + + +if __name__ == '__main__': + # argument parsing + parser = ArgumentParser(description='BDB/LungoTavolo') + req_group = parser.add_argument_group('required arguments') + req_group.add_argument('--bdb-ip', type=str, required=True, + help='bdb hostname/ip address') + req_group.add_argument('--bdb-port', type=int, required=True, + help='bdb port number') + req_group.add_argument('--public-key', type=str, required=True, + help='lungo tavolo public key') + req_group.add_argument('--private-key', type=str, required=True, + help='lungo tavolo private key') + req_group.add_argument('--file', type=str, required=True, + help='path to csv file') + args = parser.parse_args() + # set up logging + logger = getLogger('provenance_service') + logger.setLevel(DEBUG) + # local syslog + local_formatter = Formatter( + "%(name)s %(threadName)s %(levelname)s -- %(message)s", + datefmt='%Y-%m-%d %H:%M:%S') + local_syslog = SysLogHandler(address='/dev/log', + facility=SysLogHandler.LOG_SYSLOG) + local_syslog.setFormatter(local_formatter) + logger.addHandler(local_syslog) + + provenance = Provenance(logger, args.bdb_ip, args.bdb_port, args.public_key, + args.private_key, args.file) + provenance.start() +# end main From b6eaeebd66b8f79bb2a9a77b9d6c8ba45429eb6d Mon Sep 17 00:00:00 2001 From: "krish7919 (Krish)" Date: Wed, 1 Mar 2017 16:07:50 +0100 Subject: [PATCH 09/10] Lungo Tavolo product files - William approved that this is open source --- .../Product Data Lungo Tavolo - Pants.csv | 49 ++++++++++++++++++ .../Product Data Lungo Tavolo - Skirt.csv | 33 ++++++++++++ hackfest/Product Data Lungo Tavolo - Top.csv | 45 ++++++++++++++++ hackfest/Product Data Lungo Tavolo.xlsx | Bin 0 -> 108360 bytes 4 files changed, 127 insertions(+) create mode 100644 hackfest/Product Data Lungo Tavolo - Pants.csv create mode 100644 hackfest/Product Data Lungo Tavolo - Skirt.csv create mode 100644 hackfest/Product Data Lungo Tavolo - Top.csv create mode 100644 hackfest/Product Data Lungo Tavolo.xlsx diff --git a/hackfest/Product Data Lungo Tavolo - Pants.csv b/hackfest/Product Data Lungo Tavolo - Pants.csv new file mode 100644 index 0000000..25b852c --- /dev/null +++ b/hackfest/Product Data Lungo Tavolo - Pants.csv @@ -0,0 +1,49 @@ +Category,Subcategory 1,Subcategory 2,Subcategory 3,Subcategory 4,Subcategory 5,Subcategory 6 +Productname,wrap and strap pants,"=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP101a.jpg"")",,,, +Producttype,Pants,White,Mid,,, +Suggested Sex,Unisex,Donna,Uomo,,, +Ref.,LT SP 101,,,,, +Series,Summer,,,,, +Limited Edition,1/400,,,,, +Designer,Sergio Perruci,,,,, +Year,2015,,,,, +In Collaboration with,cws,,,,, +Description,Pants in Italian linen with a double wrap belted waist in signature black double satin. Wear them with the belt wrapped twice around your waist and let the extra length of the ribbon flow loosely along your leg,"=image(""https://s-media-cache-ak0.pinimg.com/564x/26/b3/a5/26b3a584ecbe16bd5dbb00c36895265f.jpg"")","=image(""https://s-media-cache-ak0.pinimg.com/originals/4c/c8/74/4cc874b4f7f8dc25fa92138a2eed94a4.png"")",,, +Detail,wrap front panel detail raw cut on the grain taped edges and hems sandwich bonded belt double satin ribbon invisible selvedge ribbon width 2.5 cm ribbon length from waist 40 cm rectangular double ring buckle raw stainless steel nickel free hem label at strap tip entirely made in Italy,"=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP101g.jpg"")","=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP101c.jpg"")",,, +Fabric 1,Linen,100%,,,, +Origin,Italy,,,,, +Colour 1,White,"=image (""http://www.lungotavolo.it/wp-content/uploads/2016/05/colwhite.jpg"")",,,, +Pantone,PANTONE 11-4001 TPG,,,,, +HEX/HTML,EFF0F1,,,,, +Colour 2,Khaki,"=image(""http://www.lungotavolo.it/wp-content/uploads/2016/05/colkhaki.jpg"")",,,, +Pantone,PANTONE 15-1216 TPX,,,,, +HEX/HTML,C0B290,,,,, +Colour 3,Stone,"=image(""http://www.lungotavolo.it/wp-content/uploads/2016/05/colgrey.jpg"")",,,, +Pantone,PANTONE PQ-CoolGray8C,,,,, +HEX/HTML,888B8D,,,,, +Ref.,LT SP 101,,,,, +Series,Print,,,,, +Limited Edition,1/400,,,,, +Designer,Sergio Perruci,,,,, +Year,2016,,,,, +In Collaboration with,cws,,,,, +Background Colour 3 ,Stripes,"=image(""http://www.lungotavolo.it/wp-content/uploads/2016/09/colstripe.jpg"")",,,, +Pantone,PANTONE 11-4001 TPG,,,,, +HEX/HTML,EFF0F1,,,,, +Pantone,PANTONE 19-0000 TPG,,,,, +HEX/HTML,454546,,,,, +Background Colour 1 ,Dots,"=image(""http://www.lungotavolo.it/wp-content/uploads/2016/09/colpois.jpg"")",,,, +Pantone,PANTONE 11-4001 TPG,,,,, +HEX/HTML,EFF0F1,,,,, +Pantone,PANTONE 19-0000 TPG,,,,, +HEX/HTML,454546,,,,, +Sizes Available,XS:waist:74cm,S:waist:80cm,M:waist:84cm,L:waist:88cm,XL:waist:92cm,XXL:waist:96cm +Price,EUR 270,USD 289,GBP 229,CAD 389,, +Aesthetic,#Elegant,#Leisure,#Italian,#Minimalism,#Zen, +Quality Trait,"=""Made in Italy""",Fair Labour,Sustainable,Verified Product,, +Quality Seal,"=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/666115-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/580362-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/580361-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/204428-200.png"")",, +Quality Statement,Produced in Italy according to Italian Law.,Produced under fair labour conditions according International Labour Organization. Excelling standards according to internal code of ethics.,"Produced under ecologically sustainable conditions according to the statues of rainforest Alliance, Certified Supply Chain. Business auditited and advised by Eco Age Ltd",This Garment was designed to inspire you. Believe in the beauty of imagination - unapolagetically! Have the courage to be who you want to be. This will set you free. ,, +Designed by,Sergio Perruci,"=image(""https://s-media-cache-ak0.pinimg.com/originals/1b/cd/ce/1bcdce55fba1809a7639b5e2235f068b.png"")",CWS,"=image(""https://s-media-cache-ak0.pinimg.com/originals/8d/3b/cc/8d3bcc1b1b148d4f741cc19303a35820.png"")",, +Country of Production,Italy,"=image (""https://upload.wikimedia.org/wikipedia/commons/thumb/0/03/Flag_of_Italy.svg/2000px-Flag_of_Italy.svg.png"")",,,, +Designed Studio,10144 Turin,"Via Treviso, 45","=image(""https://s-media-cache-ak0.pinimg.com/564x/91/70/13/9170135385ce2441ad75970fc18d408d.jpg"")","=image(""https://s-media-cache-ak0.pinimg.com/564x/6b/ce/b9/6bceb98a5d7620cac2374534e3dbfb1b.jpg"")",, +Produced in,12051 Alba,,"=image(""https://s-media-cache-ak0.pinimg.com/originals/e0/01/86/e0018641c80d305614bf19643425cd26.png"")","=image(""http://www.anselmaitalia.com/img_sito/testata/homepage/ristorante-italia_05.jpg"")",, diff --git a/hackfest/Product Data Lungo Tavolo - Skirt.csv b/hackfest/Product Data Lungo Tavolo - Skirt.csv new file mode 100644 index 0000000..87848ee --- /dev/null +++ b/hackfest/Product Data Lungo Tavolo - Skirt.csv @@ -0,0 +1,33 @@ +Category,Subcategory 1,Subcagetory 2,Subcagetory 3,Subcategory 4,Subcategory 5 +Productname,wrap and strap skirt,"=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP104a.jpg"")",,, +Producttype,Skirt,Stone,Mid,, +Suggested Sex,Unisex,Donna,Uomo,, +Collection,LT SP 104,,,, +Series,Summer,,,, +Limited Edition,1/400,,,, +Designer,Sergio Perruci,,,, +Year,2015,,,, +In Collaboration with,cws,,,, +Description,a skirt in Italian linen with a double wrap belted waist in signature black double satin wear it with the belt wrapped twice around your waist and let the extra length of the ribbon flow loosely along your leg,"=image(""https://s-media-cache-ak0.pinimg.com/originals/87/f5/0b/87f50be844c989171fdd531d9468204a.png"")","=image(""https://s-media-cache-ak0.pinimg.com/564x/3f/8a/5c/3f8a5c2d4ce602af1968fb0a3d501241.jpg"")",, +Detail,wrap front panel detail raw cut on the grain taped edges and hems sandwich bonded belt double satin ribbon invisible selvedge ribbon width 2.5 cm ribbon length from waist 40 cm rectangular double ring buckle raw stainless steel nickel free hem label at strap tip entirely made in Italy,"=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP104d.jpg"")","=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP104e.jpg"")",, +Fabric 1,Linen,100%,Pure,, +Origin,Italy,,,, +Colour 1,White,"=image (""http://www.lungotavolo.it/wp-content/uploads/2016/05/colwhite.jpg"")",,, +Pantone,PANTONE 11-4001 TPG,,,, +HEX/HTML,EFF0F1,,,, +Colour 2,Khaki,"=image(""http://www.lungotavolo.it/wp-content/uploads/2016/05/colkhaki.jpg"")",,, +Pantone,PANTONE 15-1216 TPX,,,, +HEX/HTML,C0B290,,,, +Colour 3,Stone,"=image(""http://www.lungotavolo.it/wp-content/uploads/2016/05/colgrey.jpg"")",,, +Pantone,PANTONE PQ-CoolGray8C,,,, +HEX/HTML,888B8D,,,, +Sizes Available,Short,Mid,Long,, +Price,EUR 190,USD 219,GBP 169,CAD 279, +Aesthetic,#Elegant,#Leisure,#Italian,#Minimalism,#Zen +Quality Trait,"=""Made in Italy""",Fair Labour,Sustainable,Verified Product, +Quality Seal,"=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/666115-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/580362-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/580361-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/204428-200.png"")", +Quality Statement,Produced in Italy according to Italian Law.,Produced under fair labour conditions according International Labour Organization. Excelling standards according to internal code of ethics.,"Produced under ecologically sustainable conditions according to the statues of rainforest Alliance, Certified Supply Chain. Business auditited and advised by Eco Age Ltd",This Garment was designed to inspire you. Believe in the beauty of imagination - unapolagetically! Have the courage to be who you want to be. This will set you free. , +Designed by,Sergio Perruci,"=image(""https://s-media-cache-ak0.pinimg.com/originals/1b/cd/ce/1bcdce55fba1809a7639b5e2235f068b.png"")",CWS,"=image(""https://s-media-cache-ak0.pinimg.com/originals/8d/3b/cc/8d3bcc1b1b148d4f741cc19303a35820.png"")", +Country of Production,Italy,"=image (""https://upload.wikimedia.org/wikipedia/commons/thumb/0/03/Flag_of_Italy.svg/2000px-Flag_of_Italy.svg.png"")",,, +Designed Studio,10144 Turin,"Via Treviso, 45","=image(""https://s-media-cache-ak0.pinimg.com/564x/91/70/13/9170135385ce2441ad75970fc18d408d.jpg"")","=image(""https://s-media-cache-ak0.pinimg.com/564x/6b/ce/b9/6bceb98a5d7620cac2374534e3dbfb1b.jpg"")", +Produced in,12051 Alba,,"=image(""https://s-media-cache-ak0.pinimg.com/originals/e0/01/86/e0018641c80d305614bf19643425cd26.png"")","=image(""http://www.anselmaitalia.com/img_sito/testata/homepage/ristorante-italia_05.jpg"")", diff --git a/hackfest/Product Data Lungo Tavolo - Top.csv b/hackfest/Product Data Lungo Tavolo - Top.csv new file mode 100644 index 0000000..9847eed --- /dev/null +++ b/hackfest/Product Data Lungo Tavolo - Top.csv @@ -0,0 +1,45 @@ +Category,Subcategory 1,Subcagetory 2,Subcagetory 3,Subcategory 4,Subcategory 5,Subcategory 6 +Productname,drop and strap top,"=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP106a.jpg"")",,,, +Producttype,Top,Khaki,,,, +Sugessted Sex,Women,Donna,,,, +Collection,LT SP 106,,,,, +Series,Summer,,,,, +Limited Edition,1/400,,,,, +Designer,Sergio Perruci,,,,, +Year,2015,,,,, +In Collaboration with,cws,,,,, +Description,a scarf top in Italian linen with a belt in signature black double satin wear it with the belt fastened at the back of your waist and let the extra length of the ribbon flow loosely behind you,"=image(""https://s-media-cache-ak0.pinimg.com/originals/0f/50/a6/0f50a6ed9c7162cfc157693969232ac4.png"")",,,, +Detail,"wrap front panel detail +raw cut on the grain +taped edges and hems +sandwich bonded belt +double satin ribbon +invisible selvedge +ribbon width 2.5 cm +ribbon length from waist 40 cm +rectangular double ring buckle +raw stainless steel +nickel free +hem label at strap tip +entirely made in Italy","=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP106e.jpg"")","=image(""http://www.lungotavolo.it/wp-content/uploads/2015/12/LTSP106c.jpg"")",,, +Fabric 1,Linen,100%,Pure,,, +Origin,Italy,,,,, +Colour 1,White,"=image (""http://www.lungotavolo.it/wp-content/uploads/2016/05/colwhite.jpg"")",,,, +Pantone,PANTONE 11-4001 TPG,,,,, +HEX/HTML,EFF0F1,,,,, +Colour 2,Khaki,"=image(""http://www.lungotavolo.it/wp-content/uploads/2016/05/colkhaki.jpg"")",,,, +Pantone,PANTONE 15-1216 TPX,,,,, +HEX/HTML,C0B290,,,,, +Colour 3,Stone,"=image(""http://www.lungotavolo.it/wp-content/uploads/2016/05/colgrey.jpg"")",,,, +Pantone,PANTONE PQ-CoolGray8C,,,,, +HEX/HTML,888B8D,,,,, +Size,One Size,,,,, +Price,EUR 105,USD 120,GBP 90,CAD 180,, +Aesthetic,#Elegant,#Leisure,#Italian,#Minimalism,#Zen, +Quality Trait,"=""Made in Italy""",Fair Labour,Sustainable,Verified Product,, +Quality Seal,"=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/666115-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/580362-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/580361-200.png"")","=image(""https://d30y9cdsu7xlg0.cloudfront.net/png/204428-200.png"")",, +Quality Statement,Produced in Italy according to Italian Law.,Produced under fair labour conditions according International Labour Organization. Excelling standards according to internal code of ethics.,"Produced under ecologically sustainable conditions according to the statues of rainforest Alliance, Certified Supply Chain. Business auditited and advised by Eco Age Ltd",This Garment was designed to inspire you. Believe in the beauty of imagination - unapolagetically! Have the courage to be who you want to be. This will set you free. ,, +Designed by,Sergio Perruci,"=image(""https://s-media-cache-ak0.pinimg.com/originals/1b/cd/ce/1bcdce55fba1809a7639b5e2235f068b.png"")",CWS,"=image(""https://s-media-cache-ak0.pinimg.com/originals/8d/3b/cc/8d3bcc1b1b148d4f741cc19303a35820.png"")",, +Country of Production,Italy,"=image (""https://upload.wikimedia.org/wikipedia/commons/thumb/0/03/Flag_of_Italy.svg/2000px-Flag_of_Italy.svg.png"")",,,, +Designed Studio,10144 Turin,"Via Treviso, 45","=image(""https://s-media-cache-ak0.pinimg.com/564x/91/70/13/9170135385ce2441ad75970fc18d408d.jpg"")","=image(""https://s-media-cache-ak0.pinimg.com/564x/6b/ce/b9/6bceb98a5d7620cac2374534e3dbfb1b.jpg"")",, +Produced in,12051 Alba,,"=image(""https://s-media-cache-ak0.pinimg.com/originals/e0/01/86/e0018641c80d305614bf19643425cd26.png"")","=image(""http://www.anselmaitalia.com/img_sito/testata/homepage/ristorante-italia_05.jpg"")",, diff --git a/hackfest/Product Data Lungo Tavolo.xlsx b/hackfest/Product Data Lungo Tavolo.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..29fad5efd0c644d8e847ab435484c939c9d28f22 GIT binary patch literal 108360 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 literal 0 HcmV?d00001 From 9bfd30c3300436d58cf6798b8b11ba43a8f8ae8e Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 1 Mar 2017 16:42:43 +0100 Subject: [PATCH 10/10] rest api support for multisig transactions --- hackfest/rest_api.py | 72 ++++++++++++++-------------------------- hackfest/transactions.py | 30 +++++++++-------- 2 files changed, 40 insertions(+), 62 deletions(-) diff --git a/hackfest/rest_api.py b/hackfest/rest_api.py index d9291d4..f848315 100644 --- a/hackfest/rest_api.py +++ b/hackfest/rest_api.py @@ -5,6 +5,7 @@ from bigchaindb_driver import BigchainDB from bigchaindb_driver.exceptions import NotFoundError from time import sleep, strftime, gmtime +from transactions import create_asset, create_transfer app = Flask(__name__) @@ -49,7 +50,9 @@ def handle_telemetry_data(): # should be signed req # can't be mangled here # send it to bdb as is (?!) - send_data_to_bdb(request.json) + json_data = request.json + vehicle_id = request.json.pop('vehicleId') + send_data_to_bdb(json_data, vehicle_id) # TODO - bubble up any errors return make_response(jsonify({'ok': 1})) # end handle_telemetry_data @@ -72,58 +75,31 @@ def init_system(app, bdb_ip, bdb_port, pub_key, pr_key): app.config['bdb'] = bdb app.config['keypair'] = keypair app.config['asset'] = asset_data - app.config['tx_id'] = '' + app.config['txids'] = {} # end init_system -def record_data(bdb_conn, data, metadata, keypair, tx_id): +def record_data(bdb_conn, data, metadata, keypair, tx_id, vehicle_id): fulfilled_tx = None if tx_id != '': logger.debug('Transfer tx!') - creation_tx = bdb_conn.transactions.retrieve(tx_id) - if 'id' in creation_tx['asset']: - asset_id = creation_tx['asset']['id'] + previous_tx = bdb_conn.transactions.retrieve(tx_id) + if 'id' in previous_tx['asset']: + asset_id = previous_tx['asset']['id'] else: - asset_id = creation_tx['id'] - # end if - transfer_asset = { - 'id': asset_id - } - output_index = 0 - output = creation_tx['outputs'][output_index] - transfer_input = { - 'fulfillment': output['condition']['details'], - 'fulfills': { - 'output': output_index, - 'txid': creation_tx['id'] - }, - 'owners_before': output['public_keys'] - } - prepared_transfer_tx = bdb_conn.transactions.prepare( - operation='TRANSFER', - asset=transfer_asset, - inputs=transfer_input, - recipients=keypair['public_key'], - metadata=metadata - ) - fulfilled_tx = bdb_conn.transactions.fulfill( - prepared_transfer_tx, - private_keys=keypair['private_key'] - ) - bdb_conn.transactions.send(fulfilled_tx) + asset_id = previous_tx['id'] + + transfer_tx = create_transfer(previous_tx, keypair['private_key'], + keypair['public_key'], vehicle_id, + metadata, asset_id) + bdb_conn.transactions.send(transfer_tx.to_dict()) + fulfilled_tx = transfer_tx.to_dict() else: logger.debug('Create tx!') - prepared_creation_tx = bdb_conn.transactions.prepare( - operation='CREATE', - signers=keypair['public_key'], - asset=data, - metadata=metadata - ) - fulfilled_tx = bdb_conn.transactions.fulfill( - prepared_creation_tx, - private_keys=keypair['private_key'] - ) - bdb_conn.transactions.send(fulfilled_tx) + create_tx = create_asset(keypair['private_key'], keypair['public_key'], + vehicle_id, data, metadata) + bdb_conn.transactions.send(create_tx.to_dict()) + fulfilled_tx = create_tx.to_dict() # end if # verify if the tx was registered in the bigchain @@ -149,10 +125,10 @@ def record_data(bdb_conn, data, metadata, keypair, tx_id): # end record_data -def send_data_to_bdb(telemetry_data): +def send_data_to_bdb(telemetry_data, vehicle_id): bdb = current_app.config['bdb'] keypair = current_app.config['keypair'] - tx_id = current_app.config['tx_id'] + tx_id = current_app.config['txids'].get(vehicle_id, '') asset_data = current_app.config['asset'] asset_metadata = { @@ -166,9 +142,9 @@ def send_data_to_bdb(telemetry_data): } # record data to bigchain - tx_id = record_data(bdb, asset_data, asset_metadata, keypair, tx_id) + tx_id = record_data(bdb, asset_data, asset_metadata, keypair, tx_id, vehicle_id) logger.debug('tx_id: ' + tx_id) - current_app.config['tx_id'] = tx_id + current_app.config['txids'].update({vehicle_id: tx_id}) # end send_data_to_bdb diff --git a/hackfest/transactions.py b/hackfest/transactions.py index 83b9820..ed86e5c 100644 --- a/hackfest/transactions.py +++ b/hackfest/transactions.py @@ -12,13 +12,13 @@ TEL_SK, TEL_PK = generate_key_pair() -def create_asset(): +def create_asset(vw_sk, vw_pk, vehicle_id, data, metadata): # Create asset VW -> [VW, TEL] # Custom crypto condition multisig 1-2 threshold_fulfillment = ThresholdSha256Fulfillment(threshold=1) - vw_fulfillment = Ed25519Fulfillment(public_key=VW_PK) - tel_fulfillment = Ed25519Fulfillment(public_key=TEL_PK) + vw_fulfillment = Ed25519Fulfillment(public_key=vw_pk) + tel_fulfillment = Ed25519Fulfillment(public_key=vehicle_id) threshold_fulfillment.add_subfulfillment(vw_fulfillment) threshold_fulfillment.add_subfulfillment(tel_fulfillment) @@ -29,27 +29,29 @@ def create_asset(): 'details': threshold_fulfillment.to_dict(), 'uri': threshold_fulfillment.condition.serialize_uri() }, - 'public_keys': [VW_PK, TEL_PK] + 'public_keys': [vw_pk, vehicle_id] } # Create the transaction - tx_create = Transaction.create([VW_PK], [([VW_PK, TEL_PK], 1)]) + tx_create = Transaction.create([vw_pk], [([vw_pk, vehicle_id], 1)], + asset=data, metadata=metadata) # Override the condition we our custom build one tx_create.outputs[0] = Output.from_dict(output) # Sign the transaction - tx_create_signed = tx_create.sign([VW_SK]) + tx_create_signed = tx_create.sign([vw_sk]) return tx_create_signed -def create_transfer(prev_tx): +def create_transfer(prev_tx, vw_sk, vw_pk, vehicle_id, metadata, asset_id): + prev_tx = Transaction.from_dict(prev_tx) # Create asset VW -> [VW, TEL] # Custom crypto condition multisig 1-2 threshold_fulfillment = ThresholdSha256Fulfillment(threshold=1) - vw_fulfillment = Ed25519Fulfillment(public_key=VW_PK) - tel_fulfillment = Ed25519Fulfillment(public_key=TEL_PK) + vw_fulfillment = Ed25519Fulfillment(public_key=vw_pk) + tel_fulfillment = Ed25519Fulfillment(public_key=vehicle_id) threshold_fulfillment.add_subfulfillment(vw_fulfillment) threshold_fulfillment.add_subfulfillment(tel_fulfillment) @@ -60,7 +62,7 @@ def create_transfer(prev_tx): 'details': threshold_fulfillment.to_dict(), 'uri': threshold_fulfillment.condition.serialize_uri() }, - 'public_keys': [VW_PK, TEL_PK], + 'public_keys': [vw_pk, vehicle_id], } # The yet to be fulfilled input: @@ -70,14 +72,14 @@ def create_transfer(prev_tx): 'txid': prev_tx.id, 'output': 0, }, - 'owners_before': [VW_PK, TEL_PK], + 'owners_before': [vw_pk, vehicle_id], } # Craft the payload: transfer_tx = { 'operation': 'TRANSFER', - 'asset': {'id': prev_tx.id}, - 'metadata': None, + 'asset': {'id': asset_id}, + 'metadata': metadata, 'outputs': [output], 'inputs': [input_], 'version': '0.9', @@ -106,7 +108,7 @@ def create_transfer(prev_tx): threshold_fulfillment = ThresholdSha256Fulfillment(threshold=1) - vw_fulfillment.sign(message.encode(), private_key=Ed25519SigningKey(VW_SK)) + vw_fulfillment.sign(message.encode(), private_key=Ed25519SigningKey(vw_sk)) threshold_fulfillment.add_subfulfillment(vw_fulfillment) threshold_fulfillment.add_subcondition(tel_fulfillment.condition)