send에서 블록을 추가할 때 Coinbase 트랜잭션을 추가하여 블록을 추가하는 주소에게 보상 20 코인을 주도록 합니다. cli/cli.go
// {from}에서 {to}로 {amount}만큼 보냅니다.func (cli *CommandLine) send(from, to string, amount int) {if!wallet.ValidateAddress(from) { log.Panic("Address is not Valid") }if!wallet.ValidateAddress(to) { log.Panic("Address is not Valid") } chain := blockchain.ContinueBlockChain("") // blockchain을 DB로 부터 받아온다. UTXOset :=blockchain.UTXOSet{chain}defer chain.Database.Close() cbTx := blockchain.CoinbaseTx(from, "") // 코인베이스 트랜잭션을 생성하고 tx := blockchain.NewTransaction(from, to, amount, &UTXOset) // send 트랜잭션도 생성하여 block := chain.AddBlock([]*blockchain.Transaction{cbTx, tx}) // 새로운 블록에 추가합니다. UTXOset.Update(block) fmt.Println("Success!")}
blockchain/blockchain.go파일을 열어서 VerifyTransaction을 다음과 같이 수정합니다.
// 트랜잭션을 검증합니다.func (chain *BlockChain) VerifyTransaction(tx *Transaction) bool {// Coinbase 트랜잭션은 유효하다고 판단합니다.if tx.IsCoinbase() {returntrue } prevTXs :=make(map[string]Transaction)for _, in :=range tx.Inputs { prevTX, err := chain.FindTransaction(in.ID)Handle(err) prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX }// 이전 거래 기록을 이용해서 검증합니다.return tx.Verify(prevTXs)}
blockchain/merkle.go
Merkle Tree는 해시들의 해시로 생각할 수 있습니다. 리프노드(Leaf node)는 하나의 트랜잭션의 해시를 저장하며, 두개의 리프노드의 해시값을 합하여 새로운 부모노드를 만들어냅니다. 같은 방식으로 루트노드까지 만들고 Merkle Tree는 이 루트노드를 포인팅합니다.
packageblockchainimport ("crypto/sha256")// MerkleTree의 루트를 저장하는 스트럭쳐typeMerkleTreestruct { RootNode *MerkleNode}// MerkleTree의 개별 노드typeMerkleNodestruct { Left *MerkleNode// 왼쪽 자식 Right *MerkleNode// 오른쪽 자식 Data []byte// hash 값}// MerkleNode를 생성하는 함수funcNewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode { node :=MerkleNode{}// {left}, {right}가 없다면 leaf nodeif left ==nil&& right ==nil { hash := sha256.Sum256(data) node.Data = hash[:] } else {// 자식들의 해시를 이어서 다시 Hash를 구함 prevHashes :=append(left.Data, right.Data...) hash := sha256.Sum256(prevHashes) node.Data = hash[:] }// 자식 연결 node.Left = left node.Right = rightreturn&node}// MerkleTree를 생성하는 과정funcNewMerkleTree(data [][]byte) *MerkleTree {var nodes []MerkleNode// 자식 노드의 수를 짝수로 만들어야함// 마지막 자식을 복사한다.iflen(data)%2!=0 { data =append(data, data[len(data)-1]) }// Leaf node를 만드는 과정for _, dat :=range data { node :=NewMerkleNode(nil, nil, dat) nodes =append(nodes, *node) }// Tree height 만큼 순회for i :=len(data); i >1; i /=2 {var level []MerkleNode// 순서대로 2개씩 합쳐서 노드 생성for j :=0; j <len(nodes); j +=2 { node :=NewMerkleNode(&nodes[j], &nodes[j+1], nil) level =append(level, *node) }// 다음 iteration은 새로 만들어진 노드들로 진행 nodes = level }// Root 노드 반환 tree :=MerkleTree{&nodes[0]}return&tree}
blockchain/block.go
HashTransactions 코드를 바꾸어 머클트리를 구성하고 루트의 Hash값을 반환하도록 변경합니다.
// Block의 Transaction들을 합쳐서 하나의 해시를 만듭니다.// Merkle Tree를 이용합니다.func (b *Block) HashTransactions() []byte {var txHashes [][]bytefor _, tx :=range b.Transactions { txHashes =append(txHashes, tx.Serialize()) } tree :=NewMerkleTree(txHashes)// merkle tree를 구성하고 루트노드의 데이터 값이 최종 해시값return tree.RootNode.Data}
Test
Send 시에 블록을 추가하기 때문에 코인베이스 트랜잭션이 발생하여 20코인이 더 주어진다.
printchain 으로 자세한 내용을 보면,
2번째 블록 (프린트 상에서는 위에서 첫번째)에 2개의 transaction이 적혀있고, 그 중 첫번째 트랜잭션은 Coinbase 트랜잭션임을 알 수 있다.
미구현 사항
풀 노드와 라이트 노드 개념이 없이, 일단 하나의 노드가 모든 블록체인 정보를 다 저장하기 때문에 머클트리의 장점은 활용하지 못하는 상황입니다.