일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- express실행
- express router
- 블록 만들기
- 시퀄라이즈 기본설정
- css기초
- 아이디 중복체크기능
- JWT 하드코딩
- 라우터와 미들웨어
- node.js path
- javascript기초
- FormData()
- useEffect clean up
- JWT 만들어보기
- buffer.from
- ws 라이브러리
- useContext
- next 매개변수
- 라우트 매개변수
- Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
- mysql wsl
- cookie-parser 만들어보기
- nodejs파일업로드
- 라우터미들웨어 분리
- 라우터 분리
- JWT 로그인 기능 구현
- express session
- OAuth 카카오
- 비동기파일업로드
- express.static
- 세션으로 로그인 구현
- Today
- Total
즐코
지갑, 트랜잭션 구현 (5) - 트랜잭션, utxo 업데이트! 본문
트랜잭션 발생 시 트랜잭션 내용만 Wallet.sendTransaction()의 인자로 보내줬으나,
트랜잭션 발생 시 utxo도 업데이트시켜줘야하므로, 현재 utxo 배열도 가져오게끔 인자로 넣어준다. (ws.getUTXO())
sendTransaction 관련 포스팅 : https://yjleekr.tistory.com/82?category=1284408
// index.ts
app.post("/sendTransaction", (req, res) => {
console.log("reqbody", req.body);
try {
const rcvdTx: ReceivedTx = req.body;
Wallet.sendTransaction(rcvdTx, ws.getUTXO());
// UTXO 내용을 최신화하는 함수 (트랜잭션)
} catch (e) {
if (e instanceof Error) console.log(e.message);
}
res.json([]);
});
현재 잔고 구하기
getBalance()
우선, 현재 남아있는 utxo들 중에서 나의 계정 앞으로 되어있는 utxo가 어떤건지 파악하고 그 utxo들의 amount를 합해서 내 계정 앞으로 잔액이 얼마나 있나 확인해야한다. 즉, 해당 함수를 만들려면 utxo[]와 account가 인자로 필요하다.
Wallet 클래스 내 속성인 balance도 해당 getBalance() 메소드를 사용해서 매번 잔고가 업데이트 되게끔 해준다.
// src/core/wallet/wallet.ts - Wallet 클래스
export class Wallet {
public publicKey: string;
public account: string;
public balance: number;
public signature: Signature;
constructor(_sender: string, _signature: Signature, _utxo: Utxo[]) {
this.publicKey = _sender;
this.account = Wallet.getAcct(this.publicKey);
this.balance = Wallet.getBalance(this.account, _utxo);
this.signature = _signature;
}
static getAcct(_publicKey: string): string {
return Buffer.from(_publicKey).slice(26).toString();
}
static getBalance(_account: string, _utxo: IUtxo[]): number {
// 요청 account와 같은 account를 가진 utxo들만 갖고 와서 amount 더해주기
return _utxo
.filter((v) => v.account === _account)
.reduce((acc, utxo) => {
return (acc += utxo.amount);
}, 0);
}
}
이렇게 하면 지갑 인스턴스 내에서 잔고를 쉽게 가져올 수 있다.
내 지갑 내 잔고가 트랜잭션 amount보다 작다면 에러를 던져주고, 많다면 내 모든 utxo들을 가져와서 그걸 트랜잭션 시 사용해준다.
// src/core/wallet/wallet.ts - Wallet 클래스
static sendTransaction(_rcvdTx: any, _utxo: Utxo[]): Transaction {
const verify = Wallet.getVerify(_rcvdTx);
if (verify.isError) throw new Error(verify.error);
const myWallet = new this(_rcvdTx.sender, _rcvdTx.signature, _utxo);
// 여기서부터 utxo 최신화를 위한 작업을 추가해줬다.
// 내 계좌 잔고 확인 후 트랜잭션할 금액 비교
if (myWallet.balance < _rcvdTx.amount) throw new Error("금액 모자름ㅠ");
// todo : transaction 만드는 과정 createTransaction
// 우선, 내 계정(클라이언트가 입력한) 앞으로 된 utxo만 가져와야한다.
const myUTXO: Utxo[] = Utxo.getMyUTXO(myWallet.account, _utxo);
// 위에서 가져온 내 utxo들을 사용해서 트랜잭션을 만들어준다.
const tx: Transaction = Transaction.createTransaction(_rcvdTx, myUTXO);
return tx;
}
내 utxo들을 가져오는 메소드는 utxo 클래스 내에서 만들어준다.
// src/core/transaction/utxo.ts - Utxo 클래스
static getMyUTXO(_acct: string, _utxo: Utxo[]): Utxo[] {
return _utxo.filter((utxo: UTXO) => utxo.account === _account);
}
그 받은 myUTXO 와 트랜잭션 정보를 가지고 리얼 트랜잰셕 만들어주기
createTransaction()
트랜잭션 내에는 txIns와 txOuts가 들어간다. 이를 만들어주는 함수는 따로 TxIn, TxOut 클래스로 빼주었다.
// src/core/transaction/transaction.ts - transaction 클래스
static createTransaction(_rcvdTx: any, _myUTXO: Utxo[]): Transaction {
// myUtxo를 사용해서 txIns 를 만들어준다.
const { sum, txins } = TxIn.createTxIns(_rcvdTx, _myUTXO);
console.log(sum, txins);
// 만들어진 txIns로 txOuts를 만들어준다.
if (sum === undefined) throw new Error("sum이 0임!");
const txouts: TxOut[] = TxOut.createTxOuts(sum, _rcvdTx);
// 위에서 만들어진 txins와 txouts를 사용해서 tx를 만들어준다.
const tx = new Transaction(txins, txouts);
return tx;
}
createTxIns()
내 utxo들과 트랜잭션 내용을 사용해서 txIns를 만든다.
우선, 내 utxo들을 쫙 돌면서 각 utxo들의 amount 합이 트랜잭션하려는 금액보다 같거나 커질때까지 txins라는 빈 배열에 utxo를 사용해서 만든 txIn을 추가시킨다. 조건을 만족시키면 채워진 txins 배열과 그 배열 내의 utxo들의 금액합계(sum)를 리턴한다.
// src/core/transaction/txin.ts - TxIns 클래스
static createTxIns(_rcvdTx: any, _myUTXO: IUtxo[]) {
let sum = 0;
let txins: TxIns[] = [];
for (let i = 0; i < _myUTXO.length; i++) {
const { txOutId, txOutIndex, amount } = _myUTXO[i];
const newTxIn: TxIn = new TxIn(txOutId, txOutIndex, _rcvdTx.signature);
txins.push(newTxIn);
sum += amount;
if (sum >= _rcvdTx.amount) return { sum, txins };
}
return {};
}
createTxOuts()
위의 createTxIns()로 받은 utxo들의 sum과 트랜잭션 내용을 사용하여 txOuts를 만들어준다.
우선 우리가 만든 트랜잭션 내용 안에는 보내는 사람의 계정주소가 없다. 원래는 있어야하는거지만 우린 편의상 보내는 사람의 계정 대신 공개키를 보내준 것이다. 왜냐하면 우린 공개키만 있으면 지갑주소를 쉽게 만들 수 있기 때문이다. (공개키의 뒤 40자리=계정)
그래서 트랜잭션 내 보내는 사람의 공개키를 이용해서 Wallet 클래스의 getAcct() 함수를 사용하여 보내는 사람의 계정주소를 얻었다.
보내는 이의 지갑 주소와 현재 가져온 uxto의 금액합계(sum)과 보낼 금액(amount)의 차액으로 senderTxOut을 만들고 받는 이의 지갑 주소와 보낼 금액을 묶어서 receiptTxOut을 만들어준다.
이때, 보내는 이의 txOut인 senderTxOut의 amount가 0이라면, utxo로 킵해둘 이유가 없으므로 받는이의 txOut인 receiptTxOut만 TxOuts에 추가한다. 아니라면, 둘다 TxOuts에 추가해주면 된다.
// src/core/transaction/txout.ts - TxOuts 클래스
static createTxOuts(_sum: number, _rcvdTx: any): TxOut[] {
const { sender, recipient, amount } = _rcvdTx;
const senderAcct: string = Wallet.getAcct(sender);
const senderTxOut = new TxOut(senderAcct, _sum - amount);
const receiptTxOut = new TxOut(recipient, amount);
// 보내는 사람의 계정 txOut의 amount가 0이거나, 0보다 작을경우(0보다 작을경우는 없겠지만), txOuts에 추가하지 않는다.
if (senderTxOut.amount <= 0) return [receiptTxOut];
return [receiptTxOut, senderTxOut];
}
이렇게 만들어진 txIns와 txOuts 를 사용해서 Transaction을 만들어주면 끝...
아래와 같이 transaction 객체가 만들어졌다!
근데 여기서 추가로 해줘야할 부분이 있다.
기존 utxo에서 트랜잭션을 위해서 몇 개의 utxo를 가져와가지고 txIns를 만들어줬다면, 그걸 기존 utxo에서 삭제해줘야하고,
새로 만들어진 utxo들은 추가를 해줘야한다. 그 함수가 바로 updateUTXO()
updateUTXO()
1/ 기존의 utxo 배열 상에서 txIns 로 소비한 utxo들은 없애주고, 위의 createTxOuts()로 생성한 새로운 txOuts 배열을 utxo 배열에 추가해줘야하므로 가장 최근 utxo 배열을 가져온다.
2/ 새롭게 생성한 txOuts 를 가지고 utxo 배열로 만들어준다.
// src/core/blockchain/chain.ts
updateUTXO(_tx: ITransaction): void {
const latestUTXO: Utxo[] = this.getUTXO();
// tx 내 txOuts를 사용하여 새로운 utxo 생성
const newUTXO = _tx.txOuts.map((txout, idx) => {
return new Utxo(_tx.hash, idx, txout.account, txout.amount);
});
// 최근 utxo 배열에서 tx의 txins 배열 내 txIn들의 txOutId, txOutIndex와 같은 utxo가 있다면
// 그걸 찾아서 반환한 값이 isSameUtxo이다.
this.utxo = latestUTXO
.filter((utxo) => {
const isUsedTxIn = _tx.txIns.find((txin) => {
return utxo.txOutId === txin.txOutId && utxo.txOutIndex === txin.txOutIndex;
});
console.log("utxo와 같은 TxIn", isUsedTxIn);
console.log("isSameUtxo의 반대!", !isUsedTxIn);
return !isUsedTxIn;
})
.concat(newUTXO);
}
3/ 이제 좀 살짝 복잡하다.. 가장 최근 utxo 배열을 filter메서드로 돌리면, true일 경우에만 그 utxo를 새로운 배열에 담아준다.
이때, find 메소드로 utxo의 txOutId와 txOutIndex가 같은 txIn을 찾을 때마다 true가 나오므로, 그 때 비교된 utxo가 새로운 배열에 담길것이다. 근데 우린 이때 이걸 반대로 생각해줘야한다. 우리의 목적은 txIn과 같은 utxo가 아닌 txIn에 쓰이지 않은 utxo가 필요하다. 따라서, 이게 false가 나오게 하려면 해당 리턴값을 변수에 담아주고 (isUsedTxIn), 앞에 not 연산자를 붙여주면 끝난다. (!isUsedTxIn)
4/ 아래 스크린샷처럼 기존 utxo에 있었던, 즉 사용된 TxIn을 찾아내면 이것의 반대는 false가 나오므로 해당 utxo는 배열에 담기지 않을것이다.
5/ 반면, 사용되지 않은 TxIn은 utxo의 txOutId와 txOutIndex가 같은게 없을테니 undefined가 찍힌다.
따라서, undefined의 반대인 true가 되고 filter에 의해 해당 utxo가 배열에 담길 것이다.
6/ 이렇게 txIns로 쓰인 utxo가 사라진 새로운 배열 내에 상기 2번에서 만들어준 새로운 utxo배열을 concat()로 합쳐준다.
하지만 여기서 끝난게 아니었다...
7/ 거래가 발생했을 시 (블록체인 서버 쪽에서 '/sendTransaction'으로 요청받았을때) UTXO 를 업데이트 해주는 updateUTXO()를 호출한다. 여기에 추가로, 블록 채굴 시 (블록체인 서버 쪽에서 '/mineBlock'으로 요청받았을때) addBlock()이 실행되는데 이 함수 안에도 updateUTXO()를 호출해서 채굴보상으로 떨어지는 txOut이 utxo에 들어가게 된다. 중복된 utxo를 걸러서 업데이트해줘야하므로 updateUTXO()를 아래와 같이 수정해준다.
// src/core/blockchain/chain.ts
updateUTXO(_tx: ITransaction): void {
const latestUTXO: Utxo[] = this.getUTXO();
// tx 내 txOuts를 사용하여 새로운 utxo 생성
const newUTXO = _tx.txOuts.map((txout, idx) => {
return new Utxo(_tx.hash, idx, txout.account, txout.amount);
});
// 최근 utxo 배열에서 tx의 txins 배열 내 txIn들의 txOutId, txOutIndex와 같은 utxo가 있다면
// 그걸 찾아서 반환한 값이 isSameUtxo이다.
const tmpUTXO = latestUTXO
.filter((utxo) => {
const isUsedTxIn = _tx.txIns.find((txin) => {
return utxo.txOutId === txin.txOutId && utxo.txOutIndex === txin.txOutIndex;
});
return !isUsedTxIn;
})
.concat(newUTXO);
// 중복 utxo 제거
let utxoTmp: Utxo[] = [];
const result = tmpUTXO.reduce((acc, utxo) => {
const overlapped = acc.find({ txOutId, txOutIndex }) => {
return txOutId === utxo.txOutId && txOutIndex === utxo.txOutIndex;
});
if(!overlapped) acc.push(utxo);
return acc;
}, utxoTmp);
this.utxo = result;
}
'BlockChain' 카테고리의 다른 글
이더리움 / 스마트 컨트랙트 (EVM?!) / MAC Geth 설치 (1) | 2022.06.28 |
---|---|
지갑, 트랜잭션 구현 (6) - 트랜잭션 풀 업데이트와 브로드캐스트 (0) | 2022.06.25 |
지갑, 트랜잭션 구현 (4) - txIn, txOut, UTXO (0) | 2022.06.22 |
지갑, 트랜잭션 구현 (3) - 지갑 저장, 트랜잭션 보내기 (0) | 2022.06.21 |
지갑, 트랜잭션 구현 (2) / 트랜잭션 풀 (0) | 2022.06.19 |