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