Despliegue De Web ÐApp Con Quorum+Angular+Python+Flask En Un VPS Con Ubuntu 16.04 [3]

En la tercera parte de la serie de artículos que cubre el desarrollo de una web ÐApp se dota del entorno Python que servirá como backend de la página web partiendo de las funciones definidas en Solidity.

Despliegue de web ÐApp con Quorum+Angular+Python+Flask en un VPS con Ubuntu 16.04
[1] [Quorum]
[2] [Solidity]
[3] [Python]
[4] [Angular]

La arquitectura definida para este proyecto contempla que cada nodo de la red sea gestionado por cada uno de los usuarios miembros de la misma. Los interfaces web se conectaran localmente al nodo del usuario.

Backend – Python

Se procede a instalar todos los paquetes necesarios tales como Python3 (3.5), virtualenv, pip, solc y git. Finalmente activamos el entorno virtual creado comprobando que los ejecutables de python y pip son los del entorno.

osboxes@osboxes:~$ sudo apt-get update
...
osboxes@osboxes:~$ sudo apt-get install python3-dev virtualenv python-pip solc git
...
osboxes@osboxes:~$ cd Desktop/giveliback
osboxes@osboxes:~/Desktop/giveliback$ source bin/activate
(giveliback) osboxes@osboxes:~/Desktop/giveliback$ which python
/home/osboxes/Desktop/giveliback/bin/python
(giveliback) osboxes@osboxes:~/Desktop/giveliback$ which pip
/home/osboxes/Desktop/giveliback/bin/pip

Python correrá en un servidor web con WSGI proporcionado por flask y gestionado por CherryPy. Así pues, se deben instalar dichas librerías y actualizar el gestor pip. Como el frontend es Angular, se necesitará el módulo flask-cors (CORS, Cross Origin Resource Sharing) para permitir la interactividad AJAX. La conectividad con Quorum se hará con la implementación de web3 para Python.

(giveliback) osboxes@osboxes:~/Desktop/giveliback$ pip install --upgrade pip
...
(giveliback) osboxes@osboxes:~/Desktop/giveliback$ pip install -U py-solc jupyter flask flask-cors cherrypy web3
...

Las versiones en uso para este ejemplo de software son

  • pip 18.0
  • solc 0.4.24
(giveliback) osboxes@osboxes:~/Desktop/giveliback$ pip --version
pip 18.0 from /home/osboxes/Desktop/giveliback/lib/python3.5/site-packages/pip (python 3.5)
(giveliback) osboxes@osboxes:~/Desktop/giveliback$ solc --version
solc, the solidity compiler commandline interface
Version: 0.4.24+commit.e67f0147.Linux.g++

Por lo que respecta a las librerías, entre otras:

  • CherryPy==15.0.0
  • Flask==1.0.2
  • pandas==0.23.1
  • py-solc==2.1.0
  • web3==4.4.1

Los contratos se almacenan en el directorio contracts. El directorio del proyecto alberga los archivos del servidor CherryPy y el gestor de endpoints de flask.

flask

En flask se definirán los endpoints de la aplicación web y que se basarán en gran medida en las funciones descritas en los smart contracts. Los endpoints se definen con @app.route y aportando la ruta URI que iniciará la función definida en esa ruta. Se ha optado por enumerar algunas de las funciones para simplificar el artículo:

  • /: página inicial
  • /compile_contracts: compilación de los contratos en la red
  • /manual_intance: instanciado del contrato ya desplegado
  • /create_book: creación de libro
  • /lend_book: prestar un libro a otro usuario
  • /return_book: devolver un libro al usuario propietario
  • /get_books_by_owner: ids de los libros de un usuario
  • /get_books_by_borrower: ids de los libros de un prestatario

La gestión de usuarios y almacenado de datos en una base de datos externa no se cubre en este artículo. Existen variedad de páginas web que permiten realizar búsquedas de los detalles de un libro a partir de un ISBN por lo que se puede externalizar a una llamada el guardado de tales datos.

flask es un buen servidor de páginas web que cumple con su función en entornos de prueba pero al que se le recomienda emparejar con un servidor más robusto en cuanto a carga y seguridad ejerciendo además de gestor de aplicaciones WSGI.

El servidor importa y expone la aplicación flask en el puerto 8888. Esta es la configuración para la web del nodo 1 ya que para el nodo 2 el puerto se define el 8889. Quorum tiene algunos sobresaltos con respecto al gas usado y la estimación de gas de las funciones por lo que es posible llegar fácilmente a erroresdel tipo ‘gas limit reached’. Se procede a dotar de un valor exageradamente alto mediante el parámetro --targetgaslimit 3758096384 en la ejecución del nodo. El valor, parejo al proporcionado en genesis.json ("gasLimit": "0xE0000000"), permite no llegar a la situación de que una transacción no se ejecute por no poder entrar en un bloque.

Esta solución es sólo a modo introductorio y de prueba.

/

La página inicial se cubrirá con el desarrollo de Angular. Se define como página de acceso base.html.

compile_contracts

Para la compilación y despliegue de los contratos se permite la ejecución por parte de la función create_contracts. Lo primero que se realiza es una comprobación para evaluar si el contrato está desplegado e instanciado en la sesión de Python mediante contract_instance. Tanto contract_instance como contract_address se definen como globales para poder modificar los valores de las mismas y ser accesibles para el resto de funciones.

En caso de no haber ninguna instancia de contrato, la función recupera los archivo del contrato GiveLibAck.sol que hereda de otros tres (Base.sol, ERC721.sol y BookShelf.sol) mediante compile_files. Una vez compilados, se recupera la definición ABI y el binario. En la variable contract_compiled (de tipo dict) habrá referencias a los contratos compilados y sus definiciones. Los que hacen referencia a GiveLibAck se encuentran en ‘contracts/GiveLibAck.sol:GiveLibAck’. A modo de ejemplo se muestran las interfaces de los cuatro contratos explicitadas.

Con las definiciones se compila el contrato habilitando al servidor minar la transacción. Una vez procesada la transacción, se recupera el hash de la misma para la identificación de la dirección del contrato (contract_address) a instaciar en el futuro. Muestra como retorno el hash de la transacción y el tipo de compilación con valores new o existing.

Se guarda en una variable la dirección del contrato para que no se despliegue más de una vez. La variable con la dirección del contrato sirve como filtro de ejecución de esta función. La gestión de la unicidad de despliegue de contratos es muy simplista pero funcional para este ejemplo.

En caso de que haya una instancia, remite a la dirección del despliegue del mismo.

manual_instance

En caso necesario, se puede instanciar el contrato desplegado anteriormente para poder seguir interactuando con el mismo. Para la ejecución de la función debe pasarse la dirección del contrato desplegado. La definición ABI del contrato está ya incluída en la función al ser conocida e inalterable. Devuelve si el contrato se ha cargado (Loaded) o no (Not loaded).

El resto de funciones precisa de una instancia del contrato activa para la ejecución de las mismas.

create_book

La ejecución se asigna a la cuenta principal del nodo local (web3.eth.accounts[0] y asignada a creator) tanto para el uso de gas como para la asignación del libro creado. La creación llama a la función createBook definida en BookShelf.sol. Devuelve un JSON con la información del hash de la transacción.

lend_book

Precisa como parámetros la dirección del destinatario y el libro que se presta. La dirección originaria de la ejecución y préstamo se toma como la principal del nodo local. Se ejecuta la función lendBook y devuelve un JSON con el hash de la transacción así como el estado de la ejecución (Lended o Not lended).

return_book

Se toma como dirección del origen de la devolución la principal del nodo local ya que sólo el prestatario puede devolver el libro. Esa dirección es la usada para la ejecución de la transacción. El identificador del libro es el único parámetro de la llamada de la función. Ejecuta returnBook y devuelve un JSON con el hash de la transacción así como el estado de la ejecución (Returned o Not returned).

get_books_by_owner

Llamada de consulta que en caso de no recibir el parámetro owner se asigna la cuenta principal del nodo local. Invoca la función getBooksByOwner y retorna un JSON con el array de los libros relacionados.

get_books_by_borrower

Llamada de consulta que en caso de no recibir el parámetro borrower se asigna la cuenta principal del nodo local. Invoca la función getBooksByBorrower y retorna un JSON con el array de los libros relacionados.

cherrypy

El archivo instancia la aplicación flask y la gestiona como WSGI en el puerto asignado.

Pruebas

Para las pruebas, se requerirán dos instancias de servidores web ya que cada una se conectará a un nodo. Las ejecuciones de cada servicio web se basarán en la cuenta principal ([0]) asemejando el caso de que cada usuario corre la aplicación sobre el nodo local que instancian.

El servidor del nodo1 (en el puerto 8888) se conecta por HTTP al nodo 1.

web3 = Web3(HTTPProvider('http://localhost:22000'))

Desde ese interfaz web se desplegará el contrato, creará un libro y lo cederá a otro usuario.

El servidor del nodo 2 (en el puerto 8889) se conecta por HTTP al nodo 2.

web3 = Web3(HTTPProvider('http://localhost:22001'))

Recordar que las cuentas principales del nodo 1 y nodo 2 son:

  • [Nodo 1] 0x3b6927fe4A4A4D44C3445292d375542CF299661C
  • [Nodo 2] 0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7

Se crean, pues, dos juegos de ficheros idénticos con la única diferencia de la asignación de puertos del interfaz web y de conexión al nodo local.

  • [Nodo 1] cherrypy_server.py y flask_server.py
  • [Nodo 2] cherrypy_server_2.py y flask_server_2.py

CherryPy (nodo 2)

flask (nodo 2)

El servidor del nodo 1 debe instanciarse para la ejecución de las pruebas. Lo mismo se hará más adelante con el nodo 2.

Se puede lanzar como comando python dentro del entorno virtual

(giveliback) osboxes@osboxes:~/Desktop/giveliback$ python cherrypy_server.py
[26/Jul/2018:19:21:48] ENGINE Bus STARTING
[26/Jul/2018:19:21:48] ENGINE Started monitor thread 'Autoreloader'.
[26/Jul/2018:19:21:48] ENGINE Serving on http://127.0.0.1:8888
[26/Jul/2018:19:21:48] ENGINE Bus STARTED

Compilación del contrato
http://localhost:8888/compile_contracts

Al llamar al endpoint compile_contracts, se compila y despliega el contrato al no haber ninguno disponible desde la ejecución de la instancia de Python. Esta es una aproximación simplista como ejemplo ilustrativo.

No contract deployed
Transaction hash: b'v\xc3\xb9yw*J\x0f\x0e\x8e\xed\x94\xae\xa9\xaa\xfc\xefJ\xed\x15\xda\xfa\x81w\xfe\xc7\xd2\x97U\xcf\xf9\xc9'
Type: <class 'hexbytes.main.HexBytes'>
Transaction hash (hex): 0x76c3b979772a4a0f0e8eed94aea9aafcef4aed15dafa8177fec7d29755cff9c9
Tx receipt: AttributeDict({'status': 1, 'cumulativeGasUsed': 1928526, 'to': None, 'contractAddress': '0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1', 'transactionIndex': 0, 'blockHash': HexBytes('0x9293fcd1cb93110130db64825080b62a59a2e0ceed814c730240028dcadfc336'), 'logs': [], 'gasUsed': 1928526, 'transactionHash': HexBytes('0x76c3b979772a4a0f0e8eed94aea9aafcef4aed15dafa8177fec7d29755cff9c9'), 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'from': '0x3b6927fe4a4a4d44c3445292d375542cf299661c', 'blockNumber': 33})
Contract address: 0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
The contract has been deployed at address 0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1

En el navegador aparece como respuesta el JSON con la dirección del contrato.

[{"contract_address":"0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1","tx_hash":"0x76c3b979772a4a0f0e8eed94aea9aafcef4aed15dafa8177fec7d29755cff9c9","type":"new"}]

En el Node1.log se aprecia la creación del mismo

INFO [07-26|19:23:26] Submitted contract creation fullhash=0x76c3b979772a4a0f0e8eed94aea9aafcef4aed15dafa8177fec7d29755cff9c9 to=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
INFO [07-26|19:23:26] QUORUM-CHECKPOINT name=TX-CREATED tx=0x76c3b979772a4a0f0e8eed94aea9aafcef4aed15dafa8177fec7d29755cff9c9 to=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
INFO [07-26|19:23:26] Generated next block block num=33 num txes=1
INFO [07-26|19:23:26] 🔨 Mined block number=33 hash=9293fcd1 elapsed=61.750669ms
INFO [07-26|19:23:26] QUORUM-CHECKPOINT name=TX-ACCEPTED tx=0x76c3b979772a4a0f0e8eed94aea9aafcef4aed15dafa8177fec7d29755cff9c9
INFO [07-26|19:23:26] Imported new chain segment blocks=1 txs=1 mgas=1.929 elapsed=384.505ms mgasps=5.016 number=33 hash=9293fc…dfc336
INFO [07-26|19:23:26] QUORUM-CHECKPOINT name=BLOCK-CREATED block=9293fcd1cb93110130db64825080b62a59a2e0ceed814c730240028dcadfc336
INFO [07-26|19:23:26] persisted the latest applied index index=44
INFO [07-26|19:23:26] Not minting a new block since there are no pending transactions

En Node2.log se certifica la transacción.

INFO [07-26|19:23:26] QUORUM-CHECKPOINT name=TX-ACCEPTED tx=0x76c3b979772a4a0f0e8eed94aea9aafcef4aed15dafa8177fec7d29755cff9c9
INFO [07-26|19:23:26] Imported new chain segment blocks=1 txs=1 mgas=1.929 elapsed=395.482ms mgasps=4.876 number=33 hash=9293fc…dfc336
INFO [07-26|19:23:26] QUORUM-CHECKPOINT name=BLOCK-CREATED block=9293fcd1cb93110130db64825080b62a59a2e0ceed814c730240028dcadfc336
INFO [07-26|19:23:26] persisted the latest applied index index=44

Al ejecutar de nuevo la compilación de contrato, el servidor responde que hay ya un contrato en activo.

The contract has been deployed at address 0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1

Por pantalla muestra que el contrato ya es existente.

[{"contract_address":"0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1","type":"existing"}]

Creación de un libro
http://localhost:8888/create_book?book_name=2666&isbn=9788420423920

El siguiente paso es la creación de un libro. Siguiendo el ejemplo usado en Remix, se crea el libro 2666 de Roberto Bolaño con su ISBN.

En la consola aparece el hash de la transacción así como el retorno del recibo de la misma.

Transaction hash: b'\xe5\xbdE\xacg\x0bO\x13\x02m\xd3_+\xe2\x8c[\xf8$\xff\xb8\xdb\x9aH\x86(\x9f}w\xc9\x07)\x9f'
Type: <class 'hexbytes.main.HexBytes'>
Transaction hash (hex): 0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f
AttributeDict({'status': 1, 'cumulativeGasUsed': 116438, 'to': '0x4b1b7e59f2e2c2e67a91f23f52288bff0f76ade1', 'contractAddress': None, 'transactionIndex': 0, 'blockHash': HexBytes('0xd2a0fc1038f4985619548e26814ce4db659d8698a4507ee92d75fd056398c039'), 'logs': [AttributeDict({'removed': False, 'transactionHash': HexBytes('0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f'), 'data': '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008e70b5280f0', 'transactionIndex': 0, 'blockHash': HexBytes('0xd2a0fc1038f4985619548e26814ce4db659d8698a4507ee92d75fd056398c039'), 'topics': [HexBytes('0xf7e1dd4a7b905ff00f16c1281183f49592b87c112782803b7db506590de8f8d2')], 'logIndex': 0, 'address': '0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1', 'blockNumber': 34})], 'gasUsed': 116438, 'transactionHash': HexBytes('0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f'), 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000001000000000000080000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000'), 'from': '0x3b6927fe4a4a4d44c3445292d375542cf299661c', 'blockNumber': 34})
AttributeDict({'value': 0, 's': HexBytes('0x75b5e50e701382d0e7e48c15fa39add2afa0c8b04d671f7c9b1d0a05be346392'), 'to': '0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1', 'gasPrice': 0, 'transactionIndex': 0, 'hash': HexBytes('0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f'), 'blockHash': HexBytes('0xd2a0fc1038f4985619548e26814ce4db659d8698a4507ee92d75fd056398c039'), 'v': 27, 'nonce': 34, 'gas': 3740000000, 'input': '0xd7c49dfa000000000000000000000000000000000000000000000000000008e70b5280f0', 'from': '0x3b6927fe4A4A4D44C3445292d375542CF299661C', 'r': HexBytes('0xc005a6807818977c390847b2a36272fbc0c4ce063c223692c402a4910c4a301e'), 'blockNumber': 34})
book_name creator isbn status token_id tx_hash
0 2666 0x3b6927fe4A4A4D44C3445292d375542CF299661C 9788420423920 Created None 0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a...

Por pantalla devuelve la creación del libro

[{"book_name":"2666","creator":"0x3b6927fe4A4A4D44C3445292d375542CF299661C","isbn":9788420423920,"status":"Created","token_id":null,"tx_hash":"0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f"}]

En Node1.log se aprecia la creación del libro

INFO [07-26|19:27:48] Submitted transaction fullhash=0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f recipient=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
INFO [07-26|19:27:48] QUORUM-CHECKPOINT name=TX-CREATED tx=0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f to=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
INFO [07-26|19:27:48] Generated next block block num=34 num txes=1
INFO [07-26|19:27:48] 🔨 Mined block number=34 hash=d2a0fc10 elapsed=19.645284ms
INFO [07-26|19:27:48] QUORUM-CHECKPOINT name=TX-ACCEPTED tx=0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f
INFO [07-26|19:27:48] Imported new chain segment blocks=1 txs=1 mgas=0.116 elapsed=56.558ms mgasps=2.059 number=34 hash=d2a0fc…98c039
INFO [07-26|19:27:48] QUORUM-CHECKPOINT name=BLOCK-CREATED block=d2a0fc1038f4985619548e26814ce4db659d8698a4507ee92d75fd056398c039
INFO [07-26|19:27:48] persisted the latest applied index index=45
INFO [07-26|19:27:48] TX failed, will be removed hash=e5bd45…07299f err="nonce too low"
INFO [07-26|19:27:48] Not minting a new block since there are no pending transactions

Así como en Node2.log

INFO [07-26|19:27:48] QUORUM-CHECKPOINT name=TX-ACCEPTED tx=0xe5bd45ac670b4f13026dd35f2be28c5bf824ffb8db9a4886289f7d77c907299f
INFO [07-26|19:27:48] Imported new chain segment blocks=1 txs=1 mgas=0.116 elapsed=38.692ms mgasps=3.009 number=34 hash=d2a0fc…98c039
INFO [07-26|19:27:48] QUORUM-CHECKPOINT name=BLOCK-CREATED block=d2a0fc1038f4985619548e26814ce4db659d8698a4507ee92d75fd056398c039
INFO [07-26|19:27:48] persisted the latest applied index index=45

Parece que hay un bug en Quorum con respecto al nonce aunque no afecta en la ejecución de este ejemplo.

Listado de libros de un usuario
http://localhost:8888/get_books_by_owner

Una vez se ha creado el libro por la cuenta principal del nodo 1 (0x3b6927fe4A4A4D44C3445292d375542CF299661C), se puede proceder a comprobar si dicho libro está relacionado con el usuario. Al no proporcionar ninguna dirección, toma la primera cuenta del nodo local que coincide con la de la propietaria del libro.

La función se ejecuta como llamada sin efectuar registro en la cadena de bloques al ser de lectura.

En la consola aparece

[0]
owner token_ids
0 0x3b6927fe4A4A4D44C3445292d375542CF299661C [0]

Así como por pantalla

[{"owner":"0x3b6927fe4A4A4D44C3445292d375542CF299661C","token_ids":[0]}]

Préstamo del libro
http://localhost:8888/lend_book?to=0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7&tokenId=0

Habiendo creado el libro, se procede al préstamo del mismo a la cuenta del nodo 2 (0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7).

En la consola aparece la ejecución sin problemas.

Transaction hash: b'\xaei\x14a\x9dvl\x10\x137]\xa1\xcd\xcb\xd4\xa1\x1a8\x07JR\x8a\x8c"\n\xbf\xe7\xa9zB^}'
Transaction hash (hex): 0xae6914619d766c1013375da1cdcbd4a11a38074a528a8c220abfe7a97a425e7d
AttributeDict({'status': 1, 'cumulativeGasUsed': 73808, 'to': '0x4b1b7e59f2e2c2e67a91f23f52288bff0f76ade1', 'contractAddress': None, 'transactionIndex': 0, 'blockHash': HexBytes('0x12a54993aca025cd0bb4c733b3f3e3196df77f2582d01a3a1cea01d25d62d55c'), 'logs': [AttributeDict({'removed': False, 'transactionHash': HexBytes('0xae6914619d766c1013375da1cdcbd4a11a38074a528a8c220abfe7a97a425e7d'), 'data': '0x0000000000000000000000000000000000000000000000000000000000000000', 'transactionIndex': 0, 'blockHash': HexBytes('0x12a54993aca025cd0bb4c733b3f3e3196df77f2582d01a3a1cea01d25d62d55c'), 'topics': [HexBytes('0x11dd72a5d477527ee1cbe309212bb368745a98cf1dc3befcac7eef4988a957c2'), HexBytes('0x0000000000000000000000003b6927fe4a4a4d44c3445292d375542cf299661c'), HexBytes('0x000000000000000000000000513f15ec9fc190cbc2ac25c6d6acdb58253f80d7')], 'logIndex': 0, 'address': '0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1', 'blockNumber': 35})], 'gasUsed': 73808, 'transactionHash': HexBytes('0xae6914619d766c1013375da1cdcbd4a11a38074a528a8c220abfe7a97a425e7d'), 'logsBloom': HexBytes('0x00010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000400000000000000000000000000000000000200000000000000000001004000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000800000000'), 'from': '0x3b6927fe4a4a4d44c3445292d375542cf299661c', 'blockNumber': 35})
issuer status to token_ids
0 0x3b6927fe4A4A4D44C3445292d375542CF299661C Lended 0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7 0

Por pantalla devuelve la ejecución.

[{"issuer":"0x3b6927fe4A4A4D44C3445292d375542CF299661C","status":"Lended","to":"0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7","token_ids":0}]

En Node1.log se aprecia el prestamo.

INFO [07-26|19:39:46] Submitted transaction fullhash=0xae6914619d766c1013375da1cdcbd4a11a38074a528a8c220abfe7a97a425e7d recipient=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
INFO [07-26|19:39:46] QUORUM-CHECKPOINT name=TX-CREATED tx=0xae6914619d766c1013375da1cdcbd4a11a38074a528a8c220abfe7a97a425e7d to=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
INFO [07-26|19:39:46] Generated next block block num=35 num txes=1
INFO [07-26|19:39:46] 🔨 Mined block number=35 hash=12a54993 elapsed=12.982208ms
INFO [07-26|19:39:46] QUORUM-CHECKPOINT name=TX-ACCEPTED tx=0xae6914619d766c1013375da1cdcbd4a11a38074a528a8c220abfe7a97a425e7d
INFO [07-26|19:39:46] Imported new chain segment blocks=1 txs=1 mgas=0.074 elapsed=66.984ms mgasps=1.102 number=35 hash=12a549…62d55c
INFO [07-26|19:39:46] QUORUM-CHECKPOINT name=BLOCK-CREATED block=12a54993aca025cd0bb4c733b3f3e3196df77f2582d01a3a1cea01d25d62d55c
INFO [07-26|19:39:46] persisted the latest applied index index=46
INFO [07-26|19:39:46] Not minting a new block since there are no pending transactions

En Node2.log se confirma la transacción.

INFO [07-26|19:39:46] QUORUM-CHECKPOINT name=TX-ACCEPTED tx=0xae6914619d766c1013375da1cdcbd4a11a38074a528a8c220abfe7a97a425e7d
INFO [07-26|19:39:46] Imported new chain segment blocks=1 txs=1 mgas=0.074 elapsed=61.377ms mgasps=1.203 number=35 hash=12a549…62d55c
INFO [07-26|19:39:46] QUORUM-CHECKPOINT name=BLOCK-CREATED block=12a54993aca025cd0bb4c733b3f3e3196df77f2582d01a3a1cea01d25d62d55c
INFO [07-26|19:39:46] persisted the latest applied index index=46

Listado de libros prestados a un usuario
http://localhost:8888/get_books_by_borrower?borrower=0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7

La ejecución de get_books_by_borrower pretende asegurar que se ha registrado el préstamo correctamente con una llamada tipo call() muy similar a la realizada con get_books_by_owner.

En la consola aparece

[0]
borrower token_ids
0 0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7 [0]

Por el navegador aparece

[{"borrower":"0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7","token_ids":[0]}]

Confirma que el libro está en préstamo con el usuario del nodo 2.

A partir de este momento, se instancia el servidor web apuntado al nodo 2.

(giveliback) osboxes@osboxes:~/Desktop/giveliback$ python cherrypy_server_2.py
[26/Jul/2018:19:44:53] ENGINE Bus STARTING
[26/Jul/2018:19:44:53] ENGINE Started monitor thread 'Autoreloader'.
[26/Jul/2018:19:44:53] ENGINE Serving on http://127.0.0.1:8889
[26/Jul/2018:19:44:53] ENGINE Bus STARTED

Despliegue manual del contrato
http://localhost:8889/manual_instance?contract_address=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1

El contrato se ha desplegado e instanciado en la ejecución del servidor web de python del nodo 1. La instancia de python conectada al nodo 2 no dispone del contrato instanciado. Lo primero que debe hacerse es instanciar el contrato que se ha desplegado anteriormente mediante la función manual_instance.

Al conocer la dirección en la que está desplegado el contrato y disponer de la definición del ABI, el contrato puede instanciarse. Habitualmente entre los ejemplos que abundan por Internet se instancian los contratos con el ABI y el binario. Es preferible instanciar desde el contrato desplegado como ejecución formal y porque se pretende interactuar con la cadena de bloques relacionada con dicho contrato.

En la consola se confirma que se instancia el contrato

contract instance None
No contract instance available
contract address: 0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
Gas limit: 3758096384
contract_address status
0 0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1 Loaded
[{"contract_address":"0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1","status":"Loaded"}]

El navegador lo confirma con la salida JSON.

[{"contract_address":"0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1","status":"Loaded"}]

Devolución del libro
http://localhost:8889/return_book?tokenId=0

La ejecución de la devolución del libro debe hacerse desde el nodo del prestatario y, por lo tanto, desde el nodo 2.

En la consola aparece la ejecución de la transacción.

Transaction hash: b"\x8d\xaa=JR\xa7A\x0b\x92d\xa7\x15<\xdd\x9e\xfe\xb5\xa2\xd9&0'n\x03\xe1\xc2*\\i\xfa~9"
Transaction hash (hex): 0x8daa3d4a52a7410b9264a7153cdd9efeb5a2d92630276e03e1c22a5c69fa7e39
AttributeDict({'to': '0x4b1b7e59f2e2c2e67a91f23f52288bff0f76ade1', 'transactionHash': HexBytes('0x8daa3d4a52a7410b9264a7153cdd9efeb5a2d92630276e03e1c22a5c69fa7e39'), 'from': '0x513f15ec9fc190cbc2ac25c6d6acdb58253f80d7', 'cumulativeGasUsed': 17635, 'gasUsed': 17635, 'status': 1, 'logsBloom': HexBytes('0x00010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000200000000000000000001004000000000000000000000000000000000000000000200000000000000000000000000000000000008000000000000000000040000000000000000800000000'), 'transactionIndex': 0, 'blockNumber': 36, 'logs': [AttributeDict({'transactionHash': HexBytes('0x8daa3d4a52a7410b9264a7153cdd9efeb5a2d92630276e03e1c22a5c69fa7e39'), 'logIndex': 0, 'topics': [HexBytes('0x75457583d91e0597e8497ed04c08e88e3fb24d8b586a60de172cef7186b67318'), HexBytes('0x000000000000000000000000513f15ec9fc190cbc2ac25c6d6acdb58253f80d7'), HexBytes('0x0000000000000000000000003b6927fe4a4a4d44c3445292d375542cf299661c')], 'address': '0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1', 'removed': False, 'blockNumber': 36, 'transactionIndex': 0, 'data': '0x0000000000000000000000000000000000000000000000000000000000000000', 'blockHash': HexBytes('0x3cfa540429f4af0025e611b3e8ee451507c2862f3dbecd45055db9e85b091479')})], 'contractAddress': None, 'blockHash': HexBytes('0x3cfa540429f4af0025e611b3e8ee451507c2862f3dbecd45055db9e85b091479')})
issuer status token_id
0 0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7 Returned 0

En el navegador devuelve los datos del retorno

[{"issuer":"0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7","status":"Returned","token_id":0}]

En Node1.log aparece el minado de la transacción aunque se ha ejecutado desde el nodo 2 al ser MINTER.

INFO [07-26|19:52:58] Generated next block block num=36 num txes=1
INFO [07-26|19:52:58] 🔨 Mined block number=36 hash=3cfa5404 elapsed=34.850916ms
INFO [07-26|19:52:58] QUORUM-CHECKPOINT name=TX-ACCEPTED tx=0x8daa3d4a52a7410b9264a7153cdd9efeb5a2d92630276e03e1c22a5c69fa7e39
INFO [07-26|19:52:58] Imported new chain segment blocks=1 txs=1 mgas=0.018 elapsed=468.868ms mgasps=0.038 number=36 hash=3cfa54…091479
INFO [07-26|19:52:58] QUORUM-CHECKPOINT name=BLOCK-CREATED block=3cfa540429f4af0025e611b3e8ee451507c2862f3dbecd45055db9e85b091479
INFO [07-26|19:52:58] persisted the latest applied index index=47
INFO [07-26|19:52:58] TX failed, will be removed hash=8daa3d…fa7e39 err="nonce too low"
INFO [07-26|19:52:58] Not minting a new block since there are no pending transactions

En Node2.log aparece la transacción.

INFO [07-26|19:52:58] Submitted transaction fullhash=0x8daa3d4a52a7410b9264a7153cdd9efeb5a2d92630276e03e1c22a5c69fa7e39 recipient=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
INFO [07-26|19:52:58] QUORUM-CHECKPOINT name=TX-CREATED tx=0x8daa3d4a52a7410b9264a7153cdd9efeb5a2d92630276e03e1c22a5c69fa7e39 to=0x4B1b7e59F2e2C2E67A91F23F52288bFF0F76ade1
INFO [07-26|19:52:58] QUORUM-CHECKPOINT name=TX-ACCEPTED tx=0x8daa3d4a52a7410b9264a7153cdd9efeb5a2d92630276e03e1c22a5c69fa7e39
INFO [07-26|19:52:58] Imported new chain segment blocks=1 txs=1 mgas=0.018 elapsed=475.381ms mgasps=0.037 number=36 hash=3cfa54…091479
INFO [07-26|19:52:58] QUORUM-CHECKPOINT name=BLOCK-CREATED block=3cfa540429f4af0025e611b3e8ee451507c2862f3dbecd45055db9e85b091479
INFO [07-26|19:52:58] persisted the latest applied index index=47

Listado de libros prestados a un usuario
http://localhost:8889/get_books_by_borrower

Ejecutando desde el nodo 2 la lista de libros del prestatario, no aparece ya ningún libro en su haber. Al estar ejecutado desde el nodo 2, se toma como cuenta la principal de dicho nodo que coincide con la cuenta de interés.

Por consola

[]
borrower token_ids
0 0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7 []

En el navegador

[{"borrower":"0x513F15Ec9fc190cBc2Ac25C6D6aCDB58253F80d7","token_ids":[]}]

El proceso de préstamo de despliegue de un contrato, creación de un libro, su cesión y devolución de forma correcta.

El último paso en el desarrollo de la web ÐApp será la integración con Angular para la interacción con el usuario.