블록체인 Block-Chain - 비트코인 Transaction 생성후 UTXO 업데이트

dev_swan·2022년 6월 25일
0

블록체인

목록 보기
10/36
post-thumbnail

지난 시간에 코인베이스가 아닌 일반적인 Transaction을 만드는 작업을 해보았는데 이제는 Transaction을 만들때 사용한 TxIns의 내용을 UTXO에서 삭제하고 Transaction을 만들고 나온 TxOuts의 내용을 새로 UTXO에 추가하는 작업을 해보겠습니다.

UTXO를 수정할 메서드 만들기

/* chain.ts */

// 수정할 Transaction 내용을 뽑아와야합니다.
    updateUTXO(tx: ITransaction): void {
        // 기존의 UTXO값 가져오기
        const unspentTxOuts: unspentTxOut[] = this.getUnspentTxOuts();
        // Transaction의 내용으로 새로운 txOut 생성하기
        const newUnspentTxOuts = tx.txOuts.map((txout, index) => {
            return new unspentTxOut(tx.hash, index, txout.account, txout.amount);
        });

        // txIns 부분에 사용된 UTXO를 제거하기
        const UTXO = unspentTxOuts
            .filter((utxo: unspentTxOut) => {
                const bool = tx.txIns.find((txIn: TxIn) => {
                    return utxo.txOutId === txIn.txOutId && utxo.txOutIndex === txIn.txOutIndex;
                });

                // !undefined == true
                return !bool; // tx.txIn에서 사용한 UTXO를 제외하고 사용하지 않은 UTXO 내용을 리턴해줍니다.
            })
            .concat(newUnspentTxOuts); // 새로운 UTXO에 tx.txOuts 객체들을 추가

        // 블록을 생성할때 UTXO에 값이 중복으로 추가되는것을 방지
        let unspentUTXO: unspentTxOut[] = [];
        const result = UTXO.reduce((acc, utxo) => {
            const find = acc.find(({ txOutId, txOutIndex }) => {
                return txOutId === utxo.txOutId && txOutIndex === utxo.txOutIndex;
            });
            if (!find) acc.push(utxo);
            return acc;
        }, unspentUTXO);

        this.unspentTxOuts = result;

        this.appendTransactionPoll(tx);
    }

인자값으로 생성한 Transaction을 받아온후 받아온 Transaction의 TxOut을 map 메서드를 사용하여 새로 추가할 TxOut를 만들어주었습니다.
이제 txIns 부분에 사용된 UTXO를 제거해야하니 기존의 UTXO값에서 filter 메서드를 돌려 배열안에 있는 모든 객체를 확인할 것인데 Transaction의 hash값이 일치하는것과 동시에 Transaction의 Output Index가 일치하는것들을 변수 bool에 넣어주고 bool의 ! 값을 리턴하여 사용한 txIns 부분을 제거한 값을 UTXO에 할당합니다.
또 미리 만들어둔 TxOut 배열을 UTXO 배열에 추가하기위해 concat 메서드를 사용하여 값을 추가하였습니다.
미지막으로 블록을 생성할 때 UTXO에 값이 중복으로 추가되는것을 방지하기위해 unspentUTXO라는 빈 배열을 만들어준뒤 이 배열안에 UTXO배열안에 있는 값들이 있는지 하나씩 확인하며 값이 없으면 추가하고 있다면 추가하지 않도록 하여 값이 중복으로 입력되는것을 방지하였습니다.

블록체인 HTTP 서버

app.post('/sendTransaction', (req, res) => {
    try {
        const receivedTx: ReceviedTx = req.body;
        // 새로운 Transaction을 생성합니다.

        const transaction = Wallet.sendTransaction(receivedTx, ws.getUnspentTxOuts());

        // TransactionPool에 Transaction 내용 추가
        ws.appendTransactionPoll(transaction);
        // UTXO 내용 수정 / UTXO 내용을 최신화하는 함수를 생성합니다. 인자값 Transaction
        ws.updateUTXO(transaction);
        // 트랜잭션이 발동할때마다 브로드 캐스트해주어야 합니다.
        const mesasge: Message = {
            type: MessageType.receivedTx,
            payload: transaction,
        };
        // 다른 노드에게 브로드 캐스트로 트랜잭션 내용을 전달해줍니다.
        ws.broadcast(mesasge);
    } catch (e: any) {
        if (e instanceof Error) console.error(e.message);
    }
    res.json([]);
});

Transaction을 생성하고 방금 생성한 Transaction을 인자값으로 보내주어 미리 만들어둔 chain Class에 있는 updateUTXO 메서드를 실행시켜 사용한 txIns내용은 삭제하고 txOuts의 내용은 UTXO에 추가하도록 해줍니다.

블록을 받았을때 UTXO 업데이트

// 블록을 검증하여 에러가 없을시 내 블록체인에 받은 블록을 추가하는 코드
    public addToChain(_receviedBlock: Block): Failable<undefined, string> {
        const isValid = Block.isValidNewBlock(_receviedBlock, this.getLastestBlock());
        if (isValid.isError) return { isError: true, error: isValid.error };
        this.blockchain.push(_receviedBlock);

        // UTXO 업데이트
        _receviedBlock.data.forEach((tx) => {
            this.updateUTXO(tx);
        });

        // TransactionPool 업데이트
        this.updateTransactionPool(_receviedBlock);

        return { isError: false, value: undefined };
    }
    
 -- 중략 --

// 서로의 블록체인을 비교하여 블록체인을 바꿔줄 함수
    replaceChain(receivedChain: Block[]): Failable<undefined, string> {
        const latestReceivedBlock: Block = receivedChain[receivedChain.length - 1];
        const latestBlock: Block = this.getLastestBlock();

        // 1. 받은 체인의 최신블록.heigth <= 내 체인 최신블록.height = return
        // 2. 받은 체인의 최신블록.previousHash === 내 체인 최신블록.hash = reuturn
        // 3. 받은 체인의 길이가 === 1 ( 제네시스 블록밖에 없음 ) reuturn

        if (latestReceivedBlock.height === 0) {
            return { isError: true, error: '받은 최신블록이 제네시스 블록입니다. ' };
        }

        if (latestReceivedBlock.height <= latestBlock.height) {
            return { isError: true, error: '자신의 체인이 더 길거나 같습니다. ' };
        }

        if (latestReceivedBlock.previousHash === latestBlock.hash) {
            return { isError: true, error: '블록이 하나 모자랍니다. ' };
        }

        // 4. 내 체인이 더 짧으면 받은 블록체인으로 변경
        this.blockchain = receivedChain;

        // TransactionPool 수정 UTXO 수정
        this.blockchain.forEach((_block: IBlock) => {
            // Transaction Pool 업데이트
            this.updateTransactionPool(_block);
            // UTXO 업데이트
            _block.data.forEach((_tx) => {
                this.updateUTXO(_tx);
            });
        });

        return { isError: false, value: undefined };
    }

addToChain메서드와 replaceChain메서드로 블록을 새로 추가해야할 경우에도 UTXO를 수정해주어야 합니다.

0개의 댓글