일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 배팅
- 웹툰
- git add
- solidity
- 일시정지
- 옵저버패턴
- 인센티브 기반 커뮤니티
- 토큰노믹스
- 코드스테이츠
- 트러블슈팅
- 디자인 패턴
- github
- RBAC
- 회로차단
- 재진입
- 블록체인
- 업그레이더블 컨트랙트
- 멀티시그
- 투표
- 솔리디티
- 코드리뷰
- 스테이트머신패턴
- github 에러
- 팩토리패턴
- 싱글톤패턴
- 팀 프로젝트
- 프록시패턴
- git commit
- 부트캠프
- Today
- Total
보다 더 생생한 기록
[GO] [코드리뷰] 블록체인 구축 코드 리뷰 (트랜잭션) - (3) 본문
Overview
- 강의 진도 : 10강 Transaction (55%)
Added Contents
현 시점에서 블록을 채굴(Mining)할 경우 50코인을 지급해준다.
"172a...ab43"라는 지갑주소를 가진 유저가 최초 제네시스 블럭을 마이닝해서 50코인을 얻었고, 이 중 30코인을 kkk에게 전달하려하면
트랜잭션은
input | ["172a...ab43", 50] |
output | ["172a...ab43", 20], [kkk, 30] |
와 같이 생성이 되야한다. 위 트랜잭션에서 UTXO와 연관지어 설명해야하지만, 조금 뒤에 설명하려한다.
이 내용은 바로 블록에 추가되는 것이 아닌, Mempool이라는 메모리 풀에 저장된다. 멤풀(Mempool)은 대기중인 트랜잭션을 보관하는 곳인데, 누군가 또는 어떤 노드가 채굴을 할 경우, 해당 트랜잭션은 멤풀에서 나와 블록에 추가되고, 블록은 블록체인에 추가된다.
블록체인에 추가되기 전까지, 실제 보유한 코인양은 달라진게 아니지만, UTXO를 계산하여 보유한 코인이 0개가 될때까지 전송 트랜잭션을 멤풀에 추가시킬 수 있다.
이렇게 멤풀에 추가된 트랜잭션은 여러개더라도, 채굴되면 하나의 블록에 묶여 블록체인에 추가되는 것이다. 실제 유명한 체인들은 하나의 블록에 추가될 수 있는 트랜잭션의 수가 제한되어 있지만, 현재 필자가 개발중인 체인에는 블록내 트랜잭션 수 제한이 없다.
UTXO (Unspent Transaction Outputs)
자 그러면 "172a...ab43"라는 지갑이 가진 코인의 개수는,
DB처럼 {"172a...ab43": 50개} 이런식으로 {key: value}값으로 저장되어 있을까?
이더리움(Ethereum) 같이 계정기반으로 구성되어 있는 체인들은 {key: value}처럼 간단하게 state가 저장되어 있지만,
개발중인 블록체인은 비트코인을 어느정도 따라가기 때문에 UTXO 개념을 포함하고 있다. 즉 질문처럼 간단하지 않다.
자 그러면 UTXO란 뭘까?
글자 그대로 표현하면, 미사용 트랜잭션 출력값 또는 미지출 거래 출력이라고 한다. 즉 아직 소비하지 트랜잭션들을 찾아서 잔액을 계산하는 방법이다. 이는 비트코인과 같은 블록체인 네트워크에서 사용되며, 각각의 UTXO는 특정 주소에 속하는 특정 금액의 코인을 나타낸다.
자세히 설명하면, 비트코인 네트워크에서 거래는 입력과 출력으로 구성되어 있다. 거래 출력은 특정 주소로 보내는 코인의 양을 나타내며, 이 출력들 중 일부는 나중에 다른 거래의 입력으로 사용될 수 있다. 사용 될 경우 해당 출력은 "소비된" 것으로 간주된다. 그러나 아직 소비되지 않은 출력, 즉 UTXO는 여전히 소비할 수 있는 코인을 나타낸다.
위 내용을 바탕으로, 유저가 본인 지갑에 있는 코인의 총 잔액을 확인하려 한다면, 본인 지갑에 해당되는 UTXO를 전부 찾아 합을 구하면 된다. 사용자는 하나 이상의 UTXO를 입력으로 사용하여 새로운 UTXO를 생성한다.
예를 들어보자
만약 내가 kkk에게 0.6BTC를 보내고 싶은데, 내 지갑과 대응되는 UTXO는 0.5BTC와 0.2BTC 이렇게 두개가 있다.
그렇다면 나는 이 두개의 UTXO를 합쳐 [0.5BTC, 0.2BTC]로 만들고 이를 kkk에게 보내는 트랜잭션의 Input으로 넣으면 된다.
input | [["172a...ab43", 50],["172a...ab43", 20]] |
output | ["172a...ab43", 10], [kkk, 60] |
결국 위의 표처럼 되는 것이다.
자 이제 코드를 살펴보자. 우선 체인에 흩어져 있는 UTXO부터 찾을 것이다.
for _, block := range Blocks(b) {
for _, tx := range block.Transaction {
for _, input := range tx.TxIns {
}
for index, output := range tx.TxOuts {
}
}
}
3중 for문이라 불편하긴 하지만, 가장 직관적인 방법인 듯 하다
우선 첫 코드라인에서는 전체 블럭에서 블럭들을 순회한다. 그 뒤 그 블럭에서 트랜잭션을 꺼낸뒤, 그 트랜잭션의 Input을 추출한다.
여기서 왜 트랜잭션의 입력값(Input)을 꺼낼까?
트랜잭션의 입력값은 곧 직전 UTXO의 출력값(Output)이었기 때문이다. 즉 그 Input으로부터 직전 Output을 확인할 수 있고, 그 Output에 해당되는 지갑주소가 우리가 찾는 주소와 일치하는지 확인할 수 있다.
if input.Signature == "COINBASE" {
break
}
if FindTx(b, input.TxID).TxOuts[input.Index].Address == address {
creatorTxs[input.TxID] = true // 어떤 트랜잭션이 output을 input으로 사용했는지 bool로 마킹
}
첫번째 if 문에서는 input의 서명이 "COINBASE"인지 확인하는데, 이는 채굴할때 체인이 채굴자에게 채굴보상을 준 트랜잭션의 서명값이다. 그래서 break로 패스해주는 것. (서명 관련은 다음 포스팅에 추가될 예정이다)
두번째 if문에서 트랜잭션의 input을 확인하고 output으로 파고들어가 지갑주소인 address가, 우리가 찾는 address인지 확인한다. 맞다면 그 input의 id값을 true로 대응시켜 마킹하고 creatorTxs에 저장한다.
creatorTxs := make(map[string]bool) // 사용한 트랜잭션 output -> map 형태
자 이제 마킹한 것들을 기준으로, output을 볼 차례다
for index, output := range tx.TxOuts {
if output.Address == address {
if _, ok := creatorTxs[tx.ID]; !ok {
uTxOut := &UTxOut{tx.ID, index, output.Amount}
if !isOnMempool(uTxOut) {
uTxOuts = append(uTxOuts, uTxOut)
}
}
}
}
TxInputs 반복문과 마찬가지로 순회를 하는데, 이번에는 index도 같이 추출한다.
for index, output := range tx.TxOuts {
순회를 하던 중, 타겟 지갑주소와 같을경우, 위 input for문 내에서 마킹해주던 creatorTxs에서 해당 트랜잭션이 input으로 사용되었는지를 ok를 추출함으로써 확인한다.
if output.Address == address {
if _, ok := creatorTxs[tx.ID]; !ok {
만약 ok가 false라면 input으로 사용되지 않은 output이라는 것이고, 이는 즉 UTXO에 해당되므로, 내용들을 UTxOut struct에 담아 넣어 uTxOut 변수에 초기화시켜준다.
uTxOut := &UTxOut{tx.ID, index, output.Amount}
...
type UTxOut struct { // UTxOut struct의 구조
TxID string
Index int
Amount int
}
이제 만들어진 uTxOut를 기준으로 멤풀에 있는지 확인한다. 멤풀에 있더라도 누군가에게 코인을 보내려고 멤풀에 넣었다면, 합계에 포함되어서는 안되기 때문이다. 멤풀에도 없을경우 진정한 UTXO이기 때문에 uTxOuts에 append 시키고 이를 반환해준다.
if !isOnMempool(uTxOut) {
uTxOuts = append(uTxOuts, uTxOut)
}
...
return uTxOuts
나중에 지갑에 대응하는 UTXO를 전부 합산해 지갑의 잔액이 얼마인지 확인시켜 줄 수 있다.
func BalanceByAddress(address string, b *blockchain) int {
txOuts := UTxOutsByAddress(address, b)
var amount int
for _, txOut := range txOuts {
amount += txOut.Amount
}
return amount
}
마지막 반환되는 amount가 곧 지갑의 코인 잔액이며, 이는 지갑주소에 대응하는 모든 UTXO로 부터의 합산액이다.
'Go' 카테고리의 다른 글
[GO] [코드리뷰] 블록체인 구축 코드 리뷰 (지갑과 서명) - (4)(下) (0) | 2023.09.26 |
---|---|
[GO] [코드리뷰] 블록체인 구축 코드 리뷰 (지갑과 서명) - (4)(上) (0) | 2023.09.25 |
[GO] 포인터 사용 공식 (feat. ecdsa.Verify 함수) (0) | 2023.09.22 |
[GO] 중첩반복문 탈출 방법 : label (0) | 2023.09.13 |
[GO] 중첩반복문 탈출 방법 : label (0) | 2023.09.13 |