네트워크 : difficulty의 값만큼 0의 개수를 증가시켜 난이도를 어렵게 만든다
채굴자 : nonce 값을 변경해가며 0의 개수가 맞는 것을 찾아낸다.
package main
import (
"crypto/sha256"
"fmt"
"strings"
)
func main() {
difficulty := 2
// target := "0" * 2
// 첫번째 인자값을 두번째 인자값 만큼 연결해서 출력해준다.
target := strings.Repeat("0", difficulty)
nonce := 1
for {
// 16진수 string으로 변환
hash := fmt.Sprintf("%x", sha256.Sum256([]byte("hello"+fmt.Sprint(nonce))))
fmt.Printf("Hash:%s\nTarget:%s\nNonce:%d\n\n", hash, target, nonce)
if strings.HasPrefix(hash, target) {
return
} else {
nonce++
}
}
}
Hash:001b92541ed0a22b0cb89018b561d895503206c0082c0ecf2d0b7e5182191eed
Target:00
Nonce:227
Hash:0006bc9ad4253c42e32b546dc17e5ea3fedaecdabef371b09906cea9387e8695
Target:000
Nonce:10284
Hash:0000e49eab06aa7a6b3aef7708991b91a7e01451fd67f520b832b89b18f4e7de
Target:0000
Nonce:60067
func (b *Block) mine() {
target := strings.Repeat("0", b.Difficulty)
for {
blockAsString := fmt.Sprint(b)
// 블록을 string으로 바꾼 후 해쉬로 변환시킨다음 16진수 string으로 다시 변환한다.
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(blockAsString)))
fmt.Printf("Block as String:%s\nHash:%s\nTarget:%s\nNonce:%d\n\n\n", blockAsString, hash, target, b.Nonce)
if strings.HasPrefix(hash, target) {
b.Hash = hash
break
} else {
b.Nonce++
}
}
}
Method : GET
URL : http://localhost:4000/blocks
HTTP/1.1 200 OK
Date: Sat, 31 Dec 2022 02:47:41 GMT
Content-Length: 138
Content-Type: text/plain; charset=utf-8
Connection: close
[
{
"data": "Genesis Block",
"hash": "0056f988ce765e06b2fccc508947a1771b1822f889c3594bc174aa6032fc688c",
"height": 1,
"defficulty": 2,
"nonce": 76
}
]
const (
defaultDifficulty int = 2
difficultyInterval int = 5
blockInterval int = 2
allowedRange int = 2
)
type blockchain struct {
// 최근에 등록된 Hash
NewestHash string `json:"newestHash"`
// 블록의 수
Height int `json:"height"`
// 현재 난이도
CurrentDifficulty int `json:"currentdifficulty"`
}
func (b *blockchain) AddBlock(data string) {
block := createBlock(data, b.NewestHash, b.Height+1)
b.NewestHash = block.Hash
b.Height = block.Height
b.CurrentDifficulty = block.Difficulty
b.persist()
}
func (b *blockchain) recalculateDifficulty() int {
allBlocks := b.Blocks()
// 블록 슬라이스에 가장 최근 블록이 앞에 들어간다.
newestBlock := allBlocks[0]
// 가장 최근에 난이도가 재설정된 블록은 allBlock[5-1]이다.
lastRecalculatedBlock := allBlocks[difficultyInterval-1]
// 두 블록 사이에 걸린시간 : 최근 생성된 블록의 시간 - 블록의 난이도가 재설정된 후 생성된 블록의 시간
// 타임스탬프가 Unix Time이기에 초단위로 변경해줘야한다.
actualTime := (newestBlock.Timestamp / 60) - (lastRecalculatedBlock.Timestamp / 60)
// 예상 시간 : 5 * 2 = 10분 기준으로 난이도를 설정한다.
expectedTime := difficultyInterval * blockInterval
// 10분을 기준으로 앞뒤로 2분씩 범위안에만 들어오면 난이도를 유지한다.
// 예상 시간 보다 빠르게 블록이 생성되면 난이도를 1만큼 증가시킨다.
if actualTime <= (expectedTime - allowedRange) {
return b.CurrentDifficulty + 1
} else if actualTime >= (expectedTime + allowedRange) {
// 예상 시간 보다 느리게 블록이 생성되면 난이도를 1만큼 감소시킨다.
return b.CurrentDifficulty - 1
}
return b.CurrentDifficulty
}
func (b *blockchain) difficulty() int {
// 제네시스 블록체인은 Difficulty가 2다.
if b.Height == 0 {
return defaultDifficulty
} else if b.Height%difficultyInterval == 0 {
// 비트코인은 2016개, 우리의 블록체인은 5개 블록마다 체크하여 난이도를 조정한다
return b.recalculateDifficulty()
} else {
// 난이도가 변경된 후 블록이 5개가 추가되지 않았으면 현재 난이도를 그대로 유지한다.
return b.CurrentDifficulty
}
type Block struct {
Data string `json:"data"`
Hash string `json:"hash"`
PrevHash string `json:"prevhash,omitempty"`
Height int `json:"height"`
Difficulty int `json:"defficulty"`
Nonce int `json:"nonce"`
Timestamp int `json:"timestamp"`
}
func (b *Block) mine() {
target := strings.Repeat("0", b.Difficulty)
for {
// time.Now().Unix() : int64를 반환한다. 1970년 1월 1일 UTC로부터 흐른 시간을 초단위로
b.Timestamp = int(time.Now().Unix())
hash := utils.Hash(b)
if strings.HasPrefix(hash, target) {
b.Hash = hash
break
} else {
b.Nonce++
}
}
func createBlock(data string, prevHash string, height int) *Block {
block := &Block{
Data: data,
Hash: "",
PrevHash: prevHash,
Height: height,
Difficulty: Blockchain().difficulty(),
Nonce: 0,
}
block.mine()
block.persist()
return block
}
Method : GET
기능 : 현재 생성된 블록 모두 가져오기
결과
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 31 Dec 2022 04:28:23 GMT
Content-Length: 162
Connection: close
[
{
"data": "Genesis Block",
"hash": "007b0d340726556946f5121d17bb7bb2f6b892e037a6e4cd783336aa0ff497fe",
"height": 1,
"defficulty": 2,
"nonce": 234,
"timestamp": 1672460696
}
]
Method : GET
기능 : 현재 체인의 상태 가져오기
결과
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 31 Dec 2022 04:27:48 GMT
Content-Length: 115
Connection: close
{
"newestHash": "007b0d340726556946f5121d17bb7bb2f6b892e037a6e4cd783336aa0ff497fe",
"height": 1,
"currentdifficulty": 2
}
Method : POST
기능 : JSON형태의 값을 받아 블록 하나 생성하기
{
"message" : "Blockchain Test"
}
결과
```go
HTTP/1.1 201 Created
Content-Type: application/json
Date: Sat, 31 Dec 2022 04:29:35 GMT
Content-Length: 0
Connection: close
```
총 5개의 블록을 생성했을 때 8분 미만으로 5개의 블록이 생성되면 난이도가 1 올라간다.
초기 상태
{
"newestHash": "00005e8d76daa1d457e5cde329901a1c1bd352cb00e4d626f3571c6431d2dd36",
"height": 1,
"currentdifficulty": 2
}
블록이 6번째에 난이도가 변한것을 알 수 있다.
{
"newestHash": "000e4683d1ca7256e62118d1cffde70b5957f9813d32e6f7f0b4873f5df1d81c",
"height": 6,
"currentdifficulty": 3
}
난이도가 재설정된 블록 부터 시작해서 8분 미만으로 5개의 블록이 추가로 생성되었기에 난이도가 또 올라간 것을 확인할 수 있다.
{
"newestHash": "0000be41b16c285e84f5b5deec30317127f1612e4c66e8d5016e3ce484a100ea",
"height": 11,
"currentdifficulty": 4
}