declare interface ITxIn {
txOutId: string; // 트랜잭션 해시값 ( 보내는 사람, 금액 좌표 )
txOutIndex: number; // 배열의 인덱스 값 ( 보내는 사람, 금액 좌표 )
signature?: string | undefined; // 서명 ( 보내는 사람의 서명 )
}
declare interface ITxOut {
account: string; // 지갑 주소
amount: number; // 금액
}
declare interface ITransaction {
hash: string; // Transaction Hash 값
txOuts: ITxOut[]; // Output 정보들 , 배열안에 객체로 들어간다.
txIns: ITxIn[]; // Input 정보들 , 배열안에 객체로 들어간다.
}
declare interface unspentTxOut {
txOutId: string; // Transaction Hash 값
txOutIndex: number; // UTXO 리스트의 index값
account: string; // 지갑 주소
amount: number; // 금액
}
지난 시간에 만들어둔
Transaction의Type을 정해주는Transaction interface를 활용하여Transaction class를 만들어주도록 하겠습니다.
export class TxIn {
public txOutId: string; // Transaction 해시값
public txOutIndex: number; // Transaction Output 리스트의 배열의 인덱스 값
public signature?: string; // 코인베이스는 signature가 없으니 ? (Optional chaining) 연산자를 사용하여 값이 null 또는 undefined라도 error가 아닌 undefined를 리턴하도록 합니다.
constructor(_txOutId: string, _txOutIndex: number, _signature: string | undefined = undefined) {
this.txOutId = _txOutId;
this.txOutIndex = _txOutIndex;
this.signature = _signature;
}
}
Transaction의Input내용인Txin에서는Transaction의 고유한hash값인txOutId와 이hash에 해당하는Transaction의Ouputs배열안에 몇번째 값을 사용한것인지 알 수 있도록txOutIndex값을 추가하였습니다.
signature는 코인베이스에서는Input의 내용이 없기 때문에 ?(Optional chaining)연산자를 사용하여 값이null or undefined라도error를return하지 않고undefined를return하도록 하였습니다.
export class TxOut {
public account: string; // 지갑 주소
public amount: number; // 금액
constructor(_account: string, _amount: number) {
this.account = _account;
this.amount = _amount;
}
}
Transaction의 Output 내용인 Txout에서는 지갑주소와 금액 인스턴스를 생성하도록 해주었습니다.
export class unspentTxOut {
public txOutId: string;
public txOutIndex: number;
public account: string;
public amount: number;
constructor(_txOutId: string, _txOutIndex: number, _account: string, _amount: number){
this.txOutId = _txOutId; // Transaction의 해쉬값
this.txOutIndex = _txOutIndex; // Transaction의 Output에 있는 각 객체의 Index번호
this.account = _account; // 지갑 주소
this.amount = _amount; // 금액
}
}
unspentTxOut은txOut객체에 있는 내용과Transaction의 내용을 가지고 만들어줍니다.
txOutId와txOutIndex로 해당Transaction의 고유한hash값과 해당hash값에 해당하는Transaction의Output배열안에 몇번째 인덱스인지 알 수 있도록txOutIndex값을 추가하였습니다.
마지막으로 어느 지갑에 얼마의 금액이 들어있는지 알아야 하니account와amount를 추가해주었습니다.
import { SHA256 } from 'crypto-js';
import { TxIn } from './txin';
import { TxOut } from './txout';
import { unspentTxOut } from './unspentTxOut';
export class Transaction {
public hash: string;
public txIns: TxIn[];
public txOuts: TxOut[];
constructor(_txIns: TxIn[], _txOuts: TxOut[]) {
this.txIns = _txIns; // Transaction의 input의 내용들
this.txOuts = _txOuts; // Transaction의 output의 내용들
this.hash = this.createTransactionHash(); // Transaction의 고유한 값
}
// Transaction hash 만들기
createTransactionHash(): string {
// txoutput 내용의 각 객체의 value값들을 스트링으로 이어붙임
const txoutConmtent: string = this.txOuts.map((v) => Object.values(v).join('')).join('');
// txinput 내용의 각 객체의 value값들을 스트링으로 이어붙임
const txinContent: string = this.txIns.map((v) => Object.values(v).join('')).join('');
// 이어붙인 txoutConmtent + txinContent 값을 해쉬화하여 리턴
return SHA256(txoutConmtent + txinContent).toString();
}
// UTXO 생성하기 -> 배열안에 객체 형태로 저장
createUTXO(): unspentTxOut[] {
// txOutId = Transaction의 해쉬값
// txOutIndex = Transaction의 Output에 있는 각 객체의 Index번호
// account = 지갑 주소
// amount = 금액
return this.txOuts.map((v, i) => {
return new unspentTxOut(this.hash, i, v.account, v.amount);
});
}
}
Transaction class에서는Transaction의 고유한 값인hash값과txIns(Input 내용),txOuts(Output 내용)을 추가하였습니다.
각Transaction의 고유한값인hash값을 생성할때는txIns배열안에 있는 객체들의value값과txOuts배열안에 있는 객체들의value값을 문자열로 이어붙이고 해당 값을 해쉬화하여 만들어주었습니다.
마지막으로Transaction을 생성하고 나서UTXO (미사용 트랜잭션 출력값)에 넣어주기 위해txOuts배열안에 있는 객체들에hash값과index값을 추가하여return해주는createUTXO메서드를 만들어 주었습니다.
이제 블록을 생성할때 자동으로 코인베이스 (블록의 첫번째 Transaction값으로 채굴자에 대한 보상 내용이 들어갑니다.)를 생성하도록 HTTP 서버에 블록을 생성하는 API를 수정하도록 하겠습니다.
/* index.ts ( 블록체인 HTTP 서버 ) */
// 블록채굴 API
app.post('/mineBlock', (req, res) => {
const { data } = req.body; // Transaction 객체를 채우기 위한 정보로 지갑주소값이 들어감
const newBlock = ws.miningBlock(data); // 받는 사람의 지갑 주소를 인자값으로 보내줌
if (newBlock.isError) return res.status(500).send(newBlock.error);
res.json(newBlock.value);
});
mineBlock으로 요청이 들어오면 지갑주소를 req.body로 받아오고 해당 지갑주소를 인자값으로 넣어 miningBlock 함수를 실행합니다.
/* chain.ts */
public miningBlock(_account: string): Failable<Block, string> {
const txin: ITxIn = new TxIn('', this.getLastestBlock().height + 1); // 해시값이 겹치지 않도록 this.getLastestBlock().height + 1를 넣어주었다.
const txout: ITxOut = new TxOut(_account, 50); // 지갑주소와 블록을 생성했을때 받는 금액으로 OutPut내용을 만든다.
const transaction: Transaction = new Transaction([txin], [txout]); // input 배열의 내용들과 output 배열의 내용들로 Transaction을 만든다.
return this.addBlcok([transaction, ...this.getTransactionPool()]);
}
// 블록을 추가할 때 실행할 함수
public addBlcok(data: ITransaction[]): Failable<Block, string> {
const previousBlock = this.getLastestBlock();
const adjustmentBlock: Block = this.getAdjustmentBlock();
const newBlock = Block.generateBlock(previousBlock, data, adjustmentBlock);
const isVaild = Block.isValidNewBlock(newBlock, previousBlock);
if (isVaild.isError) return { isError: true, error: isVaild.error };
this.blockchain.push(newBlock);
newBlock.data.forEach((_tx: ITransaction) => {
this.updateUTXO(_tx);
});
// 사용한 트랜잭션을 트랜잭션풀에서 제거해주어야 합니다.
this.updateTransactionPool(newBlock);
return { isError: false, value: newBlock };
}
chain.ts에 있는miningBlock을 보면txin의 내용과txout의 내용을 가지고 채굴에 대한 보상내용이 있는Transaction을 생성하고 이Transaction을addBlock함수의 인자값으로 넣어주면Block의data부분에miningBlock에서 채굴에 보상에 대한 내용으로 생성한Transaction이 들어간 상태로 블록을 생성하게 됩니다.