Cómo diseñé bloques y transacciones en mi blockchain Go

Para terminar con una cadena de bloques y no solo una base de datos, necesitamos agregar 3 elementos importantes a nuestro proyecto:

  • Descripción de la estructura y los métodos de datos del bloque.
  • Descripción de la estructura de datos y métodos de transacción.
  • Funciones de blockchain que guardan bloques en una base de datos y los encuentran allí por su hash o altura (u otra cosa).

Cómo diseñé bloques y transacciones en mi blockchain Go

Este es el segundo artículo sobre blockchain para la industria, el primero aquí.

Recordando las preguntas que me hicieron los lectores sobre el artículo anterior de esta serie, cabe señalar: en este caso, la base de datos LevelDB se utiliza para almacenar datos de blockchain, pero nada impide utilizar cualquier otro, digamos, MySQL. Ahora veamos la estructura de estos datos.

Comencemos con las transacciones: github.com/Rusldv/bcstartup/blob/master/transaction/builder.go

Aquí está su estructura de datos:

type TX struct {
	DataType byte		
	TxHash string 
	TxType byte	
	Timestamp int64		
	INs []TxIn
	OUTs []TxOut
}

type TxIn struct {
	ThatTxHash string
	TxOutN int
	ByteCode string
}

type TxOut struct {
	Value int
	ByteCode string
}

TX almacena el tipo de datos (para la transacción 2), el hash de esa transacción, el tipo de la transacción en sí, una marca de tiempo y entradas y salidas. Las entradas TxIn almacenan el hash de la transacción a cuya salida se hace referencia, el número de esta salida y el código de bytes, y las salidas TxOut almacenan algún valor y también el código de bytes.

Ahora veamos qué acciones puede realizar una transacción sobre sus datos, es decir. Veamos los métodos.

Para crear una transacción, utilice la función transacción.NewTransaction(txtype byte) *TX.

El método AddTxIn(thattxhash []byte, txoutn int, code []byte) (*TxIn, error) agrega una entrada a la transacción.

El método AddTxOut(valor int, datos []byte) (*TxOut, error) agrega una salida a la transacción.

El método ToBytes() []byte convierte la transacción en un segmento de bytes.

La función interna preByteHash(bytes []byte) cadena se utiliza en Build() y Check() para hacer que el hash de transacción generado sea compatible con los hashes de transacción generados a partir de aplicaciones JavaScript.

El método Build() establece el hash de la transacción de la siguiente manera: tx.TxHash = preByteHash(tx.ToBytes()).

El método de cadena ToJSON() convierte una transacción en una cadena JSON.

El método de error FromJSON(data []byte) carga una transacción desde el formato JSON pasado como un segmento de bytes.

El método bool Check() compara el hash resultante del campo hash de la transacción con el hash obtenido como resultado del hash de esta transacción (ignorando el campo hash).

Las transacciones se agregan al bloque: github.com/Rusldv/bcstartup/blob/master/block/builder.go

La estructura de datos del bloque es más voluminosa:

type Block struct {
	DataType byte				
	BlockHeight int					
        Timestamp int64				 
        HeaderSize int					
        PrevBlockHash string				 
        SelfBlockHash string			
	TxsHash string			
	MerkleRoot string
	CreatorPublicKey string			
	CreatorSig string
	Version int
	TxsN int
	Txs []transaction.TX
}

DataType almacena el tipo de datos, el nodo lo usa y distingue el bloque de una transacción u otros datos. Para un bloque este valor es 1.

BlockHeight almacena la altura del bloque.
Marca de tiempo.
HeaderSize es el tamaño del bloque en bytes.
PrevBlockHash es el hash del bloque anterior y SelfBlockHash es el hash del bloque actual.
TxsHash es un hash general de transacciones.
MerkleRoot es la raíz del árbol Merkle.

Más adelante en los campos está la clave pública del creador del bloque, la firma del creador, la versión del bloque, el número de transacciones en el bloque y estas transacciones en sí.

Veamos sus métodos:
Para crear un bloque, use la función block.NewBlock(): NewBlock(prevBlockHash string, height int) *Block, que toma el hash del bloque anterior y la altura establecida para el bloque creado en la cadena de bloques. El tipo de bloque también se establece desde la constante del paquete de tipos:

b.DataType = types.BLOCK_TYPE.

El método AddTx(tx *transaction.TX) agrega una transacción a un bloque.

El método Build() carga valores en los campos del bloque y genera y establece su hash actual.

El método ToBytesHeader() []byte convierte el encabezado del bloque (sin transacciones) en un segmento de bytes.

El método de cadena ToJSON() convierte el bloque al formato JSON en una representación de cadena de los datos.

El método de error FromJSON(data []byte) carga datos de JSON en una estructura de bloque.

El método bool Check() genera un hash de bloque y lo compara con el especificado en el campo hash de bloque.

El método de cadena GetTxsHash() devuelve el hash total de todas las transacciones en el bloque.

El método GetMerkleRoot() especifica la raíz del árbol Merkle para las transacciones en un bloque.

El método Sign(privk string) firma un bloque con la clave privada del creador del bloque.

El método SetHeight(height int) escribe la altura del bloque en el campo de estructura del bloque.

El método int GetHeight() devuelve la altura del bloque como se especifica en el campo correspondiente de la estructura del bloque.

El método de byte ToGOBBytes() [] codifica un bloque en formato GOB y lo devuelve como un segmento de bytes.

El método de error FromGOBBytes(datos []byte) escribe datos del bloque en la estructura del bloque desde el segmento de bytes pasado en formato GOB.

El método de cadena GetHash() devuelve el hash del bloque dado.

El método de cadena GetPrevHash() devuelve el hash del bloque anterior.

El método SetPublicKey(pubk string) escribe la clave pública del creador del bloque en el bloque.

Por lo tanto, utilizando los métodos del objeto Block, podemos convertirlo fácilmente a un formato para transmitirlo a través de la red y guardarlo en la base de datos LevelDB.

Las funciones del paquete blockchain son responsables de guardar en blockchain: github.com/Rusldv/bcstartup/tree/master/blockchain

Para hacer esto, el bloque debe implementar la interfaz IBlock:

type IGOBBytes interface {
	ToGOBBytes() []byte
	FromGOBBytes(data []byte) error
}

type IBlock interface {
	IGOBBytes
	GetHash() string
	GetPrevHash() string
	GetHeight() int
	Check() bool

}

La conexión a la base de datos se crea una vez cuando el paquete se inicializa en la función init():

db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).

CloseDB() es un contenedor para db.Cloce(), llamado después de trabajar con las funciones del paquete para cerrar la conexión a la base de datos.

La función de error SetTargetBlockHash (cadena hash) escribe el hash del bloque actual con la clave especificada por la constante BLOCK_HASH en la base de datos.

La función GetTargetBlockHash() (cadena, error) devuelve el hash del bloque actual almacenado en la base de datos.

La función de error SetTargetBlockHeight(height int) escribe en la base de datos el valor de la altura de la cadena de bloques para el nodo con la clave especificada por la constante BLOCK_HEIGHT.

La función GetTargetBlockHeight() (int, error) devuelve la altura de la cadena de bloques para un nodo determinado, almacenado en la base de datos.

La función bool CheckBlock(block IBlock) verifica la corrección de un bloque antes de agregarlo a la cadena de bloques.

La función de error AddBlock(block IBlock) agrega un bloque a la cadena de bloques.

Las funciones para recuperar y ver bloques se encuentran en el archivo explore.go del paquete blockchain:

La función GetBlockByHash(cadena hash) (*block.Block, error) crea un objeto de bloque vacío, carga en él un bloque desde la base de datos, cuyo hash se le pasó, y le devuelve un puntero.

La creación de un bloque génesis se lleva a cabo mediante la función de error Genesis() del archivo genesis.go del paquete blockchain.

El próximo artículo hablará sobre cómo conectar clientes a un nodo utilizando el mecanismo WebSocket.

Fuente: habr.com

Añadir un comentario