Anatomía de una transacción Hyperledger Fabric

El componente más importante de cualquier tecnología Blockchain es la transacción. Una transacción implementa la operación básica y atómica que se puede realizar en una Blockchain. Aunque cada tecnología Blockchain tiene su propia arquitectura de transacción, todas se basan en un esquema muy parecido al siguiente:

  • Dirección origen. Dirección de la identidad que ha emitido la transacción.
  • Dirección destino. Dirección de la identidad a la que se envía la transacción.
  • Firma digital. Consiste en la firma digital que realiza la identidad de origen y que permite a la identidad destino comprobar la autoría de la transacción.
  • Datos. Corresponde al conjunto de datos que transporta la transacción.

Aunque este es un esquema muy sencillo, es la base sobre la que se construyen todos los tipos de transacciones de las distintas tecnologías Blockchain.

Transacciones de tipo UTXO

Algunos tipos de transacciones tiene esquemas sencillos, porque los casos de usos para los que fueron diseñadas solo sencillo, por ejemplo, una transacción Bitcoin implementa un esquema UTXO (unspent transaction output ), que consiste incluir en la transacción, una o varias transacciones de entrada y una o varias transacciones de salida. El siguiente cuadro representa un ejemplo de una transacción Bitcoin, en la que podemos ver:

  • txid. El identificador de la transacción.
  • inputs. Con la transacción de origen.
  • outputs. Con las direcciones de destino.
{
  "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",
  "size": 275,
  "version": 1,
  "locktime": 0,
  "fee": 0,
  "inputs": [
    {
      "coinbase": false,
      "txid": "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9",
      "output": 0,
      "sigscript": "47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901",
      "sequence": 4294967295,
      "pkscript": "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac",
      "value": 5000000000,
      "address": "12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S",
      "witness": []
    }
  ],
  "outputs": [
    {
      "address": "1Q2TWHE3GMdB6BZKafqwxXtWAWgFt5Jvm3",
      "pkscript": "4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac",
      "value": 1000000000,
      "spent": true,
      "spender": {
        "txid": "ea44e97271691990157559d0bdd9959e02790c34db6c006d779e82fa5aee708e",
        "input": 0
      }
    },
    {
      "address": "12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S",
      "pkscript": "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac",
      "value": 4000000000,
      "spent": true,
      "spender": {
        "txid": "a16f3ce4dd5deb92d98ef5cf8afeaf0775ebca408f708b2146c4fb42b41e14be",
        "input": 0
      }
    }
  ],
  "block": {
    "height": 170,
    "position": 1
  },
  "deleted": false,
  "time": 1231731025,
  "rbf": false,
  "weight": 1100
}

Como se puede ver, el propósito de este tipo de transacciones es poder implementar una forma segura de transferir valor, en este caso bitcoins, sin la necesidad de implementar una gestión de saldos. El saldo de un usuario está formado, por la suma de todas las transacciones que tengan un output dirigido a su dirección y que no exista una transacción que tenga un input asociado a dichos outputs.

Transacciones Hyperledger Fabric

Las transacciones de Fabric tienen algunas peculiaridades que las diferencian de otras tecnologías Blockchain. Por ejemplo, no existe la dirección de destino, de hecho en Fabric las transacciones tienen como destino un chaincode, nunca la dirección de una identidad digital. Esto puede parecer chocante al principio, pero hay que tener en cuenta que Fabric se pensó para trabajar con procesos, los cuales, deben estar implementados con los chaincodes. Pero esta no es la única particularidad de las transacciones de Fabric, otra bastante curiosa es que la transacción incorpora varias firmas digitales, no solo la del emisor de la transacción. El ciclo de vida de una transacción de Fabric pasa por varias fases, una de ellas es la fase de Endorsement, en la que uno o varios peer deben ejecutar la transacción y devolver una respuesta. Esta respuesta está firmada digitalmente por los nodos que participan en el Endorsement.

Pero antes de meternos a fondo a ver la anatomía de una transacción de Fabric, hagamos una pequeña introducción sobre la estructura de un bloque. Porque también tiene algunas peculiaridades interesantes de conocer.

Para representar y explicar las estructuras de datos y los tipos, voy a utilizar las estructura de Go que tienes disponible en el código fuente de Fabric y la salida de nuestra herramienta InspectorBlock, que desarrollamos para inspeccionar transacciones y bloques utilizando los ficheros de la Blockchain.

Un bloque de Fabric

La estructura de un bloque está formada por tres atributos, los cuales se utilizan para gestionar la cabecera, los datos y los metadatos. El siguiente bloque muestra la estructura con los atributos y los tipos de cada uno de los atributos.

type Block struct {
    Header              *BlockHeader   
    Data                *BlockData    
    Metadata            *BlockMetadata 
}

Cabecera del bloque

La cabecera del bloque se gestiona con la estructura de datos BlockHeader, la cual está formada por tres atributos como se puede ver en el siguiente cuadro.

  • Number, que identifica al bloque de manera numérica.
  • PreviousHash, es un array de bytes que almacena al hash del bloque anterior.
  • DataHash, es un array de bytes que almacena el hash de la sección Data del bloque, consiste en el hash del bloque.
type BlockHeader struct {
    Number               uint64   
    PreviousHash         []byte  
    DataHash             []byte  
}

Datos del bloque

Los datos del bloque se gestionan con una estructura de tipo BlockData, la cual tiene un único atributo, Data que es un array de arrays de bytes. Este array los forman las transacciones que se han incluido en el bloque. Debemos utilizar el tipo Envelope, para convertir los arrays de bytes en estructura de tipo Envelope.

type BlockData struct {
    Data                 [][]byte 
}

type Envelope struct {
    Payload 		[]byte
    Signature           []byte  
}

La estructura Envelope es la base de la transacción de Fabric. Envelope está formada por dos atributos, Payload y Signature, que forman la transacción y que veremos con mayor detalle posteriormente.

Metadatos del bloque

Cada bloque tiene una estructura de tipo BlockMetadata para gestionar los metadatos. Esta estructura tiene un único atributo que es un array de arrays de bytes. Este array tiene 5 posiciones, cada una de ellas con un conjunto de datos distintos. En el cuadro se pueden ver las 5 constantes definidas en el código de Fabric para identificar a las distintas posiciones del array.

type BlockMetadata struct {
    Metadata             [][]byte 
}

const (
   BlockMetadataIndex_SIGNATURES          BlockMetadataIndex = 0
   BlockMetadataIndex_LAST_CONFIG         BlockMetadataIndex = 1
   BlockMetadataIndex_TRANSACTIONS_FILTER BlockMetadataIndex = 2 
   BlockMetadataIndex_ORDERER             BlockMetadataIndex = 3
   BlockMetadataIndex_COMMIT_HASH         BlockMetadataIndex = 4
)

  • BlockMetadataIndex_SIGNATURES. Esta sección registra las firmas de los nodos del servicio de Orderer que han generado el bloque.
  • BlockMetadataIndex_LAST_CONFIG. Esta sección almacena la posición del último bloque de configuración.
  • BlockMetadataIndex_TRANSACTIONS_FILTER. Aquí se almacenan el estado de validación de cada una de las transacciones que forman el bloque. Consiste en un array con los códigos de estado de cada una de las transacciones del bloque.
  • BlockMetadataIndex_COMMIT_HASH. Es el hash del TRANSACTIONS_FILTER, de los updates y el COMMIT_HASH del bloque anterior.

Estructura de la transacción

Ya hemos dado un pequeño repaso a la estructura de un bloque en Fabric, ahora vamos a ver cuál es la estructura de una transacción, porque como hemos comentado anteriormente, existen alguna peculiaridades que son interesante que conozcamos.

Como vimos en la sección anterior, el bloque dispone de una estructura de datos  de tipo BlockData, la cual contiene un array de elementos de tipo Envelope. Cada uno de estos elementos de tipo Envelope corresponde con una transacción. También hemos visto que el tipo Envelope está formado por dos atributos, Payload que contiene los datos relacionados con la transacción y Signature que contiene la firma digital de la transacción.

type Envelope struct {
    Payload 		[]byte
    Signature           []byte  
}

Vamos a ver el atributo Payload, que es de tipo Payload y está formado por dos atributo, Header con la información de la cabecera y Data con el cuerpo de la transacción.

type Payload struct {
    Header       *Header 
    Data         []byte   
}

El atributo Header es de tipo Header, el cuál tiene la siguiente estructura:

Payload
  Header  *Header
     ChannelHeader
            TxId: 823af8439bbfe63e5d0b4df3934eb779300c5d57619ac657aeb1598640d26f30 
            ChannelId: test 
            Version: 0 
            Type: ENDORSER_TRANSACTION 
            Timestamp: 2023-06-07 15:16:59 +0000 UTC 
              Epoch: 0 
              Extensions
                        Path:  
                        Name: prueba01 
                        Version:  
SignatureHeader
              Creator   []byte   
                     SerializedIdentity
                           MspId : org01 
                           IdBytes: OU=admin,C=US,CN=admin@org01.test01.zz

  • ChannelHeader, con la información del tipo de transacción, el identificador de la transacción,Timestamp o el identificador del canal.
  • SignatureHeader, que tiene la información de la identidad del creador de la transacción. Para el ejemplo, el usuario “admin@org01.test01.zz”. El atributo IdBytes en realidad contiene el certificado como un array de bytes, aquí los hemos representado con el “Subject” del x.509.

Ahora veamos el otro atributo de la estructura Payload, hablamos del atributo Data, el cual puede convertirse en varios tipos de datos distintos, en función del tipo de transacción.

type Payload struct {
    Header       *Header 
    Data         []byte   
}

Para saber el tipo de transacción debemos utilizar el atributo Payload.Header.ChannelHeader.Type, el cual nos facilitará alguna de las siguientes:

const (
    HeaderType_MESSAGE                               HeaderType = 0
    HeaderType_CONFIG                                   HeaderType = 1
    HeaderType_CONFIG_UPDATE                   HeaderType = 2
    HeaderType_ENDORSER_TRANSACTION HeaderType = 3
    HeaderType_ORDERER_TRANSACTION    HeaderType = 4
    HeaderType_DELIVER_SEEK_INFO            HeaderType = 5
    HeaderType_CHAINCODE_PACKAGE         HeaderType = 6
    HeaderType_PEER_ADMIN_OPERATION   HeaderType = 8
)

Cada uno de estos tipos de transacciones nos ayudan a conocer cómo debemos convertir el array de bytes que es el atributo Data en tipo de datos relacionado con el tipo de transacción. Por ejemplo, si tenemos un transacción de tipo:

  • HeaderType_CONFIG debemos convertir Data a tipo ConfigEnvelope
  • HeaderType_CONFIG_UPDATE debemos convertir Data a tipo  ConfigUpdateEnvelope
  • HeaderType_ENDORSER_TRANSACTION debemos convertir Data a tipo Transaction
  • HeaderType_ORDERER_TRANSACTION debemos convertir Data a tipo Envelope

Para nuestro ejemplo, el tipo de transacción es HeaderType_ENDORSER_TRANSACTION, por lo tanto, debemos convertir el contenido del atributo Data al tipo Transaction. El tipo Transaction tiene un único atributo, que es un array de elementos de tipo TransactionAction.

type Transaction struct {
        Actions              []*TransactionAction    
}

Y aquí vamos a detenernos un momento, porque para continuar debemos aclarar este concepto. Y es que en Fabric, una transacción puede estar formada por varias acciones. Normalmente, una transacción tiene una única acción, pero podríamos construir transacciones formadas por varias acciones. ¿Pero cuándo podríamos necesitar incluir en una transacción varias acciones? El objetivo de disponer de varias acciones dentro de la misma transacción, es ejecutar todas las acciones de manera atómica y en caso de que alguna de ellas no pueda ser validada, el resto de acciones quedarán invalidadas también. Es decir, utilizar grupos de acciones, nos permite agruparlas de manera que se validan todas o no se validan ninguna.

Las acciones que forman parte de la transacción son manejadas con una estructura de tipo TransactionAction, que tiene dos atributos, Header y Payload, ambos son un array de bytes.

type TransactionAction struct {
    Header                []byte 
    Payload               []byte  
}

El contenido del atributo Header se convierte a un tipo SignatureHeader. Igual que ocurre con Envelope.Payload.Header.SignatureHeader, este tipo gestiona la identidad del creador de la transacción, en este caso, sería la identidad del creador de la acción. Para transacciones que tengan una sola acción, ambos creadores suelen ser el mismo.

TransactionAction
         Header  []byte 
              SignatureHeader
                      Creator []byte
                            SerializedIdentity
                                    MspId : org01 
                                    IdBytes: OU=admin,C=US,CN=admin@org01.test01.zz

El otro atributo de la estructura TransactionAction es Payload, cuyo contenido se convierte al tipo ChaincodeActionPayload, que cuenta con dos atributos, tal como se ve en el siguiente cuadro, un atributo ChaincodeProposalPayload y otro Action. El primero contiene información sobre la llamada al chaincode, nombre, argumentos, y tipo.

TransactionAction
      Payload []byte  
         ChaincodeActionPayload
            ChaincodeProposalPayload []byte
                        Input []byte
                           ChaincodeSpec
                              type:Golang
                              name: prueba01 
                              args[0]: ChangeContador
                              args[1]: contador108
                        TransientMap
            Action *ChaincodeEndorsedAction

El segundo atributo de ChaincodeActionPayload es Action y es de tipo *ChaincodeEndorsedAction. Este tipo tiene dos atributos, ProposalResponsePayload que es un array de bytes y Endorsements que es un array de tipo Endorsement.

TransactionAction
      Payload  []byte  
         ChaincodeActionPayload                       
            Action *ChaincodeEndorsedAction
               ProposalResponsePayload []byte
               Endorsements         []*Endorsement

Vamos a ver primero el atributo Endorsements. Es un array con la información de todos los Peers que han participado en la fase de endorsement de la transacción. Para el ejemplo, han participado un peer de cada una de las dos organizaciones que pertenecen al channel, que han sido los Peers peer01.org01.test01.zz y peer01.org02.test01.zz. Además se incluye la firma digital de la ProposalResponsePayload que ha ejecutado cada Peer.

Endorsements   []*Endorsement
   Endorsements[0]
      Endorser
            Creator
               MspID: org01
               IdBytes: OU=peer,CN=peer01.org01.test01.zz
      Signature: 304509a257...f08cd6a1785c8f06e5ad2a1aabcad258e6fbd860c559
   Endorsements[1]
         Endorser
            Creator
               MspID: org02
               IdBytes: OU=peer,CN=peer01.org02.test01.zz
         Signature: 304402...3014fc21c462c83fb508fe85ccf677c70023f

Una vez visto el contenido del atributo Endorsements, volvamos a ProposalResponsePayload. Este atributo es un array de bytes que se debe transformar al tipo ProposalResponsePayload, que tiene dos atributos, ProposalHash que es un hash de la propuesta y el atributo Extension con todas las acciones que el chaincode ha realizado sobre el worldstate.

ProposalResponsePayload  []bytes
   ProposalResponsePayload
     ProposalHash: ba1c2da0606d915c136d6fe32c692e6c01de384de25abf120d345d3864d66b58
     Extension []byte
            ChaincodeAction
                  Results []byte
                     TxRwSet
                        NsRwSets[0]
                           NsRwSet
                              NameSpace: prueba01
                              KvRwSet
                                 Reads
                                    KVRead
                                       Key: contador108  
                                       Version: 58.3   
                                 Writes
                                    KVWrite
                                       Key: contador108
                                       IsDelete: false
                                       Value: 1

En el ejemplo del recuadro anterior podemos ver, como la acción que realiza el chaincode prueba01 consiste en leer del worldstate la key con valor contador108 y generar una escritura sobre la misma key, actualizando su valor con el dato “1”. La lectura se ha realizado sobre la versión “58.3”, esta forma de versionar los datos consiste en utilizar el siguiente esquema:

<#bloque>.<#tx>

Para nuestro ejemplo, la última modificación de la key contador108 se produjo en la transacción número 3 del bloque 58. Si vamos a la blockchain y localizamos ese bloque y transacción, podremos comprobar que hay una escritura sobre la key “contador108”.

La siguiente imagen muestra un ejemplo de la estructura básica de un bloque, en la que se puede ver de forma fácil, todo lo que hemos comentado a lo largo del post.

Conclusión

En este post hemos analizado la estructura de uno de los tipos de transacciones que tiene Fabric,  el tipo ENDORSER_TRANSACTION, es la transacción más habitual en cualquier infraestructura Fabric y es importante conocer su estructura, para entender cómo Fabric gestiona el ciclo de vida las transacciones, los datos que maneja sobre el chaincode, los peers que participan en el proceso de endorsement o el estado de validación.  He dejado en el tintero para futuros post varios temas relacionados con la forma en la que Fabric gestiona las transacciones, por ejemplo, los otros tipos de transacciones que existen como CONFIG_UPDATE o la forma en la que la transacción maneja los datos cuando utilizamos las colecciones privadas de Fabric.

José Mora

José Juan Mora Pérez
CTO & Founder

Trabaja con Nosotros

En Kolokium estamos siempre buscando talento, gente inquieta que no le tenga miedo a los retos, si quieres trabajar con tecnologías Blockchain.

INNOVACIÓN

COLABORAMOS EN INICIATIVAS PÚBLICO/PRIVADAS ORIENTADAS A EXPLORAR LAS POSIBILIDADES DE LA TECNOLOGÍA BLOCKCHAIN EN DISTINTOS HÁBITO INDUSTRIALES Y CORPORATIVOS
neotec

PRIOPS

El proyecto PRIOPS ha recibido el apoyo del CDTI por medio de su programa Neotec 2018, en el que se le ha concedido una subvención de 247.618 €

apia

APIA

Plataforma integral para la auditoría inteligente de obra civil basado en la captura y parametrización automática de identidades de obra en el modelo de información BIM y la certificación mediante Blockchain de su producción, financiado por el CDTI y cofinanciado por el FEDER

Consorcio: AZVI, EMERGYA, GRANT THORNTON Y KOLOKIUM
Plazo de ejecución: septiembre de 2018 a diciembre 2020
Presupuesto Total: 2.218.874,00€

k1

K 1

Framework para la generación y despliegue automatizado de smart contracts en arquitecturas distribuidas Ethereum e Hyperledger Fabric. Proyecto financiado con el apoyo

K1_FRAMEWORK PARA LA GENERACIÓN Y DESPLIEGUE AUTOMATIZADO DE SMART CONTRACTS EN LOS BLOCKCHAINS DE ETHEREUM E HYPERLEDGER del CDTI con fondos propios a través de la convocatoria INNOGLOBAL 2017 y apoyado por el Ministerio de Economía, Industria y Competitividad.

Consorcio: KOLOKIUM BLOCKCHAIN TECHNOLOGIES y GRUPO CADENA (Colombia)
Plazo de ejecución: octubre de 2017 a septiembre de 2019
Presupuesto KOLOKIUM: 381.440€

Logos Paravasis

PARAVASIS

PARAVASIS es un proyecto Subvencionado por el CDTI que ha sido apoyado por el Ministerio de Ciencia e Innovación, y que investiga en nuevas tecnologías para que haya una mejora sustancial en la flexibilidad y productividad del proceso de diseño y desarrollo de sistemas industriales complejos favoreciendo la personalización de nuevos productos intensivos en software y considerando además el mejor balance de tiempo, capacidad y coste, así como la seguridad.

Consorcio: Ghenova Digital, DHG, Integrasys, Cotesa, Capgemini Engineering, Optiva Media, Kolokium y Komorebi.

Plazo de ejecución: 01/10/2022 – 30/06/2025

Presupuesto Global: 5.364.425,00 €
Presupuesto Kolokium: 437.163,00 

Logos Valrec

VALREC

El objetivo principal del Proyecto VALREC es la investigación industrial y la demostrar nuevas soluciones avanzadas y de coste efectivo que garanticen un cierre de ciclos más eficiente y trazable (incremento de la confianza de materiales secundarios en el mercado) de grandes volúmenes de recursos materiales de construcción mayoritarios (principalmente hormigón, cerámico y yeso) a lo largo de toda la cadena de suministro de los mismos.
El proyecto VALREC “Soluciones innovadoras para fomentar la VALorización de RCD y la utilización de materiales Recuperados bajo criterios de Economía Circular en la CAM” ha sido subvencionado a través de la Convocatoria 2020 de las ayudas cofinanciadas por el Fondo Europeo de Desarrollo Regional para contribuir a la mejora de la Cooperación Público - Privada en materia de I+D+i mediante el apoyo a Proyectos de Innovación Tecnológica de efecto tractor elaborados por núcleos de innovación abierta en la Comunidad de Madrid, en el marco de la Estrategia Regional de Investigación e Innovación para una Especialización Inteligente (RIS3), dentro del Programa Operativo FEDER de la Comunidad de Madrid para el periodo 2014-2020.
Consorcio: SURGE AMBIENTAL (SURGE), VALORIZA SERVICIOS MEDIOAMBIENTALES (VSM), ADCORE, KOLOKIUM BLOCKCHAIN TECHNOLOGIES, ALLGAIER MOGENSEN, SODIRA IBERIA, SIKA, HORMICRUZ, GREEN BUILDING COUNCIL ESPAÑA (GBCe).
Plazo de ejecución: 17/11/2021 - 17/11/2023
Presupuesto Global: 4.063.243,14 €
Presupuesto Kolokium: 256.700,00 €

KOLBLM

Completa el formulario para descargar​

KOLBI

Completa el formulario para descargar​

KOLFSB

Completa el formulario para descargar