Cadena de suministro usando Lisk, guia de instalacion y configuracion
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:
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
Este comando ejecutará el index.js
archivo 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 .
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 ssh
y 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 ssh
travé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 LightAlarmTransaction
a 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 LightAlarmTransaction
. Esta 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 validateAsset
necesidades de función para ser implementados, validateAsset
. Para 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 LightAlarmTransaction
. En caso de que se encuentre un error, inserte uno nuevaTransactionError
en 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 this
variable. 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 TransactionError. Intente agregar un ajuste TransactionError
a la errors
lista 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_world
dentro del lisk-sdk-examples
repositorio. 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:TAREACopie 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
LightAlarmTransaction
y, por lo tanto, la transacción se descartará.node/index.js
que 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 . Asegúrese de que este comando se ejecute dentro de la node/ carpeta. |
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.local
de 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 usb
grabado en la placa de circuito impreso, como se muestra en el siguiente diagrama:
Para iniciar sesión usando ssh
desde un terminal, ejecute el ping
comando 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
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 nano
editor.
nano light-alarm.js
Ahora inserte el código del LightAlarmTransaction
. Use 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.js
archivo. El comando anterior se puede reutilizar con el nano
editor.
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 LightAlarmTransaction
en 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 LightAlarmTransaction
. Una vez que se completa esto, la client
aplicació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 client
aplicació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 Initialize
pá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.
Copie el objeto con las credenciales y péguelo como packetCredentials
en su script de seguimiento en la Raspberry Pi. Debe pegarse en el index.js
archivo 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 localhost
con 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: ifconfig
o un comando similar, que muestre la dirección IP actual.
Simplemente cópielo y reempláce ellocalhost
en 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
Ahora descomente el fragmento de código que crea y envía el objeto de transacción de alarma de luz .
1.3.5 Validar todos los componentes
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
Asegúrese de que el cliente de la carpeta
client/
se esté ejecutando ejecutando el siguiente comando:node app.js
Coloca el sensor de tu Raspberry Pi en un área oscura.
Ahora, inicie el script de seguimiento en su Raspberry Pi ejecutando el siguiente comando:
node index.js
Vaya a la página
Packet&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.Ahora exponga el sensor a un poco de luz y actualice la página nuevamente.
Realice una actualización adicional nuevamente, y una lista de marcas de tiempo debería estar visible indicando cuáles
LightAlarmTransactions
han sido enviadas por Raspberry Pi.
Si las marcas de tiempo son visibles y se han agregado a asset.alarms.light
la cuenta del paquete, ¡entonces part 1
el taller se ha completado con éxito! \ o /
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 RegisterPacket
transacció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 applyAsset. Esto 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 undoAsset
que 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 StartTransport
transacción. Esta transacción indica el inicio del transporte cuando el transportista recoge el paquete del remitente.
Al crear la StartTransport
transacción, el transportista define lo siguiente:
packetId
: ID del paquete que el transportista va a transportar. NopacketId
se envía en el campo de activos, pero se asigna a larecipientId
propiedad de la transacción.
Esta transacción realizará lo siguiente:
Bloquea lo especificado
security
del 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
carrier
a la cuenta del paquete.Configure el
status
del paquete depending
aongoing
.
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 asset
campo. 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 security
saldo del operador y agregue una nueva propiedad lockedSecurity
al asset
campo de la cuenta del operador. El lockedSecurity
debe ser exactamente igual a la cantidad deducida de los portadores balance
.
Para desbloquear tokens bloqueados, elimine o anule la propiedad personalizada en el asset
campo 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 senderId
y 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"
o "fail"
.
Esta transacción realizará lo siguiente:
Si
status="success"
Enviar
postage
a la cuenta del operador.Desbloquear
security
en la cuenta del operador.Aumento
trust
del portador +1.Establecer paquete
status
ensuccess
.
Si
status="fail"
Enviar
postage
a la cuenta del remitente.Agregue
security
a la cuenta del remitente y anulelockedSecurity
de la cuenta del operador.Disminución
trust
del portador en -1.Establecer paquete
status
enfail
.
Haga clic aquí para ver el código completo de FinishTransportTransaction
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:
almacenar en caché los datos con
store.account.cache
.guardar los datos como una constante con
store.account.get
.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 FinishTransportTransaction
. Si 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 ongoing
o alarm
, creará uno nuevo TransactionError
, lo empujará a la errors
lista y luego lo devolverá.
Este fragmento debe insertarse dos veces: una vez en transaction/light-alarm.js
la máquina local, y también en light-alarm.js
la 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 node
, client
y iot
, exactamente como se realizó anteriormente en el Paso 1.3 de la Parte 1 de este tutorial.
Vaya a http://localhost:3000
para 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/initialize
y copie las credenciales del paquete en su script de seguimiento en la Raspberry Pi.
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 minTrust
en 0
, ya que todavía no hay ningún operador presente en el sistema que tenga más que 0
puntos de confianza.
El remitente publica la RegisterPacket
transacción para registrar el paquete en la red.
Packet & Carrier
página para ver si el estado del paquete ahora es "pendiente"Si 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 status
. Deberí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 security
en 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 security
paquete, que se definió en el paso anterior, mediante el cual el paquete se registró en la red.
Esto se puede verificar en la Accounts
página para ver si el operador recibió los tokens con éxito.
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 trust
y security
para el paquete especificado.
El transportista publica la StartTransport
transacción y luego recibe el paquete del remitente.
Consulte la Packet & Carrier
página para ver si el estado del paquete ha cambiado a "en curso".
La alarma de luz se apagará después de publicar el StartTransport
y antes de publicar el FinishTransport
. Esto ocurre debido a la verificación de estado agregada en la sección Verificar estado en lightAlarmTransaction .
El destinatario publica la FinishTransport
transacción, una vez que se ha recibido el paquete del transportista.
Verifique si el transporte se ha realizado correctamente o si ha fallado, luego verifique los cambios en consecuencia en las cuentas de la Packet&Carrier
página.
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 pm2
globalmente 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 pm2
script de inicio.
Inicie el script de seguimiento con pm2
.
light_alarm
carpeta en la Raspberry Pi como se muestra a continuación:pm2 start --name lightAlarm index.js
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 :
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:
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.seedPeers
y 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 configDevnet
objeto 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.4
como un nodo semilla, agregue un objeto (o varios objetos), con las 2 propiedades ip
y wsPort
a la seedPeers
lista 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 delegates
lista 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 node
aplicació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 client
aplicació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';
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
).
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:
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.
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.
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.
Registre un delegado.
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 123456
con la contraseña secreta.Reemplazar 1.2.3.4
con la IP de un nodo con una API pública.
Configurar un nodo: Siga los pasos en el
README
archivo de la aplicación (alternativamente lea los tutoriales de Lisk, ya que este proceso es básicamente idéntico).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.
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 TemperatureAlarm
o HumidityAlarm
para el LightAlarm
tipo 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.