일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 라우트 매개변수
- next 매개변수
- node.js path
- express실행
- cookie-parser 만들어보기
- css기초
- OAuth 카카오
- express.static
- ws 라이브러리
- 시퀄라이즈 기본설정
- 블록 만들기
- Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
- 라우터미들웨어 분리
- nodejs파일업로드
- 비동기파일업로드
- 세션으로 로그인 구현
- javascript기초
- 아이디 중복체크기능
- JWT 로그인 기능 구현
- JWT 만들어보기
- useContext
- FormData()
- buffer.from
- 라우터 분리
- express router
- mysql wsl
- useEffect clean up
- JWT 하드코딩
- express session
- 라우터와 미들웨어
- Today
- Total
즐코
지갑, 트랜잭션 구현 (4) - txIn, txOut, UTXO 본문
트랜잭션을 코드로 나타내려면 트랜잭션이 만들어지는 과정에 대해서 먼저 이해를 해야한다.
트랜잭션의 과정
말로 표현해도 되지만, 엑셀로 표현하는 게 나중에 보기 편할 것 같아 정리해보았다
왼쪽 표는 트랜잭션 하나하나를 흐름에 따라 정리한 부분이고, 오른쪽은 각 트랜잭션에 따라 생기는 UTXO 목록이다.
UTXO 가 txIn으로 쓰였으면 spent로 표시, 아직 쓰이기 전이면 unspent로 표시하였다.
오른쪽 하단 마지막 표는 트랙잰션 tx0004까지 남아있는 최종 UTXO 이다.
거래자 A,B,C 가 있다고 가정하고, A가 첫 블록 채굴에 성공해서 50BTC 를 받는 것부터 시작한다.
여기서 tx hash는 트랜잭션의 고유한 인덱스값을 가리킨다. 실제로는 해시값으로 찍히지만 여기선 흐름을 익히기 위해 간단하게 tx0000, tx0001 이렇게 구성하였다. 보통 txOuts의 배열에는 첫 트랜잭션 빼고는 2개의 txOut이 들어가게 된다.
반면, txIns의 배열에는 해당 코인을 보내는 사람의 utxo의 구성에 따라 한개가 될 수도 있고 2개보다 더 많은 txIns가 들어갈 수 있다.
자세한 예로, 아래 흐름처럼 마지막에 B가 A에게 35 BTC를 전송하는데, 이땐 B 앞으로 남은 utxo 는 우측 표 상에서 3번째 (30BTC) 와 7번째 (10BTC) 였다. (파란색바탕) 그러면 이걸 2개의 utxo를 전부 다 가져와서 txIns에 넣어서 spent해버리고 B는 5BTC의 txOut을 돌려받고 이게 utxo가 되는 것이다. 우측 utxo 목록에서 8번째에 해당한다. tx0004 트랜잭션을 보면 알 수 있다.
(* typo : 첫번째 utxo의 txOutIdx는 블록의 height가 아닌 0이다..)
트랜잭션과 utxo, 트랜잭션 내부 속성에 쓰이는 타입을 미리 지정해두면 아래와 같다.
// @types/transaction.d.ts
declare interface ITxOut {
account: string;
amount: number;
}
declare interface ITxIn {
txOutId: string;
txOutIndex: number;
signature?: string | undefined;
}
declare interface ITransaction {
hash: string;
txOuts: ITxOut[];
txIns: ITxIn[];
}
declare interface IUtxo {
txOutId: string;
txOutIndex: number;
account: string;
amount: number;
}
Transaction
트랜잭션은 아래와 같이 이루어졌다.
1- txIns : 해당 트랜잭션에 쓰인 txIn들 (배열)
2- txOuts : 해당 트랜잭션으로 생긴 txOut들 (배열)
3- hash : 해당 트랜잭션의 고유 인덱스값 (txIns 배열 내 모든 txIns의 속성값 + txOuts 배열 내 모든 txOut의 속성값 => 해쉬한 값)
여기서 각각의 txIn과 txOut이 뭘까
TxIn 클래스
- txOutId : utxo에서 가져온 txout의 트랜잭션 해쉬/고유값 (상기표에 표시해둠)
- txOutIndex : 트랜잭션 상 txOuts 배열에서 가져온 txOut의 인덱스값 (상기표에 표시해둠)
- signature : 이 가져온 txOut의 지갑 주인의 서명
블록의 첫 거래, 트랜잭션은 채굴자가 채굴에 성공했을 때 받는 보상에 대한 트랜잭션이 들어간다. 이를 코인베이스라고 한다.
위의 예시에서 A가 채굴에 성공해서 받은 50BTC 보상에 대한 거래내역이 코인베이스인것이다.
따지고보면 이 첫 거래는 UTXO 에서 가져온 게 아니라 블록체인 네트워크 자체에서 제공하는 것이므로 txIn 이 없다.
따라서, 이 코인베이스는 txIn을 만들 때 txOutId 는 빈값을 넣고, txOutIndex는 해당 블록의 높이를 넣어주기로 한다. 그리고 서명은 생략한다.
txOutIndex도 0을 넣어주면 되지 않나 싶지만, 이렇게 하면 트랜잭션의 hash값을 만들때, 같은 채굴자가 채굴을 여러 번했다고 가정하면 그 코인베이스의 트랜잭션 해시값이 항상 같게 나오기 때문에 이를 방지하기 위해 해당 거래가 들어가는 블록 높이로 넣어준 것이다.
// src/core/transaction/txin.ts
export class TxIn implements ITxIn {
public txOutId: string;
public txOutIndex: number;
public signature?: string;
// wallet에서 블체서버로 들어올때 16진수로 들어오니까 string으로 처리
constructor(_txOutId: string, _txOutIndex: number, _signature: string | undefined = undefined) {
this.txOutId = _txOutId;
this.txOutIndex = _txOutIndex;
this.signature = _signature;
}
}
TxOut 클래스
- account : 트랜잭션으로 인해 나온 txOut amount의 주인 지갑계정
- amount : 코인의 양
// src/core/transaction/txout.ts
export class TxOut implements ITxOut {
public account: string;
public amount: number;
constructor(_account: string, _amount: number) {
this.account = _account;
this.amount = _amount;
}
}
본격적으로 위의 트랜잭션에 대해 코드를 짜보자..
Transaction 클래스
트랜잭션 고유의 해쉬값은 위에서 설명한대로 txOuts 배열 내 모든 txOut과 txIns 배열 내 모든 txIn의 속성값을 더해서 SHA256으로 해시한 값이다.
createUTXO()
트랜잭션이 발생함과 동시에 txOut들이 만들어지므로 이것들은 동시에 UTXO인 것이다.
따라서 트랜잭션 생성과 동시에 UTXO도 생성해준다.
// src/core/transaction/transaction.ts
import { SHA256 } from "crypto-js";
import { TxIn } from "./txin";
import { TxOut } from "./txout";
import { Utxo } from "./utxo";
export class Transaction {
public hash: string;
public txIns: TxIn[];
public txOuts: TxOut[];
constructor(_txIns: TxIn[], _txOuts: TxOut[]) {
this.txIns = _txIns;
this.txOuts = _txOuts;
this.hash = this.createTransactionHash();
}
createTransactionHash(): string {
const txoutContent: string = this.txOuts.map((v) => Object.values(v).join("")).join("");
const txinContent: string = this.txIns.map((v) => Object.values(v).join("")).join("");
return SHA256(txoutContent + txinContent).toString();
}
createUTXO(): Utxo[] {
return this.txOuts.map((txout: TxOut, k) => {
return new Utxo(this.hash, k, txout.account, txout.amount);
});
}
}
chain 클래스 수정
miningBlock(), appendUTXO(), getUTXO() 추가
원래는 블록 채굴 시 바로 블록을 블록 체인에 add했다. ( const newBlock = ws.addBlock(data))
이젠 채굴 보상을 주는 코드를 추가할 거고, 그게 블록 내 첫 거래가 되게끔 코드를 추가해야한다. 그걸 위한 함수인 miningBlock() 을 chain 클래스 내에서 추가로 만들어서 addBlock 대신 넣어주었다. 그리고 이 '/mineBlock' POST 요청에선 요청 바디를 받을 때 클라이언트 쪽에서 무의미한 데이터가 아닌 account 를 전달할 수 있게끔 해야한다. 왜냐하면 채굴 보상은 해당 블록을 채굴한 채굴자한테 떨어져야하기 때문이다.
// index.ts (블록체인 서버)
app.post("/mineBlock", (req, res) => {
const { data } = req.body;
// data에 이제 transaction의 배열을 넣어야 한다.
// 이때, miningBlock 함수 인자에는 첫 마이닝 보상이 떨어지는 account를 넣어줄것
const newBlock = ws.miningBlock(data); //원래는 const newBlock = ws.addBlock(data)였음
const msg: Message = {
type: MessageType.latest_block,
payload: [],
};
ws.broadcast(msg);
if (newBlock.isError) return res.status(500).json(newBlock.error);
console.log(newBlock);
res.json(newBlock.value);
});
miningBlock()
이전에 썼던 저 addBlock메소드에는 data가 스트링값을 가진 배열 이었지만, 이젠 그 데이터에 transaction[] 을 넣어줘야한다.
여기서 addBlock 함수 자체는 건드릴 필요가 없다. 왜냐하면 이미 data만 넣으면 블록을 생성해서 검증하고 블록을 추가해주는 코드이기 때문에 굳이 잘 돌아가는 함수를 건드릴 필요가 없고, 하나의 함수는 하나의 역할만 하는 게 맞기 때문이다. 그래서 이 트랜잭션 데이터를 가공해서 addBlock()의 인자로 던져줄 수 있는 새로운 함수(miningBlock())만 하나 더 만들어주는 게 효율적이다.
그리고 transaction이 추가됨과 동시에 utxo도 만들어야하므로 트랜잭션 클래스의 createUTXO()를 사용했다.
// src/core/blockchain/chain.ts - chain 클래스
import { Transaction } from "@core/transaction/transaction";
import { TxIn } from "@core/transaction/txin";
import { TxOut } from "@core/transaction/txout";
export class Chain {
private blockchain: Block[];
private utxo: IUtxo[];
constructor(){
this.blockchain = [Block.createGENESIS(GENESIS)];
this.utxo = [];
}
// ... 클래스 내 다른 메소드들 생략
public getUTXO: IUTXO[] {
return this.utxo;
}
public miningBlock(_account: string): Failable<Block, string>{
// 우선은 채굴보상을 넣어주는 함수로 만들것
// 1. todo : transaction의 데이터 가공해주기
const txin: ITxIn = new TxIn("", this.getLatestBlock().height+1);
const txout: ITxOut = new TxOut(_account, 50); // 첫 채굴에 대한 보상
const transactio: Transaction = new Transaction([txin], [txout]);
console.log("트랜잭션", transaction);
/*
트랜잭션 Transaction {
txIns: [ TxIn { txOutId: '', txOutIndex: 6, signature: undefined } ],
txOuts: [
TxOut {
account: '203d1de5a622fb91a0b073c8bb729f85f16053a4',
amount: 50
}
],
hash: 'bc7332b97f6aaf8bc2197b92ef6cf0a75c247f00106b1e4506c3b6c2af5a8e78'
}
*/
const utxo = transaction.createUTXO();
console.log("UTXO", utxo);
/*
[
Utxo {
txOutId: 'bc7332b97f6aaf8bc2197b92ef6cf0a75c247f00106b1e4506c3b6c2af5a8e78',
txOutIndex: 0,
account: '203d1de5a622fb91a0b073c8bb729f85f16053a4',
amount: 50
}
]
*/
this.appendUTXO(utxo) // utxo를 utxo를 모아두는 배열에 추가해줌
// 2. todo: addBlock 실행
return this.addBlock([transaction])
// 이전에 넣었던 string[] 대신 transaction이 담긴 배열을 넣어준것
}
public appendUTXO(_utxo: IUtxo[]): void {
this.utxo.push(..._utxo);
}
}
'BlockChain' 카테고리의 다른 글
지갑, 트랜잭션 구현 (6) - 트랜잭션 풀 업데이트와 브로드캐스트 (0) | 2022.06.25 |
---|---|
지갑, 트랜잭션 구현 (5) - 트랜잭션, utxo 업데이트! (0) | 2022.06.23 |
지갑, 트랜잭션 구현 (3) - 지갑 저장, 트랜잭션 보내기 (0) | 2022.06.21 |
지갑, 트랜잭션 구현 (2) / 트랜잭션 풀 (0) | 2022.06.19 |
HTTP 기본 인증 / 지갑, 트랜잭션 구현 (1) (0) | 2022.06.19 |