Cadena de suministro usando Lisk, guia de instalacion y configuracion

julio 11, 2020 VICTOR HUGO LAZARTE 0 Comments


Blockchain de Lisk en la gestión de la cadena de suministro, informacion avanzada para desarrolladores

Parte 0: Instalación y configuración

Este documento explica cómo preparar su computadora para el taller.

Nota: para visualizar mejor los scrolls y desarrollo de codigos se aconseja verlo desde una PC.

Requisitos

Hardware

🔴 Computadora.

🔴 Raspberry Pi Zero W, (ejecutando un Raspbian Buster lite sin cabeza).berry Pi Zero W, (ejecutando un Raspbian Buster lite sin cabeza).

🔴 Tarjeta SD de clase 10.

🔴 Un sensor de luz analógico genérico basado en un LDR.

🔴 Cables de salto.

🔴 USB <→ Cable micro-USB.

Requisitos previos del software

Requisitos básicos para todas las máquinas ( guía de instalación de Lisk ):

🔴 Un editor de código como Visual Studio Code.

🔴 Git.

🔴 Python 2.

🔴 Node.js v12(se usa nvmpara cambiar fácilmente entre versiones en caso de que se instale una versión diferente).

🔴 Postgres 10.

1.0 Configuración

Configure el software y el hardware aplicables y la instalación de las dependencias.

Clonar el repositorio e instalar las dependencias

Los siguientes pasos que se enumeran a continuación describen cómo crear los archivos iniciales para este tutorial:

  1. Clone el repositorio Lisk-SDK-examples localmente ejecutando los siguientes comandos:

    git clone https://github.com/LiskHQ/lisk-sdk-examples.git
    cd lisk-sdk-examples
    cd transport

    La estructura de archivo básica se enumera a continuación:Contenido de lisk-sdk-examples/transport

.
├── README.adoc
├── Workshop.adoc
├── cliente                                          
│ ├── cuentas.json
│ ├── app.js
│ ├── paquete.json
│ ├── guiones
│ └── vistas
├── iot                                             
│ ├── README.md
│ ├── luz_alarma
│ │ ├── paquete.json
│ │ └── index.js
├── nodo                                            
│ ├── index.js
│ └── paquete.json
└── transacciones                                    
    ├── finish-transport.js
    ├── luz-alarma.js
    ├── register-packet.js
    └── start-transport
Contiene el código de la aplicación cliente.
Contiene el código de la aplicación de IoT.
Contiene el código de la aplicación de nodo.
Contiene las transacciones personalizadas que utilizan el nodo y la aplicación cliente.

Navegue dentro de las carpetas transport/transactionsy transport/node, luego ejecute npm installpara instalar las dependencias requeridas para la aplicación de nodo como se muestra a continuación:

Este comando ejecutará el index.jsarchivo y canalizará los registros generados a la herramienta de formato de registro preferida Bunyan. Tenga en cuenta que, al iniciar el nodo por primera vez, esto puede requerir algunos minutos.

Si todo está funcionando correctamente, el nodo se puede detener presionando CTRL+C .

En caso de que el nodo falle o no funcione correctamente, siga esta guía para configurar un entorno de desarrollo dedicado usando Docker y Docker-Compose. El script configurará todo el entorno. Si se siguen correctamente las instrucciones de esta guía, se puede omitir el resto de este documento.

Prepare el dispositivo de IoT

Ejemplo 1. Referencia: Configuración de Raspberry Pi

Preparación del sistema operativo

El sistema operativo de la Raspberry debe copiarse de una computadora a una tarjeta SD para esto, recomendamos usar etcher (buen tutorial en medio )

Para preparar la Raspberry sshy el acceso wifi, siga esta guía: tutorial de pi .

Como queremos ejecutar la Raspberry en modo sin cabeza (es decir, sin teclado, mouse y monitor), también debemos habilitarlo a sshtravés de USB. Para hacerlo, siga esta guía ssh sobre usb . Una vez que se siguen todos esos pasos, la tarjeta SD se puede desmontar e insertar en el lector de tarjetas SD Raspberry.

Bibliotecas ya instaladas para acceder a datos de sensores

Los pines de la Raspberry necesitan algunas bibliotecas antes de que puedan usarse para comunicarse con diferentes sensores. Para instalar las bibliotecas necesarias, ejecute:

  • 🔴 sudo apt-get install wiringpi

  • 🔴 sudo apt-get install pigpio

  • 🔴 Node.js se puede instalar con nvm

Conexión / inicio de sesión en la Pi

Para iniciar sesión en el Pi, conéctelo usando el puerto usb etiquetado como usb, espere aproximadamente un minuto para que se inicie (la luz verde en el Pi dejará de parpadear cuando termine de arrancar) y luego abra una terminal y:

  • 🔴 ssh pi@raspberrypi.local

  • 🔴 Escriba la contraseña; de forma predeterminada, es raspberry

Parte 1: rastrear un paquete en la cadena de bloques

El objetivo de la Parte 1 es implementar una aplicación simple que rastree las mediciones de los sensores en la cadena de bloques. Por lo tanto, una vez que se inicia la aplicación IoT, enviará inmediatamente un mensaje LightAlarmTransactiona la red, siempre que el sensor detecte luz.

Los siguientes puntos a continuación se tratan en esta sección

🔴 Cómo implementar LightAlarmTransaction.

🔴 Cómo registrar el nuevo tipo de transacción con la aplicación de nodo.

🔴 Cómo crear el script de IoT y cómo colocarlo en la Raspberry Pi.

🔴 Cómo usar la aplicación del cliente para inicializar la cuenta del paquete y rastrear las transacciones de alarma en la red.

Consulte las soluciones en / transaction / solutions para las transacciones personalizadas.

También es posible 'requerir' las soluciones directamente en su aplicación simplemente cambiando lo siguiente:

const LightAlarmTransaction = require('../transactions/light-alarm')

a esto:

const LightAlarmTransaction = require('../transactions/solutions/light-alarm

Arquitectura del proyecto

A continuación, se enumeran los tres tipos diferentes de aplicaciones que deben desarrollarse para crear el sistema de cadena de suministro descentralizado:

Una aplicación de nodo

Esto aceptará los tipos de transacciones específicos de la aplicación. Esta aplicación debe instalarse en diferentes nodos independientes y configurará y mantendrá la cadena de bloques que se utiliza para almacenar los datos de los paquetes, el operador y los usuarios.

Una aplicación de cliente

Esto mostrará información de la cadena de bloques al usuario. Se requiere una interfaz que debería mostrar una lista de ID de paquete. Debe constar del transportista, el remitente, el destinatario y un campo de estado, 

pending | ongoing

 | alarm | success 

| fail). 

Además, también debería proporcionar una forma sencilla de crear y enviar los diferentes tipos de transacciones a la red.

Una aplicación de IoT

Esto se almacena en un microcontrolador / raspberry pi. Esta aplicación hará un seguimiento de que el paquete no se manipula durante la entrega. Para lograr esto, se conectarán ciertos sensores que rastrean información, como luz, temperatura y / o humedad dentro del paquete. Si la aplicación de IoT detecta algo inesperado, creará un objeto de transacción, lo firmará y lo enviará a la red.

La estructura básica del archivo se puede ver a continuación:

 (contenido de lisk-sdk-examples/transport ): 

├── README.adoc
├── Workshop.adoc
├── cliente                                          
│ ├── cuentas.json
│ ├── app.js
│ ├── paquete.json
│ ├── guiones
│ └── vistas
├── iot                                             
│ ├── README.md
│ ├── lisk_rpi_ldr_and_temperature_sensors_wiring.png
│ ├── luz_alarma
│ │ ├── paquete.json
│ │ └── index.js
├── nodo                                            
│ ├── index.js
│ └── paquete.json
└── transacciones                                    
    ├── finish-transport.js
    ├── luz-alarma.js
    ├── register-packet.js
    └── start-transport.js
Contiene el código de la aplicación cliente .
Contiene el código de la aplicación de IoT .
Contiene el código de la aplicación de nodo .
Contiene las transacciones personalizadas que utilizan el nodo y la aplicación cliente.

1.1 Implementar la transacción LightAlarm

Para una versión simple de seguimiento de paquetes, solo se necesita implementar una transacción personalizada, el LightAlarmTransactionEsta transacción será enviada por el dispositivo IoT dentro del paquete cuando detecte anomalías con su fotorresistor conectado (detección de luz).

En este siguiente paso sólo las validateAssetnecesidades de función para ser implementados, validateAssetPara obtener más detalles, consulte la explicación a continuación . La implementación de la función validateAsset()  se puede lograr fácilmente utilizando el siguiente código que se enumera a continuación:

Contenido de /transactions/light-alarm.js

const {
    BaseTransaction,
    TransactionError,
} = require('@liskhq/lisk-transactions');

/**
 * Send light alarm transaction when the packet has been opened (accepts timestamp).
 * Self-signed by packet.
 * The `LightAlarmTransaction` is extended from the `BaseTransaction` interface.
 */
class LightAlarmTransaction extends BaseTransaction {

    /* Static property that defines the transaction `type` (has to be unique in the network). */
    static get TYPE () {
        return 23;
    }

    /* The transaction `fee`. This needs to be paid by the sender when posting the transaction to the network.
       It is set to `0`, so the packet does not need any funds to send an alarm transaction. */
    static get FEE () {
        return '0';
    };

    /* Data from the packet account is cached from the databse. */
    async prepare(store) {
        await store.account.cache([
            {
                address: this.senderId,
            }
        ]);
    }

    /* Static checks for presence and correct datatype of `timestamp`, which holds the timestamp of when the alarm was triggered. */
    validateAsset() {
        const errors = [];
        /*
        Implement your own logic here.
        Static checks for presence of `timestamp` which holds the timestamp of when the alarm was triggered
        */

        return errors;
    }

    applyAsset(store) {
        /* Insert the logic for applyAsset() here */
    }

    undoAsset(store) {
        const errors = [];
        const packet = store.account.get(this.senderPublicKey);

        /* --- Revert packet status --- */
        packet.asset.status = null;
        packet.asset.alarms.light.pop();

        store.account.set(packet.address, packet);
        return errors;
    }

}

module.exports = LightAlarmTransaction;

Consulte la documentación de Lisk para obtener una descripción 

general sobre los métodos necesarios para las transacciones personalizadas

1.1.1 Tarea: Implementar validateAsset() 

Implemente su propia lógica para la funciónvalidateAsset() aquí en la línea 31 . El código validará la marca de tiempo que ha enviado LightAlarmTransactionEn caso de que se encuentre un error, inserte uno nuevaTransactionErroren la matrizerrors y devuélvalo al final de la función

Todos los datos que se envían con la transacción están disponibles a través de la thisvariable. Por lo tanto, para acceder a la marca de tiempo de la transacción, utilice this.timestamp.

El siguiente fragmento describe cómo crear un objeto TransactionErrorIntente agregar un ajuste TransactionErrora la errorslista de validateAsset(), en el caso de que la marca de tiempo no esté presente, o si tiene un formato incorrecto.

El tipo de datos esperado para la marca de tiempo es number!:

Ejemplo: a continuación TransactionError se muestra cómo crear un objeto


new TransactionError(
	'Invalid "asset.hello" defined on transaction',
	this.id,
	'.asset.hello',
	this.asset.hello,
	'A string value no longer than 64 characters',
)

Si se requiere más información con respecto a la implementación de la función validateAsset(), consulte los otros ejemplos, como hello_worlddentro del lisk-sdk-examplesrepositorio. Alternativamente, consulte los tutoriales en la documentación de Lisk.

Para verificar la implementación de validateAsset(), compárelo con la solución 

TAREA

Copie el fragmento a continuación y reemplace la función applyAssetlight-alarm.js para completar la implementación de lightAlarmTransaction.

/*Inside of `applyAsset`,

it is possible to utilise

the cached data

from the `prepare` function, * which is stored inside

of the `store` parameter.*/ applyAsset(store) { const errors = []; /* With `store.account.

get(ADDRESS)` the account

data of the packet

account can be seen. * `this.senderId`

is specified as an address,

due to the fact that

the light alarm is always

signed and sent by

the packet itself. */ const packet =

store.account.get(this.senderId); /** * Update the Packet

account: * - set packet status

to "alarm" * - add current timestamp

to light alarms list */ packet.asset.status = 'alarm'; packet.asset.alarms =

packet.asset.alarms ?

packet.asset.alarms : {}; packet.asset.alarms.light =

packet.asset.alarms.light ?

packet.asset.alarms.light : []; packet.asset.alarms.light.

push(this.timestamp); /* When all changes have been made

they are applied to the

database by executing `

store.account.set(ADDRESS, DATA)`; */ store.account.set(packet.address,

packet); /* Unlike in `validateAsset`,

the `store` parameter is present here. * Therefore inside of `applyAsset`

it is possible to make dynamic


checks against the existing data in

the database. * As this is not required here,

an empty `errors` array is returned

at the end of the function. */ return errors; }

1.1.2 Tarea: Implementar validateAsset()


La función applyAsset informa a la cadena de bloques qué cambios deben realizarse y cómo se puede cambiar la cuenta de un usuario. Esto contiene la lógica comercial central de sus transacciones personalizadas. A continuación se muestra un ejemplo que muestra la implementación de applyAssetfor the LightAlarmTransaction:
TAREA
Copie el fragmento a continuación y reemplace la función applyAsset light-alarm.js para completar la implementación de lightAlarmTransaction.

1.1.3 Registrar la transacción con la aplicación


Dado que  se ha creado una nueva transacción personalizada 
LightAlarmTransaction , debe registrarse con la aplicación de nodo. Sin este paso, los nodos no tendrán la lógica para validar a LightAlarmTransactiony, por lo tanto, la transacción se descartará.
Consulte el código en el node/index.jsque se registra LightAlarmTransaction en la aplicación blockchain como se muestra a continuación:
const { Application, genesisBlockDevnet, configDevnet } = require('lisk-sdk');
const LightAlarmTransaction = require('../transactions/light-alarm');           

configDevnet.app.label = 'lisk-transport';

const app = new Application(genesisBlockDevnet, configDevnet);

app.registerTransaction(LightAlarmTransaction);                                 

app
    .run()
    .then(() => app.logger.info('App started...'))
    .catch(error => {
        console.error('Faced error in application', error);
        process.exit(1);
    });

Requiere la transacción personalizada.

Registra la transacción personalizada con la aplicación.
Después del registro de un nuevo
tipo de transacción,
el nodo debe
reiniciarse para aplicar los
cambios con 
node index.js | npx
bunyan -o short
Asegúrese
de que este comando
 se ejecute
dentro de la 
node/carpeta.
1.2 La aplicación de IoT

En este paso, se creará un script que se ejecutará en la Raspberry Pi para rastrear si el paquete ha sido manipulado.

1.2.1 Conectarse a la Raspberry Pi

Para simplificar la topología de la red para el taller, se configuró un servidor DHCP en la Raspberry Pi que asignará una dirección IP a su computadora usando una ethernet virtual a través de un puerto USB. La Raspberry Pi tendrá el nombre raspberrypi.localde host por defecto.

Conecte un cable micro usb con la Raspberry Pi y luego conecte el otro extremo del cable a una computadora.

Asegúrese de que el cable micro USB esté conectado al puerto que se ha usbgrabado en la placa de circuito impreso, como se muestra en el siguiente diagrama:

Cómo conectarse a su Pi

Para iniciar sesión usando sshdesde un terminal, ejecute el pingcomando que se indica a continuación: Esto comenzará a hacer ping a la Raspberry Pi que generará las respuestas de retorno.

ping raspberrypi.local

Salida de ejemplo de hacer ping a la Raspberry Pi:

Request timeout for icmp_seq 79
Request timeout for icmp_seq 80
Request timeout for icmp_seq 81
Request timeout for icmp_seq 82
Request timeout for icmp_seq 83
Request timeout for icmp_seq 84
64 bytes from raspberrypi.local: icmp_seq=85 ttl=64 time=0.952 ms
64 bytes from raspberrypi.local: icmp_seq=86 ttl=64 time=0.677 ms

Una vez que se recibe la respuesta, como se puede ver en las últimas 2 líneas arriba, entonces se puede ejecutar el siguiente comando:

ssh pi@raspberrypi.local

Si se le solicita con una advertencia, presione Intro para aceptar el valor predeterminado, (Sí).

Ahora debería aparecer el mensaje para una contraseña, ingrese la contraseña para la Raspberry Pi.

Su terminal ahora debería estar conectado a la Raspberry Pi, por lo que la preparación se puede iniciar como se describe a continuación:

1.2.2 Crear el script de seguimiento

Ejecute los comandos que se enumeran a continuación para crear el script de seguimiento:

mkdir light_alarm #Create a folder to hold the tracking script.
cd light_alarm
npm init --yes #Creates the `package.json` file.
npm i @liskhq/lisk-transactions @liskhq/lisk-api-client @liskhq/lisk-constants rpi-pins #Install dependencies

Ahora, cree un nuevo archivo llamado light-alarm.js
touch light-alarm.js

Ahora copie el código de su computadora local en transport/transactions/

light-alarm.js( que se preparó previamente en el paso 1.1 ) a la Raspberry Pi. 

Abra el archivo con el nanoeditor.

nano light-alarm.js

Ahora inserte el código del LightAlarmTransactionUse CMD+V para pegar el contenido en el archivo. Para guardar y salir nano, use las siguientes teclas:

CMD+O

ENTER

CMD+X

Ahora es necesario crear un segundo archivo para el script de seguimiento real, `index.js` como se muestra a continuación:

touch index.js

A continuación, inserte el fragmento de código como se indica a continuación y guarde el index.jsarchivo. El comando anterior se puede reutilizar con el nanoeditor.

const PIN = require("rpi-pins"); const GPIO = new PIN.GPIO(); // Rpi-pins uses the

WiringPi pin numbering

system (check https:/

/pinout.xyz/pinout/

pin16_gpio23). GPIO.setPin(4, PIN.MODE.INPUT); const LightAlarmTransaction =

require('./light-alarm'); const { APIClient } = require

('@liskhq/lisk-api-client'); const {getNetworkIdentifier} =

require('@liskhq/lisk-cryptography'); const networkIdentifier =

getNetworkIdentifier( "23ce0366ef0a14a91e5fd4b1591f

c880ffbef9d988ff8bebf8f3666b0c09597d", "Lisk",

// Replace `localhost` with the

IP of the required

node to reach for the

API requests. const api = new APIClient([

'http://localhost:4000']); // Check config file or visit

localhost:4000/api/node/constants

to

verify your epoc time, (OK when

using /transport/node/index.js). const dateToLiskEpochTimestamp =

date => ( Math.floor(new Date(date).

getTime() /

1000) - Math.floor(new Date(Date.

UTC(2016, 4, 24, 17, 0, 0, 0)).

getTime() / 1000) ); const packetCredentials = { /* Insert

the credentials of the packet here in step 1.3 */ } // Check the status of the sensor in a certain interval,

(here it is set to: 1 second).

setInterval(() => { let state = GPIO.read(4); if(state === 0) { console.log('Package has been

opened! Send lisk transaction!'); // Uncomment the below code in

step 1.3 of the workshop /* let tx = new LightAlarmTransaction({ networkIdentifier: network

Identifier, timestamp:

dateToLiskEpochTimestamp(new Date()) }); tx.sign(packetCredentials.

networkIdentifier,

packetCredentials.passphrase); api.transactions.

broadcast(tx.toJSON()).

then(res => { console.log("

++++++++++++++++ API Response

+++++++++++++++++"); console.log(res.data); console.log("

++++++++++++++++ Transaction Payload

+++++++++++++++++"); console.log(tx.stringify()); console.log("++++++++++++++++

End Script

+++++++++++++++++"); }).catch(err => { console.log(JSON.stringify

(err.errors, null, 2)); }); */ } else { console.log('Alles gut'); } }, 1000);

1.2.3 Ejecutar el script de seguimiento

Para comprobar si el script puede leer los datos del sensor, inícielo ejecutando el siguiente comando:

node index.js

En primer lugar, coloque el sensor en un área oscura, luego muévalo a un área clara y verifique que se muestren los registros correctos en la consola.

Si no se ha detectado ninguna luz, se mostrará la siguiente salida:

Alles gut

Sin embargo, si se ha detectado luz, se mostrará el siguiente resultado:

Package has been opened! Send lisk transaction!

El código también intentará enviar el LightAlarmTransactionen el caso de que se haya detectado luz.

Para cancelar el script use las siguientes teclas:

CMD+C

A continuación step 1.3, se utilizará la aplicación cliente para inicializar una nueva cuenta para el paquete.

1.3 La aplicación cliente

En primer lugar, se debe almacenar una frase de contraseña del paquete en la Raspberry Pi, para que pueda firmar y transmitir el LightAlarmTransactionUna vez que se completa esto, la clientaplicación se puede iniciar para explorar las transacciones enviadas.

Mientras la Raspberry Pi todavía está conectada, abra una ventana de terminal local y navegue hasta la clientaplicación.

1.3.1 Instalación

Inicie la aplicación cliente con los siguientes comandos:

cd ../client
npm i
node app.js

Asegúrese de que la cadena de bloques se esté ejecutando para que el cliente funcione. Si no es así, inicie la cadena de bloques navegando a la node/carpeta y ejecutando el siguiente comando:

node index.js | npx bunyan -o short

1.3.2 Crear nuevas credenciales de paquete

Vaya a la Initializepágina (aplicación web que se ejecuta en http: // localhost: 3000 ) para crear una nueva cuenta de paquete. Cada vez que se actualiza la página, se crean e inicializan nuevas credenciales de paquete en la red.

Inicialización de la cuenta del paquete

Copie el objeto con las credenciales y péguelo como packetCredentialsen su script de seguimiento en la Raspberry Pi. Debe pegarse en el index.jsarchivo de la Raspberry Pi en la siguiente línea de código que se muestra a continuación:

const packetCredentials = { /* Insert the credentials of the packet here in step 1.3 */

1.3.3 Actualizar IP para API de nodo

Intercambie el localhostcon la IP donde se ejecuta su aplicación de nodo.

Si el tutorial se ha seguido correctamente, el nodo debería ejecutarse en su máquina local. Para adquirir la dirección IP, abra una nueva ventana de terminal en su máquina y escriba lo siguiente: ifconfigo un comando similar, que muestre la dirección IP actual.

Simplemente cópielo y reempláce ellocalhosten el script de seguimiento como se muestra a continuación:

const api = new APIClient(['http://localhost:4000']);

Ahora debería ser posible comprobar todos los elementos.

1.3.4 Descomentar código que envía la transacción de alarma de luz

1.3.5 Validar todos los componentes

Para rastrear la alarma de luz con la aplicación cliente, ejecute los siguientes pasos:
  1. Asegúrese de que el nodo blockchain se esté ejecutando en su máquina, ( node/carpeta) ejecutando el siguiente comando:

    node index.js | npx bunyan -o short
  2. Asegúrese de que el cliente de la carpetaclient/ se esté ejecutando ejecutando el siguiente comando:

    node app.js
  3. Coloca el sensor de tu Raspberry Pi en un área oscura.

  4. Ahora, inicie el script de seguimiento en su Raspberry Pi ejecutando el siguiente comando:

    node index.js
  5. Vaya a la páginaPacket&Carrier del cliente que se ejecuta en localhost: 3000 y actualice la página. En este punto, todavía no debería haber nada visible en la página.

  6. Ahora exponga el sensor a un poco de luz y actualice la página nuevamente.

  7. Realice una actualización adicional nuevamente, y una lista de marcas de tiempo debería estar visible indicando cuáles LightAlarmTransactionshan sido enviadas por Raspberry Pi.

Si las marcas de tiempo son visibles y se han agregado a asset.alarms.lightla cuenta del paquete, ¡entonces part 1el taller se ha completado con éxito! \ o /

cuenta de paquete

Ahora es posible detectar la manipulación de un paquete y guardar la marca de tiempo correspondiente en la cadena de bloques.

Parte 2: Un sistema de gestión de la cadena de suministro simple

El objetivo de la Parte 2 cubre cómo implementar un flujo de trabajo completo de una versión simple de un sistema de seguimiento de la cadena de suministro.

Los siguientes puntos a continuación se tratan en esta sección

🔴 La forma de aplicar los tipos de transacciones que falta RegisterPacket, StartTransporty FinishTransport.

🔴 Cómo almacenar en caché varias cuentas en el preparepaso.

🔴 Cómo implementar un sistema de confianza simple.

🔴 Cómo bloquear fondos en una cuenta.

🔴 Cómo ejecutar una primera prueba completa del sistema de seguimiento de la cadena de suministro.

Se recomienda ver el diagrama en la página de Introducción , que ayudará al usuario a tener una visión general del taller.

"¿Hay más de esas ... soluciones?"
¡Sí, hay más! , compruebe la implementación completamente funcional de las otras transacciones en la transactions/solutions/carpeta.

Después de completar una tarea, compárela con las soluciones para verificar si la tarea se completó con éxito. Sin embargo, tenga en cuenta que no solo existe una solución válida para escribir el código. En el paso 2.3 más adelante en esta sección, se ejecutará el código real para verificar que funcione correctamente.

2.0 Implementar RegisterPacket

Para pasar al siguiente paso en el prototipo es necesario implementar la RegisterPackettransacción. Esta transacción registrará un paquete en la cadena de bloques que está listo para ser recogido por una persona de entrega. La transacción permite al propietario del paquete definir las siguientes propiedades que se enumeran a continuación:

🔴 packetId: El ID de la cuenta del paquete (registrada en Raspberry Pi)

🔴 postage: El franqueo se refiere a una cantidad de tokens LSK que el transportista recibe tras la entrega exitosa del paquete. El franqueo se almacena en el campo de activos de la cuenta del paquete.

🔴 security: La seguridad se refiere a una serie de tokens LSK que el operador debe bloquear como "seguridad" antes de que el paquete pueda aceptarse para su entrega. Tras la entrega exitosa, la seguridad se desbloqueará nuevamente para el saldo del transportista.

🔴 minTrust: Esta propiedad de fideicomiso mínima se ha introducido para realizar un seguimiento de los transportistas que se comportan bien / funcionan. Siempre que un transportista entregue con éxito un paquete, su confianza se incrementará en un factor de uno. El propietario del paquete puede establecer un nivel de confianza mínimo para un transportista antes de aceptar el paquete para su entrega. Si un transportista tiene una confianza menor que el nivel de confianza mínimo requerido, no es posible aceptar el paquete para la entrega.

🔴 recipientId: Tenga en cuenta que este es un campo muy importante, ya que establece el destinatario real que recibirá el paquete.

Para lo RegisterPacketTransaction siguiente, la siguiente guía recorre la función undoAsset()  y explica cómo se puede lograr; y, a su vez, permite al usuario implementar un pequeño fragmento de código que se describe a continuación:

Contenido de register-packet.js

const { BaseTransaction, TransactionError, utils } = require('@liskhq/lisk-transactions'); /** * Register new package for sender and update package account. */ class RegisterPacketTransaction extends BaseTransaction { static get TYPE () { return 20; } static get FEE () { return '0'; }; /* Prepare function stores both sender and packet account in

the cache so it is possible to modify the accounts during the `applyAsset` and `undoAsset`

steps. */ async prepare(store) { await store.account.cache([ { address: this.asset.packetId, }, { senderPublicKey: this.senderPublicKey, } ]); } /* Static checks for presence and correct datatypes of

transaction parameters in asset field like `minTrust`, `security`, `postage`, etc. */ validateAsset() { const errors = []; if (!this.asset.packetId || typeof this.asset.packetId !== 'string') { errors.push( new TransactionError( 'Invalid "asset.packetId" defined on transaction', this.id, '.asset.packetId', this.asset.packetId ) ); } if (!this.asset.postage || typeof this.asset.postage !== 'string') { errors.push( new TransactionError( 'Invalid "asset.postage" defined on transaction', this.id, '.asset.postage', this.asset.postage, 'A string value', ) ); } if (!this.asset.security || typeof this.asset.security !== 'string') { errors.push( new TransactionError( 'Invalid "asset.security" defined on transaction', this.id, '.asset.security', this.asset.security, 'A string value', ) ); } if (typeof this.asset.minTrust !== 'number' ||

isNaN(parseFloat(this.asset.minTrust)) || !isFinite(this.asset.minTrust)) { errors.push( new TransactionError( 'Invalid "asset.minTrust" defined on transaction', this.id, '.asset.minTrust', this.asset.minTrust, 'A number value', ) ); } return errors;


applyAsset(store) { const errors = []; /* Retrieve packet account from key-value store. */ const packet = store.account.get(this.asset.packetId); /* Check if packet account already has a status assigned. If yes, then this means the package is already registered

so an error is thrown. */ if (!packet.asset.status) { /* --- Modify sender account --- */ /** * Update the sender account: * - Deduct the postage from senders' account balance */ const sender = store.account.get(this.senderId); /* Deduct the defined postage from the sender's account balance. */ const senderBalancePostageDeducted = new utils.BigNum(sender.balance).sub( new utils.BigNum(this.asset.postage) ); /* Save the updated sender account with the new balance into the

key-value store. */ const updatedSender = { ...sender, balance: senderBalancePostageDeducted.toString(), }; store.account.set(sender.address, updatedSender); /* --- Modify packet account --- */ /** * Update the packet account: * - Add the postage to the packet account balance * - Add all important data about the packet inside

the asset field: * - recipient: ID of the packet recipient * - sender: ID of the packet sender * - carrier: ID of the packet carrier * - security: Number of tokens the carrier

needs to lock during

the transport of the packet * - postage: Number of tokens the sender needs

to pay for transportation

of the packet * - minTrust: Minimal trust that is needed to

be carrier for the packet * - status: Status of the transport

(pending|ongoing|success|fail) */ /* Add the postage now to the packet's account

balance. */ const packetBalanceWithPostage =

new utils.BigNum(packet.balance).add( new utils.BigNum(this.asset.postage) ); const updatedPacketAccount = { ...packet, ...{ balance: packetBalanceWithPostage.toString(), asset: { recipient: this.asset.recipientId, sender: this.senderId, security: this.asset.security, postage: this.asset.postage, minTrust: this.asset.minTrust, status: 'pending', carrier: null } } }; store.account.set(packet.address, updatedPacketAccount); } else { errors.push( new TransactionError( 'packet has already been registered', packet.asset.status ) ); } return errors;

undoAsset(store) { const errors = []; /* UndoAsset function tells the blockchain how to rollback changes

made in the applyAsset function. The original balance for both the sender

and package account is restored. In addtion, the `asset` field for the package

account to `null` is reset,

as it did not hold any previous data.*/ /* --- Revert sender account --- */ const sender = store.account.get(this.senderId); const senderBalanceWithPostage = new utils.BigNum(sender.balance).add( new utils.BigNum(this.asset.postage) ); const updatedSender = { ...sender, balance: senderBalanceWithPostage.toString() }; store.account.set(sender.address, updatedSender); /* --- Revert packet account --- */ const packet = store.account.get(this.asset.packetId); /* something is missing here */ store.account.set(packet.address, originalPacketAccount); return errors; } } module.exports = RegisterPacketTransaction;

Tarea: Completar la implementación de la función undoAsset.

Tenga en cuenta que falta una pequeña parte de la lógica por la cual la cuenta del paquete se restableció a su estado original.Ahora trata de poner en práctica la lógica falta de undoAsset()revirtiendo los pasos de la función applyAsset(). Importante: Para verificar la implementación de undoAsset(), compárelo con la solución .

Explicación: undoAsset(store)

La función undoAsset es responsable de informar a la cadena de bloques cómo revertir los cambios que se han aplicado a través de la función applyAssetEsto es muy útil en el caso de una bifurcación en la que es necesario cambiar a una cadena diferente. Para lograr esto, es necesario revertir los bloques y aplicar nuevos bloques de una nueva cadena. Por lo tanto, al revertir los bloques, es necesario actualizar el estado de la cuenta de las cuentas afectadas. Tenga en cuenta que esta es la razón por la undoAssetque nunca debe omitirse la escritura de la lógica de la función.

2.1 Iniciar el transporte

Para el siguiente paso, ahora se requiere implementar la StartTransporttransacción. Esta transacción indica el inicio del transporte cuando el transportista recoge el paquete del remitente.

Al crear la StartTransporttransacción, el transportista define lo siguiente:

  • packetId: ID del paquete que el transportista va a transportar. No packetIdse envía en el campo de activos, pero se asigna a la recipientIdpropiedad de la transacción.

Esta transacción realizará lo siguiente:

  • Bloquea lo especificado securitydel paquete en la cuenta del operador. El transportista no puede acceder a esta seguridad, a menos que el transporte haya finalizado con éxito.

  • Agregue el carriera la cuenta del paquete.

  • Configure el statusdel paquete de pendingongoing.

El StartTransportTransaction lo prepare. la funcionundoAsset() se describe a continuación, incluyendo la implementación de la seguridad de bloqueo de los portadores de cuenta:

Contenido de start-transport.js

const { BaseTransaction, TransactionError, utils } = require('@liskhq/lisk-transactions'); class StartTransportTransaction extends BaseTransaction { static get TYPE () { return 21; } static get FEE () { return '0'; }; /* The `senderId`, which is the carrier account and the `recipientId` are both cached, which is the packet

account in the `prepare`

function. */ async prepare(store) { await store.account.cache([ { address: this.asset.recipientId, }, { senderPublicKey: this.senderPublicKey, } ]); } /* No static validation is required, as there is no data being sent

in the `asset` field.

*/ validateAsset() { const errors = []; return errors; } applyAsset(store) { const errors = []; const packet = store.account.get(this.asset.recipientId); if (packet.asset.status === "pending"){ const carrier = store.account.get(this.senderPublicKey); // If the carrier has the trust to transport the packet const carrierTrust = carrier.asset.trust ?

carrier.asset.trust : 0; const carrierBalance = new utils.BigNum(carrier.balance); const packetSecurity = new utils.BigNum(packet.asset.security); /* Check if the

carrier has the minimal

trust required for accepting the package. In addition, the carriers balance is

checked to see if it is larger

than the required security balance, as it is necessary to

lock this security inside the account. */ if (packet.asset.minTrust <= carrierTrust &&

carrierBalance.gte(packetSecurity)) { /** * Update the Carrier account: * - Lock security inside the account * - Remove the security from balance * - initialize carriertrust, if not present already */ /* Next,the defined security is locked, (number of LSK tokens)

in the asset field under the property `lockedSecurity`

and this security is deducted

from the `carrierBalance`. */ const carrierBalanceWithoutSecurity = carrierBalance.sub(packetSecurity); const carrierTrust = carrier.asset.trust ? carrier.asset.trust : 0; const updatedCarrier = /* Insert the updated carrier account here*/ store.account.set(carrier.address, updatedCarrier); /** * Update the Packet account: * - Set status to "ongoing" * - set carrier to ID of the carrier */ packet.asset.status = "ongoing"; packet.asset.carrier = carrier.address; store.account.set(packet.address, packet); } else { errors.push( new TransactionError( 'carrier has not enough trust to deliver the packet,

or not enough balance to pay the security', packet.asset.minTrust, carrier.asset.trust, packet.asset.security, carrier.balance )

} return errors; } undoAsset(store) { const errors = []; const packet = store.account.get(this.asset.recipientId); const carrier = store.account.get(this.senderPublicKey); /* --- Revert carrier account --- */ const carrierBalanceWithSecurity = new utils.BigNum(carrier.balance).add( new utils.BigNum(packet.assset.security) ); /* For the `undoAsset` function, it is necessary to revert the steps of

`applyAsset` again. Hence, it is necessary to remove the locked

balance in the `asset`

field and add this number again to the `balance` of the carrier's account. */ const updatedCarrier = { ...carrier, balance: carrierBalanceWithSecurity.toString() }; store.account.set(carrier.address, updatedCarrier); /* --- Revert packet account --- */ /* For the packet account, it is also necessary to undo certain items. Now set the `deliveryStatus` again to `pending`. The `carrier` value need sto be nullified as well. */ const updatedData = { asset: { deliveryStatus: "pending", carrier: null } }; const newObj = { ...packet, ...updatedData }; store.account.set(packet.address, newObj); return errors; } } module.exports = StartTransportTransaction;

Tarea: Bloquear fondos.

Para bloquear los fondos, simplemente reste la cantidad de tokens bloqueados del saldo de la cuenta.

const carrierBalanceWithoutSecurity = carrierBalance.sub(packetSecurity);

A continuación, almacene el número deducido de tokens en una propiedad personalizada en el assetcampo. Esto proporciona la capacidad de realizar un seguimiento de la cantidad de tokens bloqueados como seguridad.

Inserte su propio código aquí : cree un objeto actualizado para la cuenta del operador que reste el securitysaldo del operador y agregue una nueva propiedad lockedSecurityal assetcampo de la cuenta del operador. El lockedSecuritydebe ser exactamente igual a la cantidad deducida de los portadores balance.

Para desbloquear tokens bloqueados, elimine o anule la propiedad personalizada en el assetcampo y agregue el número de tokens nuevamente al de la cuenta balance.

Explicación: prepare()

La función de preparación aquí almacena en caché tanto la cuenta del operador a través de senderIdy la cuenta de paquete a través de recipientId.

¿Por qué es posible almacenar en caché dos cuentas al mismo tiempo? Tenga en cuenta que la función de caché acepta una matriz que le permite pasar varios objetos de consulta. Cuando se realiza un pase en una matriz a la función de caché, intentará encontrar un resultado para cada objeto de consulta.

También es posible pasar un solo objeto de consulta sin una matriz circundante. En este caso, solo los objetos que coincidan exactamente con este objeto de consulta se almacenarán en caché como se muestra a continuación:

async prepare(store) { await store.account.cache([ { address: this.asset.recipientId, }, { address: this.senderId, } ]); }

Puede encontrar una explicación más detallada en el artículo de análisis profundo de transacciones personalizadas en nuestro blog . El enlace abre la sección B/ Combining Filters.

2.2 Finalizar el transporte

La última transacción personalizada que se debe implementar aquí es la FinishTransportTransaction, que completará el transporte del paquete.

Al llegar al destinatario del paquete, el transportista pasa el paquete al destinatario. El destinatario debe firmar el FinishTransportTransaction, que verifica que el paquete se haya pasado al destinatario.

Al enviar la transacción, el destinatario debe especificar lo siguiente:

🔴 packetID: ID del paquete que recibió el destinatario

🔴 status: El estado del transporte, que tiene 2 opciones: "success""fail".

Esta transacción realizará lo siguiente:

  • Si status="success"

    • Enviar postagea la cuenta del operador.

    • Desbloquear securityen la cuenta del operador.

    • Aumento trustdel portador +1.

    • Establecer paquete statusen success.

  • Si status="fail"

    • Enviar postagea la cuenta del remitente.

    • Agregue securitya la cuenta del remitente y anule lockedSecurityde la cuenta del operador.

    • Disminución trustdel portador en -1.

    • Establecer paquete statusen fail.

Código para applyAsset()definish-transport.js

applyAsset(store) { const errors = []; let packet = store.account.get(this.asset.recipientId); let carrier = store.account.get(packet.asset.carrier); let sender = store.account.get(packet.asset.sender); // if the transaction has been signed by the packet recipient if (this.asset.senderId === packet.carrier) { // if the packet status is not "ongoing" and not "alarm" if (packet.asset.status !== "ongoing" && packet.asset.status !== "alarm") { errors.push( new TransactionError( 'FinishTransport can only be triggered, if packet status

is "ongoing" or "alarm" ', this.id, 'ongoing or alarm', this.asset.status ) ); return errors; } // if the transport was SUCCESSFUL if (this.asset.status === "success") { /** * Update the Carrier account: * - Unlock security * - Add postage & security to balance * - Earn 1 trustpoint */ /* Write your own code here*/ /** * Update the Packet account: * - Remove postage from balance * - Change status to "success" */ /* Write your own code here */ return errors; } // if the transport FAILED /** * Update the Sender account: * - Add postage and security to balance */ const senderBalanceWithSecurityAndPostage =

new utils.BigNum(sender.balance).add(new

utils.BigNum(packet.asset.security)).add(new utils.BigNum(packet.asset.postage)); sender.balance = senderBalanceWithSecurityAndPostage.toString(); store.account.set(sender.address, sender); /** * Update the Carrier account: * - Reduce trust by 1 * - Set lockedSecurity to 0 */ carrier.asset.trust = carrier.asset.trust ? --carrier.asset.trust : -1; carrier.asset.lockedSecurity = null; store.account.set(carrier.address, carrier); /** * Update the Packet account: * - set status to "fail" * - Remove postage from balance */ packet.balance = '0'; packet.asset.status = 'fail'; store.account.set(packet.address, packet); return errors; } errors.push( new TransactionError( 'FinishTransport transaction needs to be

signed by the recipient of the packet', this.id, '.asset.recipient', this.asset.recipient ) ); return errors;

Explicación: almacenamiento en caché de datos basados ​​en datos de la base de datos

Es posible que sea necesario almacenar en caché las cuentas u otros datos de la base de datos, dependiendo de otros datos almacenados en la base de datos.

Para lograr esto, se deben seguir los puntos que se enumeran a continuación:

  1. almacenar en caché los datos con store.account.cache.

  2. guardar los datos como una constante con store.account.get.

  3. Ahora es posible usar la constante recién creada para almacenar en caché el resto de los datos, como se muestra en el fragmento de código a continuación:

prepare() funcion de finish-transport.js

async prepare(store) { /** * Get packet account. */ await store.account.cache([ { address: this.asset.recipientId, } ]); /** * Get sender and recipient accounts of the packet. */ const pckt = store.account.get(this.asset.recipientId); await store.account.cache([ { address: pckt.asset.carrier, }, { address: pckt.asset.sender, }, ]); }

Tarea: implementar la lógica applyAsset()para un transporte exitoso

Cuando el destinatario recibe el paquete del transportista, el destinatario debe firmar y enviar el FinishTransportTransactionSi el destinatario considera que el transporte fue exitoso, entonces el transportista debe ser recompensado en consecuencia y el estado del paquete se actualizará a success.

Puede encontrar más información en los comentarios del código de finish-transport.js

2.3 Pruebe el flujo de trabajo completo con la aplicación cliente

Verifique el estado en el lightAlarmTransaction

En este punto, se debe implementar todo el flujo de trabajo con el estado de los diferentes paquetes. Si un paquete se encuentra actualmente en estado ongoing o alarm, para enviar una alarma, siga las instrucciones que se describen a continuación:

Inserte el fragmento de código que se enumera a continuación en la applyAsset()función de light-alarm.js , antes del código que aplica los cambios a las cuentas de la base de datos.

Si el estado no es ongoingalarm, creará uno nuevo TransactionError, lo empujará a la errorslista y luego lo devolverá.

Este fragmento debe insertarse dos veces: una vez en transaction/light-alarm.jsla máquina local, y también en light-alarm.jsla frambuesa pi.h


const packet = store.account.get(this.senderId); if (packet.asset.status !== 'ongoing' && packet.asset.status !== 'alarm') { errors.push( new TransactionError( 'Transaction invalid because delivery is not "ongoing".', this.id, 'packet.asset.status', packet.asset.status, `Expected status to be equal to "ongoing" or "alarm"`, ) ); return errors; }

Registre todos los tipos de transacciones con la aplicación de nodo

Siga los pasos necesarios que se enumeran a continuación para descomentar todas las transacciones personalizadas, con el fin de registrarlas con la aplicación de nodo:


const { Application, genesisBlockDevnet, configDevnet } = require('lisk-sdk'); const RegisterPacketTransaction = require('../transactions/register-packet'); const StartTransportTransaction = require('../transactions/start-transport'); const FinishTransportTransaction = require('../transactions/finish-transport'); const LightAlarmTransaction = require('../transactions/light-alarm'); configDevnet.app.label = 'lisk-transport'; configDevnet.modules.http_api.access.public = true; const app = new Application(genesisBlockDevnet, configDevnet); app.registerTransaction(RegisterPacketTransaction); app.registerTransaction(StartTransportTransaction); app.registerTransaction(FinishTransportTransaction); app.registerTransaction(LightAlarmTransaction); app .run() .then(() => app.logger.info('App started...')) .catch(error => { console.error('Faced error in application', error); process.exit(1); });

Pruébelo en la aplicación cliente

Ahora intente iniciar o reiniciar la aplicación nodeclientiot, exactamente como se realizó anteriormente en el Paso 1.3 de la Parte 1 de este tutorial.

Vaya a http://localhost:3000para acceder a la aplicación cliente a través del navegador web.

Las credenciales de cuenta preparadas para el remitente, el destinatario y el operador se pueden encontrar en client/accounts.json.

Estas credenciales ya están precargadas en los diferentes formularios de la aplicación cliente.

Los diferentes usuarios en el transporte Lisk se pueden ver a continuación:

{ "carrier": { "address": "6795425954908428407L", "passphrase": "coach pupil shock error defense outdoor tube

love action exist search idea", "encryptedPassphrase": "iterations=1&salt=4ba0d3869948e39a

7f9a096679674655&cipherText=f0a1f0009ded34c79a0af40f12fcf3507

1a88de0778abea2a1f07861386a4b5c6b13f308f1ebf1af9098b66ed77cb22fc

8bd872fa71ff71f3dbed1194928b7e447cb4089359a8be64093f9c1c8a3dca8&iv

=e0f1fb7574873142c672a565&tag=ad56e67c5115e9a211c3907c400b9458&version=1", "publicKey": "7b97ac4819515de345570181642d975590154e434f86ece578c

91bbfa2e4e1e7", "privateKey": "c7723897eaaf4462dc0b914af2b1e4905e42a548866e0ddfb

09efdfdd4d2df507b97ac4819515de345570181642d975590154e4

34f86ece578c91bbfa2e4e1e7" }, "recipient": { "name": "delegate_100", "address": "11012441063093501636", "passphrase": "dream theory eternal recall valid clever mind sell doctor

empower bread cage", "encryptedPassphrase": "iterations=10&cipher

Text=b009292f88ea0f9f5b5aec4

7a6168b328989a37e7567aea697b8011b3d7fb63a07d7d85

53c1a52740fd14453d84f560fda

384bf1c105b5c274720d7cb6f3dbf6a9ed9f967cdc7e57f2

74083c&iv=ec7e5ebe2c226fcd82

09fc06&salt=0478b7883713866370ae927af7525ed2&tag

=29aa766741bf5b4bbcfeaf3cd33ad23

7&version=1", "publicKey": "d8685de16147583d1b9f2e06eb43c6a

f9ba03844df30e20f3cda0b681c14fb05" }, "sender": { "address": "11237980039345381032L", "passphrase": "creek own stem final gate scrub live shallow stage host concert they" } }

Inicializar una nueva cuenta de paquete

Vaya http://localhost:3000/initializey copie las credenciales del paquete en su script de seguimiento en la Raspberry Pi.

Crear nuevas credenciales de paquete

Inicializar la cuenta del paquete

Registra el paquete

En primer lugar, abra la página Registrar paquete y complete el formulario para registrar su paquete en la red.

Utilice la dirección de las credenciales del paquete como el ID del paquete que se creó en el paso anterior.

Configúrelo minTrusten 0, ya que todavía no hay ningún operador presente en el sistema que tenga más que 0puntos de confianza.

El remitente publica la RegisterPackettransacción para registrar el paquete en la red.

paquete de registro

Consulte la Packet & Carrierpágina para ver si el estado del paquete ahora es "pendiente"

paquete pendienteSi el paquete se abre ahora en este punto, entonces la transacción de alarma de luz debería fallar ya que el paquete debería tener el error statusDebería mostrar el siguiente mensaje de error que se enumera a continuación:

[ { "message": "Transaction invalid because delivery is not \"ongoing\".", "name": "TransactionError", "id": "5902807582253136271", "dataPath": "packet.asset.status", "actual": "pending", "expected": "Expected status to be equal to \"ongoing\" or \"alarm\"" } ]

Financiar la cuenta del operador

Antes de que comience el transporte de paquetes, es necesario transferir algunos tokens a la cuenta del transportista vacía. Esto es necesario ya que el transportista debe bloquear el securityen la cuenta del transportista para comenzar el transporte.

Para realizar esta tarea, vaya a la página Faucet e ingrese la dirección del operador ( 6795425954908428407L), seguida de la cantidad de tokens que se transferirán a esta cuenta.

Asegúrese de que se transfieran suficientes tokens para que el operador pueda permitirse bloquear el securitypaquete, que se definió en el paso anterior, mediante el cual el paquete se registró en la red.

Esto se puede verificar en la Accountspágina para ver si el operador recibió los tokens con éxito.

Portador de fondos

Iniciar transporte

Se requiere que el transportista publique la transacción en la página Iniciar transporte para iniciar el transporte.

Ahora se requiere que el transportista especifique el packetId.

La transacción solo será aceptada si el transportista tiene suficiente trustsecuritypara el paquete especificado.

El transportista publica la StartTransporttransacción y luego recibe el paquete del remitente.

iniciar el transporte

Respuesta API

terminar el transporteConsulte la Packet & Carrierpágina para ver si el estado del paquete ha cambiado a "en curso".

cuenta de paquete 2La alarma de luz se apagará después de publicar el StartTransporty antes de publicar el FinishTransportEsto ocurre debido a la verificación de estado agregada en la sección Verificar estado en lightAlarmTransaction .

El destinatario publica la FinishTransporttransacción, una vez que se ha recibido el paquete del transportista.

terminar el transporte Verifique si el transporte se ha realizado correctamente o si ha fallado, luego verifique los cambios en consecuencia en las cuentas de la Packet&Carrierpágina.

El transporte falla

terminar el transporte falla

terminar el transporte

Una vez que se hayan completado todos los pasos anteriores, ahora se está ejecutando en su máquina una prueba de concepto simple, pero completamente funcional, de un sistema de seguimiento de la cadena de suministro descentralizado

Parte 3: Cómo publicar la aplicación

En la parte 3, se describen los siguientes pasos después del desarrollo inicial de la aplicación.

1. Hazlo portátil

Actualmente, el paquete no es realmente portátil, ya que requiere una fuente de alimentación que se establece mediante una conexión USB a la computadora.

En primer lugar, para garantizar que se pueda lograr la portabilidad, se requiere una fuente de alimentación independiente, como una batería que pueda proporcionar a la Raspberry Pi la energía adecuada, para permitir el seguimiento del paquete.

En segundo lugar, es necesario que el script de seguimiento se habilite automáticamente después de que se haya ejecutado el proceso de arranque de la Raspberry Pi.

Esto se puede lograr fácilmente en Raspberry Pi instalando pm2globalmente como se muestra a continuación:

npm install pm2 -g
pm2 startup

Esto debería imprimir el comando aplicable en la ventana del terminal. Cópielo y péguelo en el terminal nuevamente para completar la configuración del pm2script de inicio.

Inicie el script de seguimiento con pm2.

Ejecute el siguiente comando dentro de la light_alarmcarpeta en la Raspberry Pi como se muestra a continuación:
pm2 start --name lightAlarm index.js
Agregue el script de seguimiento a la lista de procesos, que se iniciará automáticamente cuando se inicie Raspberry pi como se muestra a continuación:
pm2 save

Una vez realizado esto, cierre la sesión de la Raspberry Pi, desconéctela de la computadora y conéctela a una fuente de alimentación portátil externa. Una vez finalizado el proceso de arranque (aproximadamente 1-2 minutos), la secuencia de comandos de seguimiento comenzará a ejecutarse, que a su vez comprobará el sensor de luz cada segundo.

2. Conecta más nodos

Durante el desarrollo, es necesario y conveniente tener una red bastante centralizada con un solo nodo conectado.

Una vez que el desarrollo haya alcanzado el punto de concepto o una etapa de producto utilizable, será necesario agregar más nodos a la red y, por lo tanto, ofrecer a otros usuarios potenciales la oportunidad de unirse a la red blockchain recién creada.

Después de completar la parte 2, la configuración debe aparecer como se muestra a continuación en el siguiente diagrama: Parte 2: Un sistema de seguimiento de la cadena de suministro simple :

Diagrama de un nodo

El siguiente paso es agregar un nodo más a la red que se comunica entre el nodo semilla, el IoT y la aplicación cliente, como se muestra en el siguiente diagrama a continuación:

Más diagrama de nodos

2.1. Configurar un nodo semilla adicional

Al configurar un nuevo nodo, cada nuevo nodo se conectará primero a los nodos semilla al arrancar por primera vez. A partir del nodo semilla, un nuevo nodo descubrirá el resto de la red solicitando su lista de pares. A esto le siguen las listas de pares de los pares recién descubiertos y así sucesivamente.

El nodo semilla es un nodo que se especifica en la configuración de la aplicación del nodo debajo modules.network.seedPeersy debe permanecer conectado a la red.

Además, también es conveniente que los delegados de génesis forjen activamente en el nodo semilla, en caso de que la red aún no tenga suficientes delegados reales que puedan ocupar los lugares de forja.

El configDevnetobjeto expuesto es una buena plantilla para la configuración de un nodo semilla, ya que ya incluye las credenciales de todos los 101 delegados de génesis y habilita automáticamente la falsificación para todos ellos.

2.2. Cree una nueva configuración adecuada para la aplicación de nodo

Intercambie el objeto configDevnet que se pasó al nodo durante el desarrollo con la versión personalizada.

Se recomienda crear un objeto de configuración con todas las opciones que sean diferentes a las opciones de configuración predeterminadas. Para comprobar las opciones de configuración por defecto, vaya a la página de configuración o detectar directamente en el código que aparece a continuación:

lisk-framework/src/modules/MODULE_NAME/defaults/config.js.
Lo mismo para los componentes como se muestra a continuación:
lisk-framework/src/components/COMPONENT_NAME/defaults/config.js.

La mayoría de las configuraciones pueden permanecer iguales a las definidas en las opciones de configuración predeterminadas. Sin embargo, tenga en cuenta que hay una opción que debe actualizarse: el (los) nodo (s) semilla .

Entonces, para agregar 1.2.3.4como un nodo semilla, agregue un objeto (o varios objetos), con las 2 propiedades ipwsPorta la seedPeerslista como se muestra a continuación:

const app = new Application(genesisBlockDevnet, { modules: { network: { seedPeers: [{ ip: '1.2.3.4', wsPort: 5000}] } } });

De forma predeterminada, la lista de delegados de forja está vacía.

Esto está previsto, ya que los delegados de génesis solo son necesarios para establecer un entorno de desarrollo laboral. Posteriormente se recomienda que la delegateslista esté vacía, para que los usuarios puedan ingresar sus propias credenciales en el caso de que deseen activar la forja en su nodo.

Por ejemplo, para una prueba de concepto, para proporcionar los delegados de forja ya activados dentro de la configuración; utilice los delegados de génesis de devnet en configDevnet o cree sus propios delegados de génesis y agréguelos a la configuración.

2.3. Publica la aplicación

Agregue el código de la nodeaplicación personalizada (incluidas las transacciones personalizadas) a un repositorio de código público. Por ejemplo, en Github o Gitlab .

Esto brinda a todos la oportunidad de descargar la aplicación e implementarla en un servidor para conectarse a la red.

El código debe incluir al menos los siguientes archivos que se enumeran a continuación:

🔴 index.js : el código que inicializa e inicia la aplicación del nodo.

🔴 package.json : un archivo de proyecto que enumera todas las dependencias necesarias (esto debe incluirse lisk-sdkcomo dependencia).

🔴 transacciones / : una carpeta que contiene todas las transacciones personalizadas requeridas.

🔴 README : Un archivo Léame que describe los pasos más importantes para configurar el nodo.

2.4. Conectar nodos y verificar

Agregue un segundo nodo a la red.Este nuevo nodo no tendrá ninguna forja activada, solo se requiere hablar a través de la API con la clientaplicación y mediante la conexión websocket al nodo semilla. Por lo tanto, en la actualidad, el nodo semilla es el único nodo en este punto que puede forjar nuevos bloques. Esto se debe al hecho de que todos los delegados de génesis lo están forjando activamente.

En la siguiente sección se explica cómo reemplazar los delegados de génesis por delegados reales. Reemplazar los delegados de génesis por delegados reales .

Para configurar el nodo, instale la aplicación del nodo en un nuevo servidor. Simplementesiga las instrucciones del README, que se creó en el paso anterior.

¡No olvide abrir los puertos correspondientes para la comunicación HTTP y WS!

Una vez que se configura un nuevo nodo, actualice el punto final de la API en la aplicación cliente para apuntar al nuevo nodo como se muestra a continuación:

Fragmento de client / app.js

// Constants const API_BASEURL = 'http://134.209.234.204:4000';


Agregue la IP y el puerto correctos al nodo recién agregado.

Si la aplicación cliente tiene el punto final de API del nuevo nodo, recibirá transacciones del cliente. Las transacciones serán visibles en los registros (si el nivel de registro es al menos info).

Registros del nodo recién agregado

Sincronización de nodo no forjado

En los registros que se muestran arriba, se puede ver que el nodo semilla ya estaba 3 bloques por delante cuando se inició el segundo nodo. Primero sincroniza los bloques faltantes hasta la altura actual y luego transmite las transacciones recibidas desde la aplicación del cliente al nodo semilla, por lo que los delegados pueden agregar las transacciones a los bloques y falsificarlos.

Estos nuevos bloques se transmiten nuevamente al nuevo nodo y la aplicación cliente puede mostrar los datos en función de las llamadas a la API que envía al nuevo nodo.

Registro del nodo semilla con los delegados de la génesis de forja:

Forjar registros de nodos

Tenga en cuenta que pueden producirse errores de transmisión.

A veces se producen errores al difundir transacciones entre los nodos. No hay motivo de preocupación aquí, ya que el nodo reiniciará el proceso de sincronización nuevamente; y en la mayoría de los casos tiene éxito en el siguiente intento.

Problema de sincronización común

En la imagen de arriba, el bloque a la altura 284 no se acepta debido a una marca de tiempo de bloque no válida. Como resultado, el nodo también descarta los siguientes bloques.

Anomalías como esta pueden ocurrir dentro de la red. El nodo generalmente puede resolver estos problemas por sí solo iniciando un nuevo proceso de sincronización, mediante el cual solicita los bloques faltantes de uno de sus nodos pares.

Como se muestra en los registros anteriores, los bloques a la altura 284, 285 y 286 se muestran como descartados. En este punto, el nodo se da cuenta de que no está sincronizado con los otros nodos y comienza el proceso de sincronización. Esto también se puede ver en los registros anteriores, Starting sync. Durante el proceso de sincronización, los bloques faltantes se reciben de los pares y se agregan a la base de datos del nodo.


3. Reemplazar delegados de génesis por delegados reales

Durante el desarrollo de la aplicación Lisk Transport, se habilitó un nodo para forjar los 101 delegados de génesis.
Después del lanzamiento de la primera versión de la aplicación blockchain, es necesario que los delegados reales tomen las posiciones de falsificación de los delegados de génesis. La red se volverá estable y descentralizada por primera vez cuando al menos 51 delegados reales se forjen activamente en la red.
Para unirse a la red como nuevo delegado, siga los pasos que se enumeran a continuación:
Cree una cuenta propia y privada en la red.Genere las credenciales de la cuenta
    1. Envíe algunos fondos (al menos los suficientes para registrarse como delegado), a la dirección recién generada con el fin de inicializar su cuenta en la red.

  1. Registre un delegado.

    1. Genere el objeto de registro de delegado .

    2. Transmita el registro de delegado a la red como se muestra a continuación:

export SECRET_PASSPHRASE=123456 lisk transaction:create:delegate lightcurve -p=env:SECRET_PASSPHRASE |

tee >(curl -X POST

-H "Content-Type: application/json"

-d @- 1.2.3.4:4000/api/transactions)

Reemplazar 123456con la contraseña secreta.Reemplazar 1.2.3.4con la IP de un nodo con una API pública.

  1. Configurar un nodo: Siga los pasos en el READMEarchivo de la aplicación (alternativamente lea los tutoriales de Lisk, ya que este proceso es básicamente idéntico).

  2. Habilite la forja para el delegado recién creado en el nodo

  3. Las personas se convencen de votar por un delegado en la red, si el delegado tiene los siguientes atributo

    • 🔴 Es útil.

    • 🔴 Es responsable.

    • 🔴 Es compartir recompensas.

    • 🔴 Ofrece servicios o herramientas útiles.

Diagrama de 3 nodos

Cómo reemplazar un delegado de génesis

Si un delegado se une a la red en una etapa muy temprana, probablemente reemplazará a uno de los delegados de génesis. Los delegados de génesis son votados por la cuenta de génesis que contiene todos los tokens en el inicio de la red inicial. La cuenta de génesis vota con estos tokens para los delegados de génesis, con el fin de estabilizar la red durante el desarrollo.

Por lo tanto, al reemplazar a un delegado de génesis, el nuevo delegado deberá convencer a la persona que controla la cuenta de génesis de la red; que probablemente será el desarrollador de la aplicación.

Más tarde, cuando la mayoría de los tokens existentes se distribuyen entre las diferentes cuentas privadas, el nuevo delegado debe ganarse la confianza de la comunidad para ser votado en una posición de falsificación.

4. Escribe pruebas para transacciones personalizadas

Cuanto más compleja sea la lógica dentro de las transacciones personalizadas, más complicado se volverá verificar que la lógica de la transacción personalizada funcione como se esperaba.

Por lo tanto, se recomienda escribir pruebas unitarias , que verifiquen la lógica del tipo de transacción.

Especialmente para verificar el código de la función undoAsset(), es conveniente escribir pruebas unitarias. Esto se debe al hecho de que el código de la función undoAsset solo se ejecuta si el nodo se descubre en una bifurcación con la cadena principal.

Estar en una bifurcación significa que el nodo agregó algunos bloques diferentes a la cadena que sus pares. Para volver a sincronizarse con la red, tiene que eliminar los bloques que son diferentes y deshacer las transacciones dentro de estos bloques. Para deshacer la transacción, undoAsset()se llamará a la función para cada transacción dentro de los bloques que deben descartarse.

Para probar si la transacción se deshace correctamente, escriba una prueba unitaria como se muestra a continuación:

Ejemplo: prueba unitaria para la función undoAsset () de RegisterPacketTransaction

const { when } = require('jest-when'); const dateToLiskEpochTimestamp = date => ( Math.floor(new Date(date).getTime() / 1000) - Math.floor(new Date(Date.UTC(2016, 4, 24, 17, 0, 0, 0)).getTime() / 1000) ); describe('RegisterPacket Transaction', () => { let storeStub; beforeEach(() => { storeStub = { account: { get: jest.fn(), set: jest.fn(), }, }; }); test('it should undo the state for register packet correctly', async () => { // Arrange const senderId = 'senderXYZ'; const asset = { security: transactions.utils.convertLSKToBeddows('10'), minTrust: 0, postage: transactions.utils.convertLSKToBeddows('10'), packetId: 'not important', }; const mockedPacketAccount = { address: 'xyz123', }; const mockedSenderAccount = { address: 'abc123', balance: '10000000000', // 100 LSK }; when(storeStub.account.get) .calledWith(asset.packetId) .mockReturnValue(mockedPacketAccount); when(storeStub.account.get) .calledWith(senderId) .mockReturnValue(mockedSenderAccount);

// Act const tx = new RegisterPacketTransaction({ senderId, asset, recipientId: 'xyzL', timestamp: dateToLiskEpochTimestamp(new Date()), }); tx.undoAsset(storeStub); // Assert expect(storeStub.account.set).toHaveBeenNthCalledWith( 1, mockedPacketAccount.address, { address: mockedPacketAccount.address, balance: 0, asset: null, } ); expect(storeStub.account.set).toHaveBeenNthCalledWith( 2, mockedSenderAccount.address, { address: mockedSenderAccount.address, balance: new transactions.utils.BigNum(mockedSenderAccount.balance).add( new transactions.utils.BigNum(asset.postage) ).toString() } ); }); });

Qué más se necesita probar?

¿Escribir pruebas unitarias es realmente suficiente para garantizar la funcionalidad de una transacción personalizada?

Respuesta corta: las pruebas unitarias son suficientes.

Explicación: Quizás se pregunte si es necesario escribir pruebas funcionales y de integración adicionales. Tenga en cuenta que la lectura y escritura correctas de los datos en la base de datos ya es parte de las pruebas del software Lisk SDK y, por lo tanto, no es necesario volver a probarlo para su nueva transacción personalizada. Por lo tanto, las pruebas unitarias generalmente son suficientes para probar la funcionalidad de una transacción personalizada.

5. Nuevas mejoras

Conecte más sensores para asegurar el viaje del paquete. Por ejemplo, implemente un TemperatureAlarmHumidityAlarm para el LightAlarmtipo de transacción.

Alternativamente, deje que la red sepa la ubicación actual del paquete transmitiendo la ubicación GPS en un cierto intervalo de tiempo.

Fuente: blog de Lisk





Blockchain de Lisk en la gestión de la cadena de suministro, informacion avanzada para desarrolladores Parte 0: Instalación y confi...