이번 시간에는 websocket을 활용해서 블록체인을 만들어 보도록 하겠다.
websocket은 http통신과는 다른 형식으로 된 프로토콜 형식이다. HTTP통신과 호환이 되며, 처음 접속시 http통신과 handshake가 이루어진다. handshake는 처음 접속 때 한번만 이루어진다.
블록체인에서 express 서버의 역할은 단 하나이다. block.js와 network.js가 잘 연결되도록 하는 역할을 담당한다. '서버'로서의 역할이 아니라 하나의 클라이언트(?) 적인 역할을 하는 것이다. '서버'의 모양을 하고 있지만 '서버'가 아니다.
아래의 코드를 보면 url이 6개임을 알 수 있다.
현재 네트워크상의 블록을 가져오는 역할을 한다.
현재 블록체인의 버전을 가져오는 역할을 한다.
블록체인을 만드는 작업을 한다.
자신의 peer에 등록된 소켓들의 도메인을 가져오는 역할을 한다.
다른 사람이 내 웹소켓과 통신하기 위해 처음으로 접속하는 포트이다. 이걸 성공해야지 블록체인을 공유할 수 있다.
app.post('/addPeers',(req,res)=>{
const peers = req.body.peers
ws.connectionToPeers(peers)
res.send('success')
})
body에 있는peers를 connectionToPeers라는 함수의 인잣값으로 넣는 것을 알 수 있다.
서버를 멈추게 한다.
process.exit(0)
은 서버를 멈추게 하는 명령어이다. 이 주소를 입력하면 ctrl+c를 입력하지 않아도 서버를 종료시킬 수 있다. 원격으로도 종료시킬 수 있는 것이다.
//server.js
const express = require('express')
const app = express()
const port = process.env.PORT || 3001
const bodyParser = require('body-parser')
const bc = require('./block.js')
const ws = require('./network.js')
app.use(bodyParser.json())
app.get("/blocks",(req,res)=>{
res.send(bc.getBlocks())
})
app.get("/version",(req,res)=>{
res.send(bc.getVersion())
})
app.post("/mineBlock",(req,res)=>{
const data = req.body.data
const result = bc.mineBlock(data) //
if(result === null ) {
res.status(400).send(`블럭추가에 오류가 발생되었습니다.`)
} else {
res.send(result)
}
})
app.get('/peers',(req,res)=>{
res.send( ws.getSockets().map( socket => {
return `${socket._socket.remoteAddress}:${socket._socket.remotePort}`;
}) )
})
app.post('/addPeers',(req,res)=>{
const peers = req.body.peers
ws.connectionToPeers(peers)
res.send('success')
})
app.get("/stop",(req,res)=>{
res.send("Server Stop")
process.exit(0)
})
ws.wsInit()
app.listen(port,()=>{
console.log(`server start port ${port}`)
})
여기서 ws.Init()의 역할은 좀 더 뒤로 가서 알아보도록 하겠다.
block.js는 블럭을 만드는 함수들을 모아놓은 파일이다. 함수가 많지만 종류는 8가지로 나눌 수 있다.
function createGenesisBlock(){
const version ="1.0.0"
const index = 0
const time = 1630907567
const previousHash = '0'.repeat(64)
const body = ['hello block']
const tree = merkle('sha256').sync(body)
const root = tree.root() || '0'.repeat(64)
const header = new BlockHeader(version,index,previousHash,time,root)
return new Block(header,body)
}
class BlockHeader {
constructor(version ,index ,previousHash, time, merkleRoot){
this.version = version
this.index = index
this.previousHash = previousHash
this.time = time
this.merkleRoot = merkleRoot
}
}
class Block {
constructor(header,body){
this.header = header
this.body = body
}
}
let Blocks = [createGenesisBlock()]
createGenesisBlock()은 최초의 블록을 만드는 역할을 한다. 여기선 header
부분과 body
부분으로 나눠서 Block이란 클래스로 만드는 역할을 한다. (new가 붙었으니 객체의 형태로 return 될 것이다.)
previousHash, time, index, version을 하드코딩 해주었다는 것을 기억하자!
그리고 처음 생성한 블록을 Blocks란 배열에 넣었다. 이 Blocks란 배열은 앞으로 블록들이 생성되어져서 넣어질 공간이다.
간단한 함수로 단 2줄이면 끝난다. 이는 나중에 전체의 블럭이 유효한지 검증하는데 쓰인다.
function getBlocks(){
return Blocks
}
이 함수는 나중에 내가 가진 블록체인과 남이 가진 블록체인을 비교해서 하나만 모자랄경우 하나만 추가하는데 쓰이는 함수이다.
function getLastBlock() {
return Blocks[Blocks.length - 1]
}
function nextBlock(data){
const prevBlock = getLastBlock()
const version = getVersion()
const index = prevBlock.header.index + 1
const previousHash = createHash(prevBlock)
const time = getCurrentTime()
const merkleTree = merkle("sha256").sync(data)
const merkleRoot = merkleTree.root() || '0'.repeat(64)
const header = new BlockHeader(version,index,previousHash,time,merkleRoot);
return new Block(header,data)
}
function getVersion(){
const {version} = JSON.parse(fs.readFileSync("../package.json"))
return version
}
function getCurrentTime(){
return Math.ceil(new Date().getTime()/1000)
}
nextBlock
이라는 함수는 인잣값으로 받은 data를 body에 넣어서 블록을 생성한다. 여기엔 하드코딩으로 작성된 코드가 없다. previousHash를 createHash(prevBlock) 즉, createHash에 이전 블록을 넣어서 만드는 것 말고는 createGenesisBlock()
와 다른 점이 없다. 그럼 createHash에 대해서 알아보자.
function createHash(block){
const {
version,
index,
previousHash,
time,
merkleRoot
} = block.header
const blockString = version+index+previousHash+time+merkleRoot;
const Hash = CryptoJs.SHA256(blockString).toString()
return Hash
}
위에서 인잣값으로 이전 블록의 값을 받았음을 알 수 있다. 이는 header에 속해있는 값들을 string값으로 만들어 모두 더한 것을 sha256으로 암호화한 결과라는 것을 알 수 있다. 이것이 이전 블록의 주소값이 되는 것이다.
🔑새로운 블록을 만들어 블록체인에 추가하려면 반드시 '검증'을 거쳐야 한다.
❗검증할 것
1. 현재의 블록
- 타입들이 올바른가
- 인덱스가 올바른가
- previousHash가 올바른가 (이전 블록값 이용)
- body의 내용이 존재하지 않는가? 👉없으면 안됨
- 암호화가 제대로 되었는가
2. 전체적인 블록
- genesisblock이 같은가
- 다른 블록들 검증(현재의 블록 검증방법으로)
function addBlock(newBlock){
if(isValidNewBlock(newBlock, getLastBlock())) {
Blocks.push(newBlock);
return true;
}
return false;
}
이 함수는 블록 다른 노드와 블록 하나만 차이나거나, 아니면 블록 전체를 검증할 때 각각 블록들을 검증할 때 쓰이는 함수이다.
function isValidNewBlock(currentBlock,previousBlock){
if(!isValidType(currentBlock)){
console.log(`invaild block structrue ${JSON.stringify(currentBlock)}`)
return false
}
if(previousBlock.header.index + 1 !== currentBlock.header.index) {
console.log(`invaild index`)
return false
}
if(createHash(previousBlock) !== currentBlock.header.previousHash){
console.log(`invaild previousBlock`)
return false
}
if (currentBlock.body.length === 0) {
console.log(`invaild body`)
return false
}
if (merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot) {
console.log(`invalid merkleRoot`)
return false
}
return true
}
function isValidType(block){
return (
typeof(block.header.version) === "string" &&
typeof(block.header.index) === "number" &&
typeof(block.header.previousHash) === "string" &&
typeof(block.header.time) === "number" &&
typeof(block.header.merkleRoot) === "string" &&
typeof(block.body) === "object"
)
}
function isValidBlock(Blocks){
if (JSON.stringify(Blocks[0]) !== JSON.stringify(createGenesisBlock())) {
console.log(`genesis error`)
return false
}
let tempBlocks = [Blocks[0]]
for ( let i = 1; i < Blocks.length; i++) {
if (isVaildNewBlock(Blocks[i],tempBlocks[i-1])) {
tempBlocks.push(Blocks[i])
} else {
return false
}
}
return true
}
위의 함수는 먼저 제네시스 블록을 검증한 후에, 그 다음에 하나씩 블록을 검증하는 함수이다. 다른 노드와 블록의 차이가 하나를 초과할 때에 제네시스 블록부터 시작해서 잘못되었는지 각각의 블록을 검증한다.(현재의 블록을 추가하는 코드로)
//block.js
const fs = require('fs')
const merkle = require('merkle')
const CryptoJs = require('crypto-js')
const random = require('random')
class BlockHeader {
constructor(version ,index ,previousHash, time, merkleRoot){
this.version = version
this.index = index
this.previousHash = previousHash
this.time = time
this.merkleRoot = merkleRoot
}
}
class Block {
constructor(header,body){
this.header = header
this.body = body
}
}
let Blocks = [createGenesisBlock()]
function getBlocks(){
return Blocks
}
function getLastBlock() {
return Blocks[Blocks.length - 1]
}
function createGenesisBlock(){
const version ="1.0.0"
const index = 0
const time = 1630907567
const previousHash = '0'.repeat(64)
const body = ['hello block']
const tree = merkle('sha256').sync(body)
const root = tree.root() || '0'.repeat(64)
const header = new BlockHeader(version,index,previousHash,time,root)
return new Block(header,body)
}
function nextBlock(data){
const prevBlock = getLastBlock()
const version = getVersion()
const index = prevBlock.header.index + 1
const previousHash = createHash(prevBlock)
const time = getCurrentTime()
const merkleTree = merkle("sha256").sync(data) // []
const merkleRoot = merkleTree.root() || '0'.repeat(64)
const header = new BlockHeader(version,index,previousHash,time,merkleRoot);
return new Block(header,data)
}
function createHash(block){
const {
version,
index,
previousHash,
time,
merkleRoot
} = block.header
const blockString = version+index+previousHash+time+merkleRoot;
const Hash = CryptoJs.SHA256(blockString).toString()
return Hash
}
function addBlock(newBlock){
if(isVaildNewBlock(newBlock, getLastBlock())) {
Blocks.push(newBlock);
return true;
}
return false;
}
function mineBlock(blockData){
const newBlock = nextBlock(blockData) // Object Block {header, body}
if(addBlock(newBlock)){
const nw = require('./network')
nw.broadcast(nw.responseLastMsg())
return newBlock
} else {
return null
}
}
function isVaildNewBlock(currentBlock,previousBlock){
if(!isVaildType(currentBlock)){
console.log(`invaild block structrue ${JSON.stringify(currentBlock)}`)
return false
}
if(previousBlock.header.index + 1 !== currentBlock.header.index) {
console.log(`invaild index`)
return false
}
if(createHash(previousBlock) !== currentBlock.header.previousHash){
console.log(`invaild previousBlock`)
return false
}
if (currentBlock.body.length === 0) {
console.log(`invaild body`)
return false
}
if (merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot) {
console.log(`invalid merkleRoot`)
return false
}
return true
}
function isVaildType(block){
return (
typeof(block.header.version) === "string" &&
typeof(block.header.index) === "number" &&
typeof(block.header.previousHash) === "string" &&
typeof(block.header.time) === "number" &&
typeof(block.header.merkleRoot) === "string" &&
typeof(block.body) === "object"
)
}
function replaceBlock(newBlocks){
if (isVaildBlock(newBlocks) && newBlocks.length > Blocks.length && random.boolean()) {
console.log(`Blocks 배열을 newBlocks 으로 교체합니다.`)
const nw = require('./network')
Blocks = newBlocks
nw.broadcast(nw.responseLastMsg())
} else {
console.log(`메시지로부터 받은 블록배열이 맞지 않습니다.`)
}
}
function getVersion(){
const {version} = JSON.parse(fs.readFileSync("../package.json"))
return version
}
function getCurrentTime(){
return Math.ceil(new Date().getTime()/1000)
}
function isVaildBlock(Blocks){
if (JSON.stringify(Blocks[0]) !== JSON.stringify(createGenesisBlock())) {
console.log(`genesis error`)
return false
}
let tempBlocks = [Blocks[0]]
for ( let i = 1; i < Blocks.length; i++) {
if (isVaildNewBlock(Blocks[i],tempBlocks[i-1])) {
tempBlocks.push(Blocks[i])
} else {
return false
}
}
return true
}
module.exports = {
getBlocks,
getLastBlock,
addBlock,
getVersion,
mineBlock,
createHash,
replaceBlock
}
websocket은 여기서 주요한 역할을 담당한다. 서버의 역할을 담당하면서 클라이언트의 역할도 된다. 왜냐하면 블록체인은 말 그대로 자기 자신이 하나의 서버이자 클라이언트의 역할을 하기때문이다. 그래서 하나의 노드 서버가 종료되어도 모두의 서버가 같은 정보를 가지고 있을 수 있다.
function wsInit(){
const server = new WebSocket.Server({ port:wsPORT})
server.on("connection",(ws)=>{
init(ws)
})
}
처음에 server.js에 있던 코드이다. 이걸로 시작이 된다.
서버의 역할을 하는 코드이다.
WebSocket.Server는 자신의 port번호를 설정함으로써 웹소켓 서버를 개설한다.
그리고 다른 웹소켓 서버가 접속할 때까지 기다리는 역할을 한다.
server.on("connection",callback)
은 다른 소켓이 처음으로 접속했을 때 핸드쉐이크가 일어날 때 작동하는 코드이다. ws
에는 고유의 키값이 담긴다.
function connectionToPeers(newPeers){
newPeers.forEach(peer=>{
const ws = new WebSocket(peer)
ws.on("open",()=>{ init(ws) })
ws.on("error",()=>{ console.log("connection failed") })
})
}
클라이언트 쪽에서는 웹소켓에 접속하기 위에서 다음과 같이 코드를 작성해준다.
cosnt ws = new WebSocket(접속할 url)
그리고 접속이 이루어질 때 작동하게 할 코드가 있다면 ws.on("open",callback)
으로 입력해준다.
클라이언트 접속과 서버 접속의 코드 차이
클라이언트가 접속해서 작동을 할 때는
cosnt ws = new WebSocket(접속할 url)
으로 접속하고ws.on("open",콜백함수)
의 콜백함수에 코드를 입력해주면 된다.
반면 서버가 접속을 감지할 때는
const server = new WebSocket.Server({ port:자신의 포트번호})
를 입력한 뒤 자신의 포트번호에 들어온 이에게 행할 함수에 대해선
server.on("connection",콜백함수)
로 입력해준다.
let sockets = []
다음과 같이 소켓에 주소를 넣을 공간을 마련한다.
init(ws)
여기서 ws는 상대방의 도메인 값이거나 상대방의 고유한 키값이다.
function init(ws){
sockets.push(ws)
initMessageHandler(ws)
initErrorHandler(ws)
write(ws,queryBlockMsg())
}
가장 먼저 실행되는 wsInit()에 포함되어 가장 먼저 실행되는 코드이다. 클라이언트든지 서버든지 이 코드가 실행된다.
먼저, sockets의 배열에 넣는다.
그 다음에 initMessageHandler
가 실행이 된다. 만약 에러가 날 경우는 initErrorHandler
가 실행이 된다.
그 후에 write
함수가 실행이 되는 구조이다.
initMessageHandler(ws)
function initMessageHandler(ws){
ws.on("message",data => {
const message = JSON.parse(data)
switch(message.type){
case MessageAction.QUERY_LAST:
write(ws,responseLastMsg())
break;
case MessageAction.QUERY_ALL:
write(ws,responseBlockMsg())
break;
case MessageAction.RESPONSE_BLOCK:
handleBlockResponse(message)
break;
}
})
}
initMessageHandler
는 메세지를 받았을 때 그것을 타입에 따라 나누어주는 각기 다른 함수를 실행시키는 역할을 한다.
만약 메세지의 타입이 'MessageAction.QUERY_LAST'이라면 write(ws,responseLastMsg())
를 실행시킨다. 이러한 함수의 형태는 react의 reducer의 형태와 아주 비슷하다. socket.io에서 사용자정의 함수가 하는 역할을 type이 한다고 생각하면 편하다. ws는 가벼운 패키지기에 이런 것 하나하나 설정해주어야 한다. 하나의 기법이기에 어렵게 생각하지 않아도 된다.
그리고
function responseLastMsg(){
return {
type:MessageAction.RESPONSE_BLOCK,
data:JSON.stringify([bc.getLastBlock()])
}
}
이기에 결국 이 함수의 형태는 다음과 같이 된다.
function initMessageHandler(ws){
ws.on("message",data => {
const message = JSON.parse(data)
switch(message.type){
case 0:
write(ws,{type:2,data:JSON.stringify([bc.getLastBlock()])})
break;
case 1:
write(ws,{type:0,data:null})
break;
case 2:
handleBlockResponse(message)
break;
}
})
}
여기에
function write(ws,message){
ws.send(JSON.stringify(message))
}
을 대입하면 initMessageHandler(ws)
는 다음과 같이 나타낼 수 있다.
function initMessageHandler(ws){
ws.on("message",data => {
const message = JSON.parse(data)
switch(message.type){
case 0://query_last
ws.send(JSON.stringify({type:2,data:JSON.stringify([bc.getLastBlock()])}))
break;
case 1://query_all
ws.send(JSON.stringify({type:0,data:null}))
break;
case 2://response_block
handleBlockResponse(message)
break;
}
})
}
handleBlockResponse(message)
다른 사람의 블럭이 나의 블럭보다 많을 때
👉받은 블록 중 마지막 블록의 previousHash값 = 내 마지막 블록으로 만들어진 암호화 값
->하나만 추가한다.(addBlock사용)
👉받은 블록의 길이가 1일 때(내꺼의 블록이 연결되지 않았음?)
->모든 블럭을 보낸다.
👉많이 차이가 날 때
->블록 전체를 최신화한다. (replaceBlock사용)
만약 다른사람들의 블럭보다 나의 블럭이 더 많을 때는 반대로 실행이 될 것이다.
function handleBlockResponse(message){
const receivedBlocks = JSON.parse(message.data)
const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1]
const lastBlockHeld = bc.getLastBlock()
if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
if (bc.addBlock(lastBlockReceived)) {
broadcast(responseLastMsg())
}
} else if (receivedBlocks.length === 1) {
console.log(`피어로부터 블록을 연결해야합니다!`)
broadcast(queryAllMsg())
} else {
console.log(`블럭을 최신화를 진행합니다.`)
bc.replaceBlock(receivedBlocks)
}
} else {
console.log('블럭이 이미 최신화입니다.')
}
}
function broadcast(message){
sockets.forEach( socket => {
write(socket,message)
})
}
내가 연결된 모든 소켓한테 메세지를 보내는 함수이다.
function getSockets(){ return sockets }
const MessageAction = {
QUERY_LAST:0,
QUERY_ALL:1,
RESPONSE_BLOCK:2,
}
function init(ws){
sockets.push(ws)
initMessageHandler(ws)
initErrorHandler(ws)
write(ws,queryBlockMsg())
}
function initMessageHandler(ws){
ws.on("message",data => {
const message = JSON.parse(data)
switch(message.type){
case MessageAction.QUERY_LAST:
write(ws,responseLastMsg())
break;
case MessageAction.QUERY_ALL:
write(ws,responseBlockMsg())
break;
case MessageAction.RESPONSE_BLOCK:
handleBlockResponse(message)
break;
}
})
}
function queryBlockMsg(){
return {
type:MessageAction.QUERY_LAST,
data:null
}
}
function initErrorHandler(ws){
ws.on("close",()=>{ closeConnection(ws) })
ws.on("error",()=>{ closeConnection(ws) })
}
function write(ws,message){
ws.send(JSON.stringify(message))
}
function responseLastMsg(){
return {
type:MessageAction.RESPONSE_BLOCK,
data:JSON.stringify([bc.getLastBlock()])
}
}
function responseBlockMsg(){
return {
type:MessageAction.RESPONSE_BLOCK,
data:JSON.stringify(bc.getBlocks())
}
}
function handleBlockResponse(message){
const receivedBlocks = JSON.parse(message.data)
const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1]
const lastBlockHeld = bc.getLastBlock()
if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
console.log(
"블록의 갯수 \n" +
`내가 받은 블록의 index 값 ${lastBlockReceived.header.index}\n` +
`내가 가지고있는 블럭의 index 값 ${lastBlockHeld.header.index}\n`
)
if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
if (bc.addBlock(lastBlockReceived)) {
broadcast(responseLastMsg())
}
} else if (receivedBlocks.length === 1) {
console.log(`피어로부터 블록을 연결해야합니다!`)
broadcast(queryAllMsg())
} else {
console.log(`블럭을 최신화를 진행합니다.`)
bc.replaceBlock(receivedBlocks)
}
} else {
console.log('블럭이 이미 최신화입니다.')
}
}
//network.js
const WebSocket = require('ws')
const wsPORT = process.env.WS_PORT || 6006
const bc = require('./block')
let sockets = []
function getSockets(){ return sockets }
const MessageAction = {
QUERY_LAST:0,
QUERY_ALL:1,
RESPONSE_BLOCK:2,
}
function initMessageHandler(ws){
ws.on("message",data => {
const message = JSON.parse(data)
switch(message.type){
case MessageAction.QUERY_LAST:
write(ws,responseLastMsg())
break;
case MessageAction.QUERY_ALL:
write(ws,responseBlockMsg())
break;
case MessageAction.RESPONSE_BLOCK:
handleBlockResponse(message)
break;
}
})
}
function queryAllMsg(){
return {
type:MessageAction.QUERY_ALL,
data:null
}
}
function queryBlockMsg(){
return {
type:MessageAction.QUERY_LAST,
data:null
}
}
function responseLastMsg(){
return {
type:MessageAction.RESPONSE_BLOCK,
data:JSON.stringify([bc.getLastBlock()])
}
}
function responseBlockMsg(){
return {
type:MessageAction.RESPONSE_BLOCK,
data:JSON.stringify(bc.getBlocks())
}
}
function handleBlockResponse(message){
const receivedBlocks = JSON.parse(message.data)
const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1]
const lastBlockHeld = bc.getLastBlock()
if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
console.log(
"블록의 갯수 \n" +
`내가 받은 블록의 index 값 ${lastBlockReceived.header.index}\n` +
`내가 가지고있는 블럭의 index 값 ${lastBlockHeld.header.index}\n`
)
if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
if (bc.addBlock(lastBlockReceived)) {
broadcast(responseLastMsg())
}
} else if (receivedBlocks.length === 1) {
console.log(`피어로부터 블록을 연결해야합니다!`)
broadcast(queryAllMsg())
} else {
console.log(`블럭을 최신화를 진행합니다.`)
bc.replaceBlock(receivedBlocks)
}
} else {
console.log('블럭이 이미 최신화입니다.')
}
}
function initErrorHandler(ws){
ws.on("close",()=>{ closeConnection(ws) })
ws.on("error",()=>{ closeConnection(ws) })
}
function closeConnection(ws){
console.log(`Connection close ${ws.url}`)
sockets.splice(sockets.indexOf(ws),1)
}
function wsInit(){
const server = new WebSocket.Server({ port:wsPORT})
server.on("connection",(ws)=>{
console.log('ws는 과연 무엇일까요?')
console.log(ws)
init(ws)
})
}
function write(ws,message){ ws.send(JSON.stringify(message)) }
function broadcast(message){
sockets.forEach( socket => {
write(socket,message)
})
}
function connectionToPeers(newPeers){
newPeers.forEach(peer=>{
const ws = new WebSocket(peer)
ws.on("open",()=>{ init(ws) })
ws.on("error",()=>{ console.log("connection failed") })
})
}
function init(ws){
sockets.push(ws)
initMessageHandler(ws)
initErrorHandler(ws)
write(ws,queryBlockMsg())
}
module.exports = {
wsInit,
getSockets,
broadcast,
responseLastMsg,
connectionToPeers,
}
$ curl http://localhost:3001/blocks
를 zsh터미널에 입력한다.
curl -X POST -H "Content-Type:application/json" -d "{\"peers\":[\"ws://상대의 웹소켓 주소및 포트\"]}" http://localhost:3001/addPeers
$ curl -X POST -H "Content-Type:application/json" -d"{\"data\":[\"데이터 내용\"]}" http://localhost:3001/mineBlock
위와 같이 터미널에 입력한다. 그러면 블록이 생성된다.
$ curl http://localhost:3001/blocks
를 zsh터미널에 입력한다.
만약 파이썬이 깔려있다면 다음과 같이 입력하면 잘 정리된 json파일을 볼 수 있다.
$ curl http://localhost:3001/blocks | python3 -m json.tool