Ejemplo básico

Ejemplo básico para usar la tecnología Kore Ledger.

1 - Levantar el primer nodo

Pasos para configurar el primer nodo Kore

Para lanzar un nodo kore, debe ejecutar el binario kore-http, ubicado en la carpeta cliente del repositorio. Para utilizar su imagen Docker, hay que ir a la página de dockerhub.

Si no disponemos de la imagen o no tenemos la última versión, descárgala con:

docker pull koreadmin/kore-http:0.5-sqlite

Podemos ejecutarlo lanzándolo:

docker run koreadmin/kore-http:0.5-sqlite

Sin embargo, esto nos dará un error, ya que debemos especificar obligatoriamente ciertos aspectos de la configuración.

Podemos generar nosotros la clave criptográfica o dejar que el nodo la genere. En este tutorial el nodo se encargara de esa tarea.

  • Lo primero que debemos añadir obligatoriamente a la configuración es la clave privada. Podemos generar una válida utilizando kore-tools, que se encuentra en el mismo repositorio que el cliente en el directorio kore-tools. En concreto, su binario keygen, que creará el material criptográfico necesario para el nodo.
  • Una vez tenemos la imagen debemos generar un archivo de configuración indicando lo siguiente: . listen_addresses Dirección donde va a escuchar el nodo para comunicarse con otros nodos . boot_nodes un vector de nodos conocidos, como es el primer nodo lo dejaremos vacio
// config.json
{
    "kore": {
      "network": {
          "listen_addresses": ["/ip4/0.0.0.0/tcp/50000"],
          "routing": {
            "boot_nodes": [""]
          }
      }
    }
  }

Para levantar el nodo debemos indicar desde que puerto de nuestra máquina podemos acceder a la API, además del puerto donde escuchara el nodo. Po último importante indicar el archivo de configuración.

docker run -p 3000:3000 -p 50000:50000 -e KORE_PASSWORD=polopo -e KORE_FILE_PATH=./config.json -v ./config.json:/config.json koreadmin/kore-http:0.5-sqlite

2 - Creando la gobernanza

Pasos para crear una gobernanza

Ahora que hemos podido lanzar nuestro primer nodo, lo primero que debemos hacer para que sea útil es crear una gobernanza. Las gobernanzas son sujetos especiales que definen las reglas del caso de uso en cuestión. Sin gobernanza no puede haber sujetos. Tanto su esquema como su contrato están fijos y definidos en el código de kore. Lo mismo ocurre con su estructura.

Un aspecto interesante de la API kore-http son las diferentes posibilidades para utilizar el punto final de envío de solicitudes de eventos. La forma más ortodoxa sería incluir la solicitud y la firma de la misma. Para ello, se puede utilizar kore-sign (incluido en kore-tools) para firmar la solicitud. Pero también se puede omitir la firma en el cuerpo de la solicitud y que el cliente la firme con nuestra propia clave privada. Obviamente, esto no se puede hacer para invocaciones externas donde el firmante no es el propietario del nodo. Otro cambio destinado a aumentar la simplicidad de los eventos de Génesis/Creación es que la clave pública se puede omitir del cuerpo y el cliente creará una para nosotros. En general, antes de crear un sujeto, debes llamar a la API de creación de material criptográfico para generar un par de claves /keys y el método POST. Esta API devuelve el valor de la clave pública del KeyPair para incluirlo en los eventos Create y Transfer.

Para hacer esto, debemos lanzar una solicitud de evento usando la API kore-http. El punto final que debemos usar es /event-requests y el método es POST. Este endpoint admite diferentes configuraciones para hacer la vida más fácil al usuario:

Entonces, si optamos por la tercera vía, el cuerpo de la solicitud post que crea la gobernanza quedaría así:

{
  "request": {
    "Create": {
      "governance_id": "",
      "schema_id": "governance",
      "namespace": "",
      "name": "EasyTutorial"
    }
  }
}
curl --silent --location 'http://localhost:3000/event-requests' \
--header 'Content-Type: application/json' \
--data '{
  "request": {
    "Create": {
      "governance_id": "",
      "schema_id": "governance",
      "namespace": "",
      "name": "tutorial"
    }
  }
}'

La respuesta que obtenemos al iniciar la solicitud de evento es la identificación de la solicitud en sí. Si queremos saber cuál terminó siendo el SubjectId de la gobernanza, debemos consultar el endpoint /event-requests/{id} y el método GET. La respuesta a este punto final devuelve información sobre la solicitud que incluye el SubjectId de la gobernanza.

curl --silent 'http://127.0.0.1:3000/event-requests/{request_id}/state'

Response:

{
    "id": "Jr4kWJOgdIhdtUMTqyLbu676-k8-eVCd8VQ9ZmLWpSdg",
    "subject_id": "{{GOVERNANCE-ID}}",
    "sn": 0,
    "state": "finished",
    "success": true
}

También podemos solicitar la lista de sujetos en /subjects usando el método GET. En este caso, obtendremos una lista de los sujetos que tenemos en el nodo, en este caso, solo tendremos la gobernanza que acabamos de crear.

Siendo nosotros dueños del sujeto, se puede decir que somos testigos del mismo. El único rol que se define por defecto en el estado inicial de la gobernanza es el que hace que todos los miembros de la gobernanza sean testigos de ella, pero en el caso de los miembros, viene vacío. En el siguiente paso, nos agregaremos como miembros de la gobernanza. Esto se debe a que el estado inicial no tiene miembros y, para participar activamente en el caso de uso, debemos agregarnos como miembros. Aunque este paso no es obligatorio, depende del caso de uso.

El punto final a utilizar es el mismo que para la creación, pero el tipo de evento será FACT:

Debemos obtener nuestro controller_id que nos permitira añadirnos como mienbro de la gobernanza

curl --silent 'http://127.0.0.1:3000/controller-id'
{
  "request": {
    "Fact": {
      "subject_id": "{{GOVERNANCE-ID}}",
      "payload": {
          "Patch": {
              "data": [
                {
                    "op": "add",
                    "path": "/members/0",
                    "value": {
                    "id": "{{CONTROLER_ID}}",
                    "name": "EasyTutorial"
                    }
                }
            ]
          }
      }
    }
  }
}
curl --silent 'http://localhost:3000/event-requests' \
--header 'Content-Type: application/json' \
--data '{
    "request": {
        "Fact": {
            "subject_id": "{{GOVERNANCE-ID}}",
            "payload": {
                "Patch": {
                    "data": [
                        {
                            "op": "add",
                            "path": "/members/0",
                            "value": {
                                "id": "{{CONTROLER-ID}}",
                                "name": "EasyTutorial"
                            }
                        }
                    ]
                }
            }
        }
    }
}'

Reemplace {{GOVERNANCE-ID}} con el SubjectId de la gobernanza que hemos creado. La identificación de nuestro usuario que obtenemos cuando usamos kore-keygen en el paso anterior. Es nuestro KeyIdentifier, que identifica nuestra clave pública. El método Patch es el único que actualmente contiene el contrato de gobernanza y simplemente aplica un JSON Patch a su estado. Este método requiere la fase de Aprobación.

Como mencionamos anteriormente, el creador será el firmante en todas las fases si no hay nadie más definido, por lo que para este evento 1 seremos el Evaluador, Aprobador y Validador. La evaluación y validación funcionan automáticamente, pero la parte de aprobación requiere la intervención del usuario a través de la API (siempre que la variable de entorno que aprueba automáticamente no esté definida).

Para esto, primero debemos solicitar aprobaciones pendientes en /approval-requests?status=pending usando un GET.

curl --silent 'http://localhost:3000/approval-requests?status=pending'

El id de la respuesta json es lo que debemos utilizar para aprobarla. En /approval-requests/{id} usando un PATCH, agregaremos la identificación recibida para emitir el voto. Como en nuestro caso, queremos aprobarlo, el cuerpo debería ser:

{"state": "RespondedAccepted"}
curl --silent --request PATCH 'http://localhost:3000/approval-requests/{{REQUEST-ID}}' \
--header 'Content-Type: application/json' \
--data '{"state": "RespondedAccepted"}'

Podemos observar que el state de la respuesta ha pasado de null a Responded. Indicando asi que hemos respondido el evento de Fact en la gobernanza.Además si obtenemos el estado de la request-id veremos que el estado es Finished.

3 - Añadir miembros

Añadiendo mienmbros a la gobernanza

Segundo nodo

Para agregar un segundo miembro, podemos repetir el paso anterior pero cambiando ligeramente el cuerpo de la solicitud. Para hacer esto, primero ejecutaremos kore-keygen nuevamente para crear un segundo material criptográfico que identifique al segundo miembro:

PRIVATE KEY ED25519 (HEX): 388e07385cfd8871f990fe05f82610af1989f7abf5d4e42884c8337498086ba0
CONTROLLER ID ED25519: {{CONTROLLER-ID}}
PeerID: 12D3KooWRS3QVwqBtNp7rUCG4SF3nBrinQqJYC1N5qc1Wdr4jrze

Deberemos levantar el segundo nodo para ello crearemos un archivo de configuración añadiendo el peer-id del nodo 1, para ello debemos ejecutar:

curl --silent 'http://127.0.0.1:3000/peer-id'
//config2.json
{
    "kore": {
      "network": {
          "listen_addresses": ["/ip4/0.0.0.0/tcp/50000"],
          "routing": {
            "boot_nodes": ["/ip4/172.17.0.1/tcp/50000/p2p/{{PEER-ID}}"]
          }
      },
    }
  }

Levantamos el nodo 2 en el puerto 3001:

docker run -p 3001:3000 -p 50001:50000 -e KORE_PASSWORD=polopo -e KORE_FILE_PATH=./config.json -v ./config2.json:/config.json koreadmin/kore-http:0.5-sqlite

Obtenemos el controler_id del Nodo 2:

curl --silent 'http://127.0.0.1:3000/controller-id'
{
  "request": {
    "Fact": {
      "subject_id": "{{GOVERNANCE-ID}}",
      "payload": {
          "Patch": {
              "data": [
                {
                    "op": "add",
                    "path": "/members/1",
                    "value": {
                    "id": "{{CONTROLER-ID}}",
                    "name": "Node2"
                    }
                }
            ]
          }
      }
    }
  }
}

Debemos aprobar nuevamente la nueva solicitud como en el caso anterior.

Comunicación entre nodos

Ahora que está activo y encuentra el nodo definido en boot_nodes. Los eventos de la gobernanza comenzarán a llegar al segundo nodo, aunque aún no quedarán guardados en su base de datos. Esto se debe a que las gobernanzas siempre deben estar previamente autorizadas para permitir la recepción de sus eventos. Para esto se utiliza el endpoint /allowed-subjects/{{GOVERNANCE-ID}} y el método PUT. Recordad que en este caso hay que lanzarlo en el segundo nodo, que por la configuración que hemos puesto estará escuchando en el puerto 3001 de localhost. El segundo nodo ahora se actualizará correctamente con el sujeto de gobernanza.

curl --silent --request PUT 'http://localhost:3001/allowed-subjects/{{GOVERNANCE-ID}}' \
--header 'Content-Type: application/json' \
--data '{
    "providers": []
}'

Respuesta:

OK

Modificar la gobernanza

Como hemos visto anteriormente, el contrato de gobernanza actualmente solo tiene un método para modificar su estado, el método Patch. Este método incluye un objeto con un atributo de datos que a su vez es una matriz que representa un JSON Patch. Este parche se aplicará al estado actual de la gobernanza para modificarlo. Además al realizar la modificación se comprueba que el estado obtenido sea válido para una gobernanza, no sólo realizando la validación con el propio esquema de gobernanza sino también realizando comprobaciones exhaustivas, como que no haya miembros repetidos, cada esquema definido por turno…

Para facilitar la obtención del resultado que queremos y generar el JSON Patch específico podemos utilizar la herramienta kore-patch, incluida entre las kore-tools. A este ejecutable se le pasa el estado actual y el estado deseado y genera el parche correspondiente tras cuya aplicación se pasa de uno a otro.

Por ejemplo, haremos que todos los miembros de la gobernanza sean aprobadores, para ello debemos agregar el rol:

{
    "namespace": "",
    "role": "APPROVER",
    "schema": {
        "ID": "governance"
    },
    "who": "MEMBERS"
}

Entonces el JSON Patch que tendremos que aplicar será:

[
  {
    "op": "add",
    "path": "/roles/1",
    "value": {
        "namespace": "",
        "role": "APPROVER",
        "schema": {
            "ID": "governance"
        },
        "who": "MEMBERS"
    }
  }
]

Entonces el cuerpo de la solicitud será:

{
  "request": {
    "Fact": {
      "subject_id": "{{GOVERNANCE-ID}}",
      "payload": {
          "Patch": {
              "data": [
                {
                  "op": "add",
                  "path": "/roles/1",
                  "value": {
                      "namespace": "",
                      "role": "APPROVER",
                      "schema": {
                          "ID": "governance"
                      },
                      "who": "MEMBERS"
                  }
                }
              ]
          }
      }
    }
  }
}

Aunque el siguiente estado dice que ambos son aprobadores, para calcular los firmantes de las diferentes fases se utiliza el estado actual del sujeto, previo a aplicar el cambio de estado de este nuevo evento que estamos creando, por lo que el único derecho de aprobador ahora seguirá siendo el primer nodo por ser el dueño de la gobernanza, por lo que debemos repetir el paso de autorización anterior.

Tercer nodo

Levantar el tercer nodo

Para agregar un tercer miembro repetimos los pasos anteriores, lo primero es crear el material criptográfico con kore-keygen o dejar que el nodo lo genere:

Lanzamos el contenedor de docker modificando los puertos pero usando el mismo archivo de config que el nodo 2:

docker run -p 3002:3000 -p 50002:50000 -e KORE_PASSWORD=polopo -e KORE_FILE_PATH=./config.json -v ./config2.json:/config.json koreadmin/kore-http:0.5-sqlite

Modificar la gobernanza

Ahora lanzaremos el evento que suma al tercer miembro a la gobernanza, pero para comprobar el funcionamiento de las aprobaciones votaremos con un nodo y no con el otro, lo que dejará el evento como rechazado en la fase de aprobación. Aún se agregará a la cadena del sujeto, pero no modificará su estado.

{
  "request": {
    "Fact": {
      "subject_id": "{{GOVERNANCE-ID}}",
      "payload": {
          "Patch": {
              "data": [
                {
                    "op": "add",
                    "path": "/members/2",
                    "value": {
                    "id": "{{CONTOLLER-ID}}",
                    "name": "Node3"
                    }
                }
            ]
          }
      }
    }
  }
}

Primero debemos solicitar aprobaciones pendientes en /approval-requests?status=pending usando un GET. El id del json de respuesta es el que debemos utilizar para aprobarlo. En /approval-requests/{id} usando un PATCH agregaremos la identificación recibida para emitir el voto.

curl --silent 'http://localhost:3000/approval-requests?status=pending'

En el nodo 1(puerto 3000) lo aprobaremos pero en el nodo 2(puerto 3001) lo rechazaremos. Como el quórum es de mayoría, esto significa que ambos deben aprobarlo para que sea aprobado. Por lo que si uno de los dos lo rechaza, será rechazado porque no se puede alcanzar el quórum de aceptación.

Nodo 1:

{"state": "RespondedAccepted"}
curl --silent --request PATCH 'http://localhost:3000/approval-requests/{{REQUEST-ID}}' \
--header 'Content-Type: application/json' \
--data '{"state": "RespondedAccepted"}'

Nodo 2:

{"state": "RespondedRejected"}
curl --silent --request PATCH 'http://localhost:3001/approval-requests/{{REQUEST_ID}}' \
--header 'Content-Type: application/json' \
--data '{"state": "RespondedRejected"}'

Comprobamos que el estado no se ha modificado buscando a nuestros sujetos, sin embargo, el sn del sujeto habrá aumentado en 1:

También podemos buscar un evento específico con la api de eventos: /subjects/{id}/events/{sn} cuyo id es el SubjectId del sujeto, el sn es el evento específico al que vamos a buscar (si no se agrega nada devolverá todos los eventos del asunto) y la solicitud es de tipo GET.

curl --silent 'http://localhost:3000/subjects/{{GOVERNANCE-ID}}/events/4' \

Ahora repetiremos la misma solicitud pero votaremos sí con ambos nodos, los cuales aprobarán la solicitud y modificarán el estado del asunto. Aprobamos la gobernanza en el tercer nodo y veremos cómo se actualiza en un corto periodo de tiempo.

4 - Próximos pasos

¿Qué siguientes pasos podemos dar?

Una vez llegado a este punto, tenemos una pequeña red de tres nodos con una gobernanza común para todos ellos. A partir de ahí podremos realizar configuraciones extra para adaptar la red al caso de uso que se requiera, tanto modificando la gobernanza como creando sujetos que no sean gobernanzas.

En los siguientes tutoriales veremos cómo realizar los siguientes pasos, que requieren un manejo más avanzado de kore:

  • Agregar nuevos esquemas a la gobernanza.
  • Creación de contratos.
  • Creación de sujetos.
  • Invocación externa.