Bolt is simply a key-value repository inspired by the Howard Chu LMDB project . The goal of the project is to provide a simple, fast and reliable database for projects that do not require a full-fledged database server, such as Postgres or MySQL.Sounds perfect for our needs! Take a moment to review the base.
Since Bolt is intended to be used as such a low-level element of functionality, simplicity is key. The API will be small and focus only on getting values ​​and setting values. It's all!
Block
), we must serialize them, that is, implement a mechanism for translating the structure into a byte array and restoring it back from the array. We will use encoding / gob for this, although JSON, XML, Protocol Buffers
also suitable. We use encoding/gob
because it is simple and it is part of the standard Go library.blocks
stores metadata describing all the blocks in a chain.chainstate
stores the chain state, which represents all unspent transaction outputs and some metadatablocks
pair key->value
is:'b' + 32- -> 'f' + 4- -> 'l' -> 4- : 'R' -> 1- boolean : 'F' + 1- + -> 1 boolean: , 't' + 32- ->
'b' + 32- -> 'f' + 4- -> 'l' -> 4- : 'R' -> 1- boolean : 'F' + 1- + -> 1 boolean: , 't' + 32- ->
'b' + 32- -> 'f' + 4- -> 'l' -> 4- : 'R' -> 1- boolean : 'F' + 1- + -> 1 boolean: , 't' + 32- ->
'b' + 32- -> 'f' + 4- -> 'l' -> 4- : 'R' -> 1- boolean : 'F' + 1- + -> 1 boolean: , 't' + 32- ->
'b' + 32- -> 'f' + 4- -> 'l' -> 4- : 'R' -> 1- boolean : 'F' + 1- + -> 1 boolean: , 't' + 32- ->
'b' + 32- -> 'f' + 4- -> 'l' -> 4- : 'R' -> 1- boolean : 'F' + 1- + -> 1 boolean: , 't' + 32- ->
'b' + 32- -> 'f' + 4- -> 'l' -> 4- : 'R' -> 1- boolean : 'F' + 1- + -> 1 boolean: , 't' + 32- ->
In chainstate
pairs, key->value
is:'c' + 32- -> 'B' -> 32- : ,
'c' + 32- -> 'B' -> 32- : ,
'c' + 32- -> 'B' -> 32- : ,
(A detailed explanation can be found here )blocks
. In addition, as mentioned above, we will store the entire database in one file, without storing the blocks in separate files. Therefore, we do not need anything related to file numbers. Therefore, the key->value
pairs that we will use are:[]byte
type, and we want to store the Block
structure in the database. We will use encoding/gob
to serialize the structures.Serialize
method for Block
(error handling is omitted for brevity) func (b *Block) Serialize() []byte { var result bytes.Buffer encoder := gob.NewEncoder(&result) err := encoder.Encode(b) return result.Bytes() }
gob
encoder and encode the block, return the result as an array of bytes.Block
. This will not be a method, but an independent function: func DeserializeBlock(d []byte) *Block { var block Block decoder := gob.NewDecoder(bytes.NewReader(d)) err := decoder.Decode(&block) return &block }
NewBlockchain
function. Now she creates a new copy of Blockchain
and adds a genesis block to it. We want to do the following:Blockchain
instanceBlockchain
instance to the hash of the last block stored in the databaseBlockchain
instance with a tip indicating the genesis of the block func NewBlockchain() *Blockchain { var tip []byte db, err := bolt.Open(dbFile, 0600, nil) err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) if b == nil { genesis := NewGenesisBlock() b, err := tx.CreateBucket([]byte(blocksBucket)) err = b.Put(genesis.Hash, genesis.Serialize()) err = b.Put([]byte("l"), genesis.Hash) tip = genesis.Hash } else { tip = b.Get([]byte("l")) } return nil }) bc := Blockchain{tip, db} return &bc }
db, err := bolt.Open(dbFile, 0600, nil)
err = db.Update(func(tx *bolt.Tx) error { ... })
(db.Update(...))
, because we plan to put the genesis block in the database. b := tx.Bucket([]byte(blocksBucket)) if b == nil { genesis := NewGenesisBlock() b, err := tx.CreateBucket([]byte(blocksBucket)) err = b.Put(genesis.Hash, genesis.Serialize()) err = b.Put([]byte("l"), genesis.Hash) tip = genesis.Hash } else { tip = b.Get([]byte("l")) }
l
from it, if it does not exist, then we generate a genesis block, create a basket, save the block in it and update the key l
, which stores the hash of the last block in the chain.Blockchain
: bc := Blockchain{tip, db}
Blockchain
structure looks like now: type Blockchain struct { tip []byte db *bolt.DB }
AddBlock
method: adding blocks to a chain is no longer as simple as adding an element to an array. From now on, we will store the blocks in the database: func (bc *Blockchain) AddBlock(data string) { var lastHash []byte err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) return nil }) newBlock := NewBlock(data, lastHash) err = bc.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) err = b.Put([]byte("l"), newBlock.Hash) bc.tip = newBlock.Hash return nil }) }
err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) return nil })
newBlock := NewBlock(data, lastHash) b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) err = b.Put([]byte("l"), newBlock.Hash) bc.tip = newBlock.Hash
l
, which now stores the hash of the new block. type BlockchainIterator struct { currentHash []byte db *bolt.DB }
Blockchain
instance that stores the connection to the database) and, thus, is created in the Blockchain
method: func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} return bci }
BlockchainIterator
does only one thing: returns the next block from the blockchain. func (i *BlockchainIterator) Next() *Block { var block *Block err := i.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) encodedBlock := b.Get(i.currentHash) block = DeserializeBlock(encodedBlock) return nil }) i.currentHash = block.PrevBlockHash return block }
NewBlockchain, bc.AddBlock
in main
. It's time to improve it! We want to have such commands: blockchain_go addblock "Pay 0.031337 for a coffee" blockchain_go printchain
CLI
structure. type CLI struct { bc *Blockchain }
Run
function Run
func (cli *CLI) Run() { cli.validateArgs() addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) addBlockData := addBlockCmd.String("data", "", "Block data") switch os.Args[1] { case "addblock": err := addBlockCmd.Parse(os.Args[2:]) case "printchain": err := printChainCmd.Parse(os.Args[2:]) default: cli.printUsage() os.Exit(1) } if addBlockCmd.Parsed() { if *addBlockData == "" { addBlockCmd.Usage() os.Exit(1) } cli.addBlock(*addBlockData) } if printChainCmd.Parsed() { cli.printChain() } }
addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) addBlockData := addBlockCmd.String("data", "", "Block data")
addblock
and printchain
, then add the -data
flag to the first one. printchain
does not require any flags. switch os.Args[1] { case "addblock": err := addBlockCmd.Parse(os.Args[2:]) case "printchain": err := printChainCmd.Parse(os.Args[2:]) default: cli.printUsage() os.Exit(1) }
if addBlockCmd.Parsed() { if *addBlockData == "" { addBlockCmd.Usage() os.Exit(1) } cli.addBlock(*addBlockData) } if printChainCmd.Parsed() { cli.printChain() }
func (cli *CLI) addBlock(data string) { cli.bc.AddBlock(data) fmt.Println("Success!") } func (cli *CLI) printChain() { bci := cli.bc.Iterator() for { block := bci.Next() fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) fmt.Printf("Data: %s\n", block.Data) fmt.Printf("Hash: %x\n", block.Hash) pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) fmt.Println() if len(block.PrevBlockHash) == 0 { break } } }
BlockchainIterator
to iterate over the blocks in the blockchain.main
function accordingly: func main() { bc := NewBlockchain() defer bc.db.Close() cli := CLI{bc} cli.Run() }
Blockchain
is created regardless of which command line arguments were passed. $ blockchain_go printchain No existing blockchain found. Creating a new one... Mining the block containing "Genesis Block" 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b Prev. hash: Data: Genesis Block Hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b PoW: true $ blockchain_go addblock -data "Send 1 BTC to Ivan" Mining the block containing "Send 1 BTC to Ivan" 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 Success! $ blockchain_go addblock -data "Pay 0.31337 BTC for a coffee" Mining the block containing "Pay 0.31337 BTC for a coffee" 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148 Success! $ blockchain_go printchain Prev. hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 Data: Pay 0.31337 BTC for a coffee Hash: 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148 PoW: true Prev. hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b Data: Send 1 BTC to Ivan Hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 PoW: true Prev. hash: Data: Genesis Block Hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b PoW: true
Source: https://habr.com/ru/post/351296/
All Articles