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

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

Requisitos

Requisitos básicos para todas las máquinas, ( 

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

Los siguientes pasos enumerados a continuación describen cómo crear los archivos iniciales para este tutorial:
Este comando ejecutará el 
Si todo funciona correctamente, el nodo se puede detener presionando 
En caso de que el nodo falle o no funcione correctamente, siga esta guía para configurar un entorno de desarrollo dedicado utilizando Docker y Docker-Compose. 

Ejemplo 1. Referencia: configuración de Raspberry Pi.

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 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 están cubiertos en esta sección:
  • Cómo implementar el 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 ponerlo en la Raspberry Pi.
  • Cómo usar la aplicación cliente para inicializar la cuenta del paquete y rastrear las transacciones de alarma en la red.

Consulte las soluciones en / transacciones / soluciones 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');


Arquitectura del proyecto

A continuación se enumeran los tres tipos diferentes de aplicaciones que deben desarrollarse para crear el sistema descentralizado de la cadena de suministro:
Una aplicación de nodo
Esto aceptará los tipos de transacción 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 cliente
Esto mostrará información de la cadena de bloques al usuario. Se requiere una interfaz que debe mostrar una lista de ID de paquete. Debe consistir en el operador, el remitente, el destinatario y un campo de estado, ( pending | ongoing | alarm | success | fail). Además, también debería proporcionar una manera fácil 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 la luz, la temperatura y / o la humedad dentro del paquete. Si la aplicación 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                                          
│ ├── accounts.json
│ ├── app.js
│ ├── package.json
│ ├── guiones
└── └── vistas
├── iot                                             
│ ├── README.md
│ ├── lisk_rpi_ldr_and_temperature_sensors_wiring.png
│ ├── luz_alarma
│ │ ├── package.json
│ │ └── index.js
├── nodo                                            
│ ├── index.js
│ └── package.json
└── transacciones                                    
    ├── finish-transport.js
    ├── light-alarm.js
    ├── register-packet.js
    └── start-transport.js
Contiene el código para la aplicación cliente .
Contiene el código para la aplicación IoT .
Contiene el código para la aplicación de nodo .
Contiene las transacciones personalizadas que utilizan el nodo y la aplicación cliente.

1.1 Implemente la transacción LightAlarm

Para una versión simple del seguimiento de paquetes, solo se necesita implementar una transacción personalizada, la LightAlarmTransactionEsta transacción será enviada por el dispositivo IoT dentro del paquete cuando detecte anomalías con su fotorresistencia conectada (detección de luz).
En este próximo paso solo la validateAssetfunción necesita ser implementada validateAsset,. Para más detalles, consulte la explicación a continuación . La implementación de la validateAsset()función se puede lograr fácilmente utilizando el siguiente código que se detalla a continuación:
Contenido de /transactions/light-alarm.js



1.1.1 Tarea: Implementar validateAsset()

Implemente su propia lógica para la validateAsset()función 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 nuevo TransactionErroren la errorsmatriz 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, use this.timestamp.
El fragmento a continuación describe cómo crear un TransactionErrorobjeto. Intente agregar un ajuste TransactionErrora la errorslista de validateAsset(), en el caso de que la marca de tiempo no esté presente, o si tiene el formato incorrecto.
El tipo de datos esperado para la marca de tiempo es number!
Ejemplo: cómo crear un TransactionErrorobjeto se muestra a continuación:
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 necesita más información sobre la implementación de la validateAsset()función, 
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 .

1.1.2 Tarea: Implementar applyAsset()

La applyAssetfunción informa a la cadena de bloques qué cambios se deben hacer y cómo se puede cambiar la cuenta de un usuario. Esto mantiene la lógica comercial central de sus transacciones personalizadas. continuación se puede ver un ejemplo que muestra la implementación de applyAssetfor LightAlarmTransaction:
TAREA
Copie el fragmento a continuación y reemplace la applyAssetfunción light-alarm.jspara 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 againstthe 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.3 Registrar la transacción con la aplicación

Como LightAlarmTransactionse ha creado una nueva transacción personalizada , 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 shortAsegúrese de que este comando se ejecute dentro de la node/carpeta.

1.2 La aplicación IoT

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

1.2.1 Conectarse a la Raspberry Pi

Para simplificar la topología de red para el taller, se configuró un servidor DHCP en la Raspberry Pi que asignará una dirección IP a su computadora utilizando un 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 usbgrabado en la placa de circuito impreso, como se muestra en el siguiente diagrama:
Cómo conectarse a tu Pi
Para iniciar sesión usando sshdesde una terminal, ejecute el pingcomando que se detalla a continuación: Esto comenzará a hacer ping a la Raspberry Pi que generará las respuestas de retorno.
ping raspberrypi.local
Ejemplo de salida 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 anteriores, se puede ejecutar el siguiente comando:
ssh pi@raspberrypi.local
Si se le solicita una advertencia, presione Entrar para aceptar el valor predeterminado, (Sí).
Ahora debe aparecer la solicitud de contraseña, ingrese la contraseña para Raspberry Pi.
Su terminal ahora debe estar conectado a la Raspberry Pi, por lo que la preparación puede comenzar como se describe a continuación:

1.2.2 Crear el script de seguimiento

Ejecute los comandos enumerados 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. Abre el archivo con el nanoeditor.
nano light-alarm.js
Ahora inserte el código de la LightAlarmTransactionUse CMD+V para pegar los contenidos 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 detalla 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(
    "23ce0366ef0a14a91e5fd4b1591fc880ffbef9d988ff8bebf8f3666b0c09597d",
    "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: networkIdentifier,
            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');
    }

1.2.3 Ejecute el script de seguimiento

Para verificar 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 luz, se mostrará la siguiente salida:
Alles gut
Sin embargo, si se ha detectado luz, se mostrará la siguiente salida:
Package has been opened! Send lisk transaction!
El código también intentará enviar el LightAlarmTransactionen caso de que se haya detectado luz.
Para cancelar el script, use las siguientes teclas:
CMD+C
A continuación step 1.3, la aplicación cliente se usará para inicializar una nueva cuenta para el paquete.

1.3 La aplicación del cliente

En primer lugar, una frase de contraseña del paquete debe almacenarse en la Raspberry Pi, para que pueda firmar y transmitir el LightAlarmTransactionUna vez que esto se haya completado, la clientaplicación se puede iniciar para explorar las transacciones enviadas.
Mientras Raspberry Pi todavía está conectado, abra una ventana de terminal local y navegue hacia la clientaplicación.
La implementación completa del cliente se prepara antes del taller. En esta parte 1 del taller, solo se utilizarán las páginas InitializePacket&Carrier.

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, 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

Navegue 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 paquetes en la red.
Inicialización de la cuenta del paquete.
Copie el objeto con las credenciales y péguelo como packetCredentialsen su secuencia de comandos de seguimiento en la Raspberry Pi. Debe pegarse en el index.jsarchivo de 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 la API del nodo

Intercambie 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ácelo localhosten el script de seguimiento como se muestra a continuación:
const api = new APIClient(['http://localhost:4000']);
Ahora debería ser posible verificar todos los elementos.

1.3.4 Código de no comentario 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 client/carpeta se esté ejecutando ejecutando el siguiente comando:
    node app.js
  3. Coloque el sensor de su 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 Packet&Carrierpágina en el cliente que se ejecuta en localhost: 3000 y actualice la página. En este punto, nada debería estar visible en la página todavía.
  6. Ahora exponga el sensor a algo de luz y actualice la página nuevamente.
  7. Realice una actualización adicional nuevamente, y una lista de marcas de tiempo debe ser 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 una manipulación de paquetes y guardar la marca de tiempo correspondiente en la cadena de bloques.








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

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 están cubiertos en esta sección:
  • Cómo implementar los tipos de transacciones que faltan RegisterPacketStartTransportFinishTransport.
  • 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! , consulte la implementación totalmente funcional de las otras transacciones en la transactions/solutions/carpeta.
Después de completar una tarea, compare con las soluciones para verificar si la tarea se ha completado con éxito. 

2.0 Implementar RegisterPacket

Para pasar al siguiente paso en el prototipo, es necesario implementar la RegisterPackettransacción. 
  • packetId: El ID de la cuenta del paquete (registrada en Raspberry Pi).
  • postage: El franqueo se refiere a una serie de tokens LSK que el transportista recibe al entregar el paquete con éxito. 
  • security: La seguridad se refiere a una cantidad de tokens LSK que el operador debe bloquear como "seguridad" antes de que el paquete pueda ser aceptado para la entrega. 
  • minTrust: Esta propiedad de confianza mínima se ha introducido para realizar un seguimiento de los portadores que se comportan bien. 
  • recipientId: Tenga en cuenta que este es un campo muy importante, ya que establece el destinatario real que recibirá el paquete.
Para la RegisterPacketTransactionsiguiente guía se ejecuta a través de la undoAsset()función, y explica cómo se puede lograr esto; 
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;
    }

}

Tarea: completar la implementación de la undoAssetfunción.

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 applyAsset()función.
Importante: Para verificar la implementación de 

Explicación: undoAsset(store)

La undoAssetfunción es responsable de informar a la cadena de bloques cómo revertir los cambios que se han aplicado a través de la applyAssetfunción. undoAssetnunca se debe omitir 

2.1 Iniciar el transporte

Para el siguiente paso, ahora se requiere implementar la StartTransporttransacción. 
Al crear la StartTransporttransacción, el transportista define lo siguiente:
  • packetId: El ID del paquete que el transportista va a transportar. packetIdno se envía en el campo de activos, pero se asigna a la recipientIdpropiedad de la transacción.
Esta transacción realizará lo siguiente:
  • Bloquee el securitypaquete 
  • Agregue el carriera la cuenta del paquete.
  • Establezca el statusdel paquete de pendingongoing.
El StartTransportTransaction, las prepare(),and the `undoAsset()funciones se describen 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{

    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
                    )
                );
            }
        } else {
            errors.push(
                new TransactionError(
                    'packet status needs to be "pending"',
                    packet.asset.status
                )
            );
        }

        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 deduzca el número 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. 
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. 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 nuevamente el número de tokens a la cuenta balance.
Importante: para verificar la implementación, compárela con la 

Explicación: prepare

La función de preparación aquí es el almacenamiento en caché de la cuenta del operador a través de senderIdy la cuenta del 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 múltiples objetos de consulta. 
También es posible pasar un solo objeto de consulta sin una matriz circundante. 
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 inmersiones profundas de transacciones personalizadas en nuestro blog . B/ Combining Filters.

2.2 Terminar el transporte

La última transacción personalizada aquí es implementar es la FinishTransportTransactionque completará el transporte del paquete.
Al llegar al destinatario del paquete, el transportista pasa el paquete al destinatario. FinishTransportTransaction, que verifica que el paquete se haya pasado al destinatario.
Al enviar la transacción, el destinatario debe especificar lo siguiente:
  • packetID: La 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.
    • Desbloquee securityla cuenta del operador.
    • Aumento trustde la portadora +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 para el 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

Puede ser necesario almacenar en caché 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 siguientes puntos:
  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 tiene que firmar y enviar el FinishTransportTransactionsuccess.
Se puede encontrar más información en los comentarios de código de finish-transport.js
Importante: para verificar su implementación 

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

Verifique el estado en el lightAlarmTransaction

En este punto, todo el flujo de trabajo debe implementarse con el estado de los diferentes paquetes. ongoing o alarmestado, a continuación, enviar una alarma siga las instrucciones que se describen a continuación:
Inserte el fragmento de código que figura 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 está en 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 y registrarlas en 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 nodeclienty la iotaplicación, exactamente como se realizó anteriormente en el Paso 1.3 en 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 rellenadas previamente en los diferentes formularios de la aplicación cliente.
Los diferentes usuarios en el transporte de 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=4ba0d3869948e39a7f9a096679674655&cipherText=f0a1f0009ded34c79a0af40f12fcf35071a88de0778abea2a1f07861386a4b5c6b13f308f1ebf1af9098b66ed77cb22fc8bd872fa71ff71f3dbed1194928b7e447cb4089359a8be64093f9c1c8a3dca8&iv=e0f1fb7574873142c672a565&tag=ad56e67c5115e9a211c3907c400b9458&version=1",
    "publicKey": "7b97ac4819515de345570181642d975590154e434f86ece578c91bbfa2e4e1e7",
    "privateKey": "c7723897eaaf4462dc0b914af2b1e4905e42a548866e0ddfb09efdfdd4d2df507b97ac4819515de345570181642d975590154e434f86ece578c91bbfa2e4e1e7"
  },
  "recipient": {
    "name": "delegate_100",
    "address": "11012441063093501636",
    "passphrase": "dream theory eternal recall valid clever mind sell doctor empower bread cage",
    "encryptedPassphrase": "iterations=10&cipherText=b009292f88ea0f9f5b5aec47a6168b328989a37e7567aea697b8011b3d7fb63a07d7d8553c1a52740fd14453d84f560fda384bf1c105b5c274720d7cb6f3dbf6a9ed9f967cdc7e57f274083c&iv=ec7e5ebe2c226fcd8209fc06&salt=0478b7883713866370ae927af7525ed2&tag=29aa766741bf5b4bbcfeaf3cd33ad237&version=1",
    "publicKey": "d8685de16147583d1b9f2e06eb43c6af9ba03844df30e20f3cda0b681c14fb05"
  },
  "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 secuencia de 
Crear nuevas credenciales de paquete
Inicializar cuenta de paquete

Registra el paquete

En primer lugar, abra la Registrar paquete y complete el formulario para registrar su paquete en la red.
Use la dirección de las credenciales del paquete como la ID del paquete que se creó en el paso anterior.
Ajuste minTrust0, ya que no hay portadora presente en el sistema sin embargo, que cuenta con más de 0TrustPoints.
El remitente publica la RegisterPackettransacción para registrar el paquete en la red.
paquete de registro
Verifique la Packet & Carrierpágina para ver si el estado del paquete ahora está "pendiente"
paquete pendiente
Si el paquete ahora está abierto en este punto, entonces la transacción de la alarma de luz debería fallar ya que el paquete debería estar equivocado status
[
  {
    "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 de transportista vacía. securitycuenta de transportistas para comenzar el transporte.
Para realizar esta tarea, vaya a la página Faucet e ingrese la dirección del operador ( 6795425954908428407L), seguido 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.
Operador de fondos

Iniciar transporte

El transportista debe publicar la transacción en la Iniciar transporte , para iniciar el transporte.
El operador ahora debe especificar el packetId.
La transacción solo se aceptará 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 transporte
Verifique la Packet & Carrierpágina para ver si el estado del paquete ha cambiado a "en curso".
paquete de cuenta 2
La alarma de luz se extinguirá después de publicarla StartTransporty antes de publicarla FinishTransportVerificar el estado en lightAlarmTransaction .
paquete de alarma

Terminar el transporte

Cuando el transportista pasa el paquete al destinatario, el destinatario firmará la FinishTransport , que completará el transporte del paquete.
Solo el packetIdy el statusque pueden failsuccessdeben especificarse aquí.
Para ayudar con la decisión del estado final, el destinatario puede inspeccionar el paquete después de recibirlo. 
En caso de que el destinatario no reciba el paquete después de un período de tiempo razonable, el destinatario también debe enviar la FinishTransporttransacción (probablemente con status=fail).
El destinatario FinishTransportcontabiliza 
terminar el transporte Verifique si el transporte ha sido exitoso o si ha fallado, luego verifique los cambios en consecuencia en las cuentas de la Packet&Carrierpágina.
Falla de transporte
terminar el transporte falla
Respuesta API
terminar el transporte
Una vez que se hayan completado todos los pasos anteriores, se está ejecutando en su máquina una prueba de concepto simple pero totalmente funcional de un sistema descentralizado de seguimiento de la cadena de suministro.

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 al Raspberry Pi la potencia 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 Raspberry Pi.
Esto se puede lograr fácilmente en Raspberry Pi mediante la instalación pm2global como se muestra a continuación:
npm install pm2 -g
pm2 startup
Esto debería imprimir el comando correspondiente en la ventana de terminal. pm2script de inicio.
Comience 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 sesión en Raspberry Pi, desconéctelo de la computadora y conéctelo a una fuente de alimentación portátil externa. 

2. Conecte 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 conceptual 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 debería aparecer como se muestra a continuación en el siguiente diagrama: Parte 2: Un sistema simple de seguimiento de la cadena de suministro :
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. 

Al configurar un nuevo nodo, cada nuevo nodo se conectará primero a los nodos semilla cuando se inicie por primera vez. 
El nodo semilla es un nodo que se especifica en la configuración de la aplicación de nodo 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 tomar los puntos de forja.
El configDevnetobjeto 

2.2. 

Intercambie el configDevnetobjeto 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. 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 lo que se define en las opciones de configuración predeterminadas. los nodos semilla .
Entonces, para agregar 1.2.3.4como 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}]
        }
    }
});
Por defecto, la lista de delegados de falsificación está vacía.
Esto está previsto, ya que los delegados de génesis solo son necesarios para establecer un entorno de desarrollo de trabajo. delegateslista esté vacía, de modo que los usuarios puedan ingresar sus propias credenciales en el caso en que deseen activar la falsificación en su nodo.
Por ejemplo, para una prueba de concepto, para proporcionar los delegados de forja ya activados dentro de la configuración; configDevnet o cree sus propios delegados de génesis y agréguelos a la configuración.

2.3. 

Agregue el código para la nodeaplicación Github o Gitlab .
Esto brinda a todos la oportunidad de descargar la aplicación e implementarla en un servidor para conectarse con la red.
El código debe incluir al menos los siguientes archivos enumerados a continuación:
  • index.js : el código que inicializa e inicia la aplicación de nodo.
  • package.json : un archivo de proyecto que enumera todas las dependencias necesarias (esto debe incluir 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. 

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 a través de la conexión websocket al nodo semilla. 
Cómo reemplazar los delegados de génesis por delegados reales se trata en la siguiente sección Reemplazar delegados de génesis por verdaderos .
Para configurar el nodo, instale la aplicación de nodo en un nuevo servidor. 
¡No olvide abrir los puertos correspondientes 
Una vez que se configura un nuevo nodo, actualice el punto final de la API en la 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 API del nuevo nodo, recibirá transacciones del cliente. 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 adelante cuando se inició el segundo nodo. 
Estos nuevos bloques se transmiten nuevamente al nuevo nodo, y la aplicación del cliente puede mostrar los datos en función de las llamadas API que envía al nuevo nodo.
Registro del nodo semilla con los delegados de forja de génesis:
Forjar registros de nodo
Tenga en cuenta que pueden producirse errores de transmisión.
A veces se producen errores al transmitir transacciones entre los nodos. 
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. 
Anomalías como esta pueden ocurrir dentro de la red. 
Como se muestra en los registros anteriores, los bloques a la altura 284, 285 y 286 se muestran como descartados. Starting sync

3. Reemplazar delegados de génesis por 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 ranuras de falsificación de los delegados de génesis. 
Para unirse a la red como un nuevo delegado, siga los pasos que se detallan a continuación:
  1. Cree una cuenta propia y privada en la red.
    1. Envíe algunos fondos (al menos lo suficiente como para registrarse como delegado) a la dirección recién generada para inicializar su cuenta en la red.
  2. Registrar un delegado
    1. 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 frase secreta.
      Reemplace 1.2.3.4con la IP de un nodo con una API pública.
  3. Configure un nodo: siga los pasos en el READMEarchivo de la aplicación (como alternativa, lea los tutoriales de Lisk, ya que este proceso es básicamente idéntico).
  4. Las personas se convencen de votar por un delegado en la red, si el delegado tiene los siguientes atributos:
    • Es útil.
    • Es responsable
    • Está compartiendo 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. 
Por lo tanto, al reemplazar un delegado de génesis, el nuevo delegado deberá convencer a la persona que controla la cuenta de génesis de la red; 
Más tarde, cuando la mayoría de los tokens existentes se distribuyen entre las diferentes cuentas privadas, el nuevo delegado necesita ganarse la confianza de la comunidad para ser votado en una posición de falsificación.

4. Escribir pruebas para transacciones personalizadas

Cuanto más compleja es la lógica dentro de las transacciones personalizadas, más complicado se vuelve verificar que la lógica de transacción personalizada funcione como se espera.
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 undoAsset()función, es conveniente escribir pruebas unitarias. undoAssetfunción 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. undoAsset()se llamará a 
Para probar si la transacción se deshace correctamente, escriba una prueba unitaria como se muestra a continuación:
Ejemplo: Prueba de unidad para la función undoAsset () de RegisterPacketTransaction
const RegisterPacketTransaction = require('../register-packet');
const transactions = require('@liskhq/lisk-transactions');
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 necesita ser probado?
¿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. 

5. Mejoras adicionales

Conecte más sensores para asegurar el viaje del paquete. TemperatureAlarmao HumidityAlarm en el LightAlarmtipo de transacción.
Alternativamente, informe a la red la ubicación actual del paquete transmitiendo la ubicación del GPS en un cierto intervalo de tiempo.

Blockchain de Lisk en la gestión de la cadena de suministro, informacion avanzada para desarrolladores Este documento explicar...