go get github.com/br0xen/boltbrowser
boltbrowser <filename>
GitHub - evnix/boltdbweb: A web based GUI for BoltDB files
go get github.com/evnix/boltdbweb
boltdbweb --db-name=<DBfilename>
func main() {
blockchain.Blockchain()
}
func Blockchain() *blockchain {
if b == nil {
once.Do(func() {
// NewestHash가 없고 Height는 0인 블록체인을 만들고
b = &blockchain{"", 0}
// checkpoint에 data가 있는지 확인한다.
fmt.Printf("NewestHash: %s\nHeight: %d\n", b.NewestHash, b.Height)
checkpoint := db.Checkpoint()
if checkpoint == nil {
//없으면 initialize한다.
b.AddBlock("Genesis Block")
} else {
fmt.Printf("Restoring...\n")
b.restore(checkpoint)
}
})
}
fmt.Printf("NewestHash: %s\nHeight: %d\n", b.NewestHash, b.Height)
return b
}
// checkpoint가 있는지 없는지 리턴하는 함수
func Checkpoint() []byte {
var data []byte
// View : Read Only
DB().View(func(tx *bolt.Tx) error {
// bucket을 가져온다
bucket := tx.Bucket([]byte(dataBucket))
data = bucket.Get([]byte(checkpoint))
return nil
})
return data
}
> go run main.go
NewestHash:
Height: 0
Restoring...
NewestHash: 110e8eb40de97b0929e53a3b0cd2d697a4bd25a38fe1a5f069d6d35a40976e17
Height: 4
func main() {
blockchain.Blockchain()
cli.Start()
}
// ...
func documentation(rw http.ResponseWriter, r *http.Request) {
data := []urlDescription{
{
URL: url("/"),
Method: "GET",
Description: "See Documentation",
},
{
URL: url("/blocks"),
Method: "GET",
Description: "See All Block",
},
{
URL: url("/blocks"),
Method: "POST",
Description: "Add A Block",
Payload: "data:string",
},
{
URL: url("/blocks/{hesh}"),
Method: "GET",
Description: "See A Block",
},
}
rw.Header().Add("Content-Type", "application/json")
json.NewEncoder(rw).Encode(data)
}
func block(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
hash := (vars["hash"])
block, err := blockchain.FindBlock(hash)
encoder := json.NewEncoder(rw)
if err == blockchain.ErrNotFound {
encoder.Encode(errorResponse{fmt.Sprint(err)})
} else {
encoder.Encode(block)
}
}
func Start(aPort int) {
router := mux.NewRouter()
port = fmt.Sprintf(":%d", aPort)
// ...
// hex 값을 id로 받아오기 위해 a-f
router.HandleFunc("/blocks/{hash:[a-f0-9]+}", block).Methods("GET")
// ...
log.Fatal(http.ListenAndServe(port, router))
}
HTTP/1.1 200 OK
Date: Fri, 30 Dec 2022 05:10:50 GMT
Content-Length: 180
Content-Type: text/plain; charset=utf-8
Connection: close
{
"data": "Third",
"hash": "110e8eb40de97b0929e53a3b0cd2d697a4bd25a38fe1a5f069d6d35a40976e17",
"prevhash": "64bf97f63405065b829e45a1f99321bf3f55a02fb352779759c95adc1a83e8f4",
"height": 4
}
func Goexit()
// ...
func blocks(rw http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
json.NewEncoder(rw).Encode(blockchain.Blockchain().Blocks())
case "POST":
var addBlockBody addBlockBody
utils.HandleErr(json.NewDecoder(r.Body).Decode(&addBlockBody))
blockchain.Blockchain().AddBlock(addBlockBody.Message)
rw.WriteHeader(http.StatusCreated)
}
}
// ...
// 등록된 블록을 가져오는 함수
func (b *blockchain) Blocks() []*Block {
// 찾은 블록들을 저장할 Block 포인터를 저장할 슬라이스를 만든다.
var blocks []*Block
// 최근에 생성된 블록의 해시값을 가져온다.
hashCursor := b.NewestHash
for {
// 최근 블록 부터 가져온다
// 무조건 찾을 수 있기 때문에 error 처리를 따로 하지 않는다.
block, _ := FindBlock(hashCursor)
// 찾은 블록을 []*block에 넣는다.
blocks = append(blocks, block)
// 가져온 블록의 이전 해시값이 빈값이 아니면 이전 블록이 있는 것이므로
// 이전 블록의 해시 값을 가져와 가르킨다.
if block.PrevHash != "" {
hashCursor = block.PrevHash
} else {
// PrevHash가 없는 Genesis Block면
break
}
}
return blocks
}
HTTP/1.1 200 OK
Date: Fri, 30 Dec 2022 07:12:47 GMT
Content-Length: 845
Content-Type: text/plain; charset=utf-8
Connection: close
[
{
"data": "Data for my block",
"hash": "c57fca35b986fc973b8f1b535ae9970c081606d5bffde0c3def73cba9cf50bf9",
"prevhash": "110e8eb40de97b0929e53a3b0cd2d697a4bd25a38fe1a5f069d6d35a40976e17",
"height": 5
},
{
"data": "Third",
"hash": "110e8eb40de97b0929e53a3b0cd2d697a4bd25a38fe1a5f069d6d35a40976e17",
"prevhash": "64bf97f63405065b829e45a1f99321bf3f55a02fb352779759c95adc1a83e8f4",
"height": 4
},
{
"data": "Second",
"hash": "64bf97f63405065b829e45a1f99321bf3f55a02fb352779759c95adc1a83e8f4",
"prevhash": "3fd9ee8fc98866d3fb2946af3aa9d32048d60b71467f1d6c89f07b4ba77d21cc",
"height": 3
},
{
"data": "First",
"hash": "3fd9ee8fc98866d3fb2946af3aa9d32048d60b71467f1d6c89f07b4ba77d21cc",
"prevhash": "81f2ced897805e5539e750784e8d12bff104712be9bf8845ce52e006b0f3252e",
"height": 2
},
{
"data": "Genesis Block",
"hash": "81f2ced897805e5539e750784e8d12bff104712be9bf8845ce52e006b0f3252e",
"height": 1
}
]
{
"message" : "Blockchain Test"
}
HTTP/1.1 201 Created
Date: Fri, 30 Dec 2022 07:14:17 GMT
Content-Length: 0
Connection: close
HTTP/1.1 200 OK
Date: Fri, 30 Dec 2022 07:14:23 GMT
Content-Length: 1035
Content-Type: text/plain; charset=utf-8
Connection: close
[
{
"data": "Blockchain Test",
"hash": "b2b2a8fb45aa0631ee0c7f00d48fe508ac29155b9aac4f21628d320d0e3ce39f",
"prevhash": "c57fca35b986fc973b8f1b535ae9970c081606d5bffde0c3def73cba9cf50bf9",
"height": 6
},
{
"data": "Data for my block",
"hash": "c57fca35b986fc973b8f1b535ae9970c081606d5bffde0c3def73cba9cf50bf9",
"prevhash": "110e8eb40de97b0929e53a3b0cd2d697a4bd25a38fe1a5f069d6d35a40976e17",
"height": 5
},
{
"data": "Third",
"hash": "110e8eb40de97b0929e53a3b0cd2d697a4bd25a38fe1a5f069d6d35a40976e17",
"prevhash": "64bf97f63405065b829e45a1f99321bf3f55a02fb352779759c95adc1a83e8f4",
"height": 4
},
{
"data": "Second",
"hash": "64bf97f63405065b829e45a1f99321bf3f55a02fb352779759c95adc1a83e8f4",
"prevhash": "3fd9ee8fc98866d3fb2946af3aa9d32048d60b71467f1d6c89f07b4ba77d21cc",
"height": 3
},
{
"data": "First",
"hash": "3fd9ee8fc98866d3fb2946af3aa9d32048d60b71467f1d6c89f07b4ba77d21cc",
"prevhash": "81f2ced897805e5539e750784e8d12bff104712be9bf8845ce52e006b0f3252e",
"height": 2
},
{
"data": "Genesis Block",
"hash": "81f2ced897805e5539e750784e8d12bff104712be9bf8845ce52e006b0f3252e",
"height": 1
}
]
HTTP/1.1 200 OK
Date: Fri, 30 Dec 2022 07:15:29 GMT
Content-Length: 190
Content-Type: text/plain; charset=utf-8
Connection: close
{
"data": "Blockchain Test",
"hash": "b2b2a8fb45aa0631ee0c7f00d48fe508ac29155b9aac4f21628d320d0e3ce39f",
"prevhash": "c57fca35b986fc973b8f1b535ae9970c081606d5bffde0c3def73cba9cf50bf9",
"height": 6
}