TIL 24 - Golang으로 블록체인 만들기(DB, Flag 적용하기) 7

프동프동·2023년 2월 10일
0

TIL

목록 보기
24/46
post-thumbnail

os.Args 사용

  • os.Args
    var Args []string
    CLI에서 사용된 문자열 배열을 리턴합니다. 첫번째 인자는 실행프로그램의 이름
  • 소스 코드
    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    func usage() {
    	fmt.Printf("Welcome to FDong Coin\n\n")
    	fmt.Printf("Please use the following commands:\n\n")
    	fmt.Printf("explorer: 		Start the HTML Explorer\n")
    	fmt.Printf("rest:			Start the REST API (recommende)\n")
    	os.Exit(0)
    }
    
    func main() {
    
    	if len(os.Args) < 2 {
    		usage()
    	}
    
    	switch os.Args[1] {
    	case "explorer":
    		fmt.Println("Start Explorer")
    	case "rest":
    		fmt.Println("Start REST API")
    	default:
    		usage()
    	}
    }
  • 실행 결과
    • go run main.go 입력 시

      > go run main.go     
      Welcome to FDong Coin
      
      Please use the following commands:
      
      explorer:               Start the HTML Explorer
      rest:                   Start the REST API (recommende)
    • go run main.go explorer 입력 시

      > go run main.go explorer
      Start Explorer
    • go run main.go rest 입력 시

      > go run main.go rest    
      Start REST API

FlagSet 사용

  • Flag를 여러개 사용할 때 쓰기 좋다
  • 소스 코드
    package main
    
    import (
    	"flag"
    	"fmt"
    	"os"
    )
    
    func usage() {
    	fmt.Printf("Welcome to FDong Coin\n\n")
    	fmt.Printf("Please use the following commands:\n\n")
    	fmt.Printf("explorer: 		Start the HTML Explorer\n")
    	fmt.Printf("rest:			Start the REST API (recommende)\n")
    	os.Exit(0)
    }
    
    func main() {
    	// FlagSet은 go에게 어떤 command가 어떤 flag를 가질 것인지 알려주는 역할을 한다.
    
    	if len(os.Args) < 2 {
    		usage()
    	}
    
    	rest := flag.NewFlagSet("rest", flag.ExitOnError)
    	portFlag := rest.Int("port", 4000, "Sets the port of the server")
    
    	switch os.Args[1] {
    	case "explorer":
    		fmt.Println("Start Explorer")
    	case "rest":
    		rest.Parse(os.Args[2:])
    	default:
    		usage()
    	}
    	if rest.Parsed() {
    		fmt.Println(*portFlag)
    		fmt.Println("Start Server")
    	}
    
    }

FlagSet 응용

  • 소스 코드
    • main.go
      package main
      
      import "coin/exam23/cli"
      
      func main() {
      	cli.Start()
      }
    • cli/cli.go
      package cli
      
      import (
      	"coin/exam23/explorer"
      	"coin/exam23/rest"
      	"flag"
      	"fmt"
      	"os"
      )
      
      func usage() {
      	fmt.Printf("Welcome to FDong Coin\n\n")
      	fmt.Printf("Please use the following flags:\n\n")
      	fmt.Printf("-port: 		Set the PORT of the server\n")
      	fmt.Printf("-mode:			Choose between 'html' and 'rest'\n")
      	os.Exit(0)
      }
      func Start() {
      	if len(os.Args) == 1 {
      		usage()
      	}
      
      	port := flag.Int("port", 4000, "Set port of the server")
      	mode := flag.String("mode", "rest", "Choose between 'html' and 'rest'")
      	flag.Parse()
      
      	switch *mode {
      	case "rest":
      		// start rest api
      		rest.Start(*port)
      	case "html":
      		// start html explorer
      		explorer.Start(*port)
      	default:
      		usage()
      	}
      }
  • 실행 결과
    • go run main.go -mode=rest -port=2000
      > go run main.go -mode=rest -port=2000
      Listening on http://localhost:2000
    • go run main.go -mode=html -port=8000
      > go run main.go -mode=html -port=8000
      Listening on http://localhost8000
    • 잘못된 flag
      > go run main.go -mode=html -port=asdf
      invalid value "asdf" for flag -port: parse error
      Usage of /var/folders/5s/13x9ywys5wz_w321jgl_f_pw0000gn/T/go-build895749235/b001/exe/main:
        -mode string
              Choose between 'html' and 'rest' (default "rest")
        -port int
              Set port of the server (default 4000)
      exit status 2

bolt.db 사용하기 1

https://github.com/boltdb/bolt

bolt db는 Key/Value 형태의 저장소이다.

  • bolt.db 설치하기
    go get github.com/boltdb/bolt
  • 소스 코드
    • db/db.go
      package db
      
      import (
      	"coin/exam24/utils"
      
      	"github.com/boltdb/bolt"
      )
      
      const (
      	dbname       = "blockchain.db"
      	dataBucket   = "data"
      	blocksBucket = "blocks"
      )
      
      var db *bolt.DB
      
      // DB initialize, Singleton pattern형식
      func DB() *bolt.DB {
      	if db == nil {
      		// init db
      		// path는 DB의 이름, 파일이 없으면 자동으로 생성해준다,
      		dbPointer, err := bolt.Open(dbname, 0600, nil)
      		utils.HandleErr(err)
      		db = dbPointer
      		// bucket이 존재하지 않으면 생성시켜주는 Transaction, 두개의 bucket을 만들어준다.
      		// bucket는 table 같은거다
      		err = db.Update(func(tx *bolt.Tx) error {
      			_, err := tx.CreateBucketIfNotExists([]byte(dataBucket))
      			utils.HandleErr(err)
      			_, err = tx.CreateBucketIfNotExists([]byte(blocksBucket))
      
      			return err
      		})
      		utils.HandleErr(err)
      	}
      	return db
      }

bolt.db 사용하기 2

  • block 데이터 저장하기
  • 소스 코드
    • main.go
      package main
      
      import (
      	"coin/exam26/blockchain"
      )
      
      func main() {
      	blockchain.Blockchain()
      }
    • db/db.go
      package db
      
      import (
      	"coin/exam26/utils"
      	"fmt"
      
      	"github.com/boltdb/bolt"
      )
      
      const (
      	dbname       = "blockchain.db"
      	dataBucket   = "data"
      	blocksBucket = "blocks"
      )
      
      var db *bolt.DB
      
      // DB initialize, Singleton pattern형식
      func DB() *bolt.DB {
      	if db == nil {
      		// init db
      		// path는 DB의 이름, 파일이 없으면 자동으로 생성해준다,
      		dbPointer, err := bolt.Open(dbname, 0600, nil)
      		utils.HandleErr(err)
      		db = dbPointer
      		// bucket이 존재하지 않으면 생성시켜주는 Transaction, 두개의 bucket을 만들어준다.
      		// bucket는 table 같은거다
      		err = db.Update(func(tx *bolt.Tx) error {
      			_, err := tx.CreateBucketIfNotExists([]byte(dataBucket))
      			utils.HandleErr(err)
      			_, err = tx.CreateBucketIfNotExists([]byte(blocksBucket))
      
      			return err
      		})
      		utils.HandleErr(err)
      	}
      	return db
      }
      
      func SaveBlock(hash string, data []byte) {
      	fmt.Printf("Saving Block %s\nData: %b", hash, data)
      	err := DB().Update(func(tx *bolt.Tx) error {
      		bucket := tx.Bucket([]byte(blocksBucket))
      		err := bucket.Put([]byte(hash), data)
      		return err
      	})
      	utils.HandleErr(err)
      }
      func SaveBlockchain(data []byte) {
      	err := DB().Update(func(tx *bolt.Tx) error {
      		bucket := tx.Bucket([]byte(dataBucket))
      		err := bucket.Put([]byte("checkpoint"), data)
      		return err
      	})
      	utils.HandleErr(err)
      }
    • blockchain/block.go
      package blockchain
      
      import (
      	"bytes"
      	"coin/exam26/db"
      	"coin/exam26/utils"
      	"crypto/sha256"
      	"encoding/gob"
      	"fmt"
      )
      
      type Block struct {
      	Data     string `json:"data"`
      	Hash     string `json:"hash"`
      	PrevHash string `json:"prevhash,omitempty"`
      	Height   int    `json:"height"`
      }
      
      func (b *Block) toBytes() []byte {
      	var blockBuffer bytes.Buffer
      	encoder := gob.NewEncoder(&blockBuffer)
      	utils.HandleErr(encoder.Encode(b))
      	return blockBuffer.Bytes()
      }
      
      func (b *Block) persist() {
      	db.SaveBlock(b.Hash, b.toBytes())
      }
      func createBlock(data string, prevHash string, height int) *Block {
      	block := &Block{
      		Data:     data,
      		Hash:     "",
      		PrevHash: prevHash,
      		Height:   height,
      	}
      	payload := block.Data + block.PrevHash + fmt.Sprint(block.Height)
      	block.Hash = fmt.Sprintf("%x", sha256.Sum256([]byte(payload)))
      	block.persist()
      	return block
      }
    • blockchain/chain.go
      package blockchain
      
      import (
      	"sync"
      )
      
      type blockchain struct {
      	NewestHash string `json:"newestHash"`
      	Height     int    `json:"height"`
      }
      
      var b *blockchain
      
      var once sync.Once
      
      // AddBlock receiver
      func (b *blockchain) AddBlock(data string) {
      	block := createBlock(data, b.NewestHash, b.Height)
      	b.NewestHash = block.Hash
      	b.Height = block.Height
      }
      
      func Blockchain() *blockchain {
      	if b == nil {
      		once.Do(func() {
      			b = &blockchain{"", 0}
      			b.AddBlock("Genesis Block")
      		})
      	}
      	return b
      }
  • 실행 결과
    > go run main.go
    Saving Block 8500b59bb5271135cd9bcbf0afd693028d76df3b9c7da58d412b13fc8a8f9394
    Data: [111101 11111111 10000001 11 1 1 101 1000010 1101100 1101111 1100011 1101011 1 11111111 10000010 0 1 100 1 100 1000100 1100001 1110100 1100001 1 1100 0 1 100 1001000 1100001 1110011 1101000 1 1100 0 1 1000 1010000 1110010 1100101 1110110 1001000 1100001 1110011 1101000 1 1100 0 1 110 1001000 1100101 1101001 1100111 1101000 1110100 1 100 0 0 0 1010100 11111111 10000010 1 1101 1000111 1100101 1101110 1100101 1110011 1101001 1110011 100000 1000010 1101100 1101111 1100011 1101011 1 1000000 111000 110101 110000 110000 1100010 110101 111001 1100010 1100010 110101 110010 110111 110001 110001 110011 110101 1100011 1100100 111001 1100010 1100011 1100010 1100110 110000 1100001 1100110 1100100 110110 111001 110011 110000 110010 111000 1100100 110111 110110 1100100 1100110 110011 1100010 111001 1100011 110111 1100100 1100001 110101 111000 1100100 110100 110001 110010 1100010 110001 110011 1100110 1100011 111000 1100001 111000 1100110 111001 110011 111001 110100 0]

bolt.db 사용하기 3

  • block 데이터 저장하기
  • chain 데이터 저장하기
  • []byte로 만들어주는 유틸 함수만들기
  • 소스 코드
    • blockchain/chain.go
      package blockchain
      
      import (
      	"coin/exam27/db"
      	"coin/exam27/utils"
      	"sync"
      )
      
      type blockchain struct {
      	NewestHash string `json:"newestHash"`
      	Height     int    `json:"height"`
      }
      
      var b *blockchain
      
      var once sync.Once
      
      func (b *blockchain) persist() {
      	db.SaveBlockchain(utils.ToBytes(b))
      }
      
      // AddBlock receiver
      func (b *blockchain) AddBlock(data string) {
      	block := createBlock(data, b.NewestHash, b.Height+1)
      	b.NewestHash = block.Hash
      	b.Height = block.Height
      	b.persist()
      }
      
      func Blockchain() *blockchain {
      	if b == nil {
      		once.Do(func() {
      			b = &blockchain{"", 0}
      			b.AddBlock("Genesis Block")
      		})
      	}
      	return b
      }
    • blockchain/block.go
      package blockchain
      
      import (
      	"coin/exam27/db"
      	"coin/exam27/utils"
      	"crypto/sha256"
      	"fmt"
      )
      
      type Block struct {
      	Data     string `json:"data"`
      	Hash     string `json:"hash"`
      	PrevHash string `json:"prevhash,omitempty"`
      	Height   int    `json:"height"`
      }
      
      func (b *Block) persist() {
      	db.SaveBlock(b.Hash, utils.ToBytes(b))
      }
      func createBlock(data string, prevHash string, height int) *Block {
      	block := &Block{
      		Data:     data,
      		Hash:     "",
      		PrevHash: prevHash,
      		Height:   height,
      	}
      	payload := block.Data + block.PrevHash + fmt.Sprint(block.Height)
      	block.Hash = fmt.Sprintf("%x", sha256.Sum256([]byte(payload)))
      	block.persist()
      	return block
      }
    • uitls/utils.go
      package utils
      
      import (
      	"bytes"
      	"encoding/gob"
      	"log"
      )
      
      func HandleErr(err error) {
      	if err != nil {
      		log.Panic(err)
      	}
      }
      
      // 원하는 건 모든지 받을 수 있다.
      func ToBytes(i interface{}) []byte {
      	var aBuffer bytes.Buffer
      	encoder := gob.NewEncoder(&aBuffer)
      	HandleErr(encoder.Encode(i))
      	return aBuffer.Bytes()
      }
profile
좋은 개발자가 되고싶은

0개의 댓글