前言

为了更好的理解区块链的底层实现原理,决定自己动手模拟实现一条区块链。

思路分析

通过之前的学习,从文本知识的角度,我们知道,创世区块、记账原理、挖矿原理、工作量证明、共识机制等等区块链的相关知识。


创建一条区块链,首先默认构造创世区块。在此基础上,我们可以发布交易,并进行挖矿,计算出工作量证明,将交易记录到区块中,每成功的挖一次矿,块高就+1。当然在此过程中,可能会出现“造假”的问题。也就是说,每一个新注册的节点,都可以有自己的链。这些链长短不一,为了保证账本的一致性,需要通过一种一致性共识算法来找到最长的链,作为样本,同步数据,保证每个节点上的账本信息都是一致的。

数据结构

* 区块链

如图所示,索引为1的区块即为创始区块。可想而知,可以用List<区块>来表示区块链。其中,区块链的高度即为链上区块的块数,上图区块高度为4。
* 区块

单个区块的数据结构有索引、交易列表、时间戳、工作量证明、上一个区块的hash组成。
* 交易列表

整个区块链就是一个超级大的分布式账本,当发生交易时,矿工们通过计算工作量证明的方法来进行挖矿(本文中挖到矿将得到1个币的奖励),将发生的交易记录到账本之中。
Web API

我们将通过Postman来模拟请求。请求API如下:

/nodes/register 注册网络节点
/nodes/resolve 一致性共识算法
/transactions/new 新建交易
/mine 挖矿
/chain 输出整条链的数据


项目目录结构

Gradle Web 项目

dependencies { compile('javax:javaee-api:7.0')
compile('org.json:json:20160810') testCompile('junit:junit:4.12') }
实现代码

注释写的很详细,如果遇到不懂的地方,欢迎大家一同讨论。

* BlockChain类 ,所有的核心代码都在其中。 // 存储区块链 private List<Map<String, Object>> chain;
// 该实例变量用于当前的交易信息列表 private List<Map<String, Object>> currentTransactions; //
网络中所有节点的集合 private Set<String> nodes; private static BlockChain blockChain =
null; private BlockChain() { // 初始化区块链以及当前的交易信息列表 chain = new
ArrayList<Map<String, Object>>(); currentTransactions =new
ArrayList<Map<String, Object>>();// 初始化存储网络中其他节点的集合 nodes = new
HashSet<String>();// 创建创世区块 newBlock(100, "0"); } /** * 在区块链上新建一个区块 * @param
proof 新区块的工作量证明 * @param previous_hash 上一个区块的hash值 * @return 返回新建的区块 */ public
Map<String, Object>newBlock(long proof, String previous_hash) { Map<String,
Object> block =new HashMap<String, Object>(); block.put("index",
getChain().size() +1); block.put("timestamp", System.currentTimeMillis());
block.put("transactions", getCurrentTransactions()); block.put("proof", proof);
// 如果没有传递上一个区块的hash就计算出区块链中最后一个区块的hash block.put("previous_hash", previous_hash
!=null ? previous_hash : hash(getChain().get(getChain().size() - 1))); //
重置当前的交易信息列表 setCurrentTransactions(new ArrayList<Map<String, Object>>());
getChain().add(block);return block; } // 创建单例对象 public static BlockChain
getInstance() { if (blockChain == null) { synchronized (BlockChain.class) { if
(blockChain ==null) { blockChain = new BlockChain(); } } } return blockChain; }
/** * @return 得到区块链中的最后一个区块 */ public Map<String, Object> lastBlock() { return
getChain().get(getChain().size() -1); } /** * 生成新交易信息,信息将加入到下一个待挖的区块中 * @param
sender 发送方的地址 * @param recipient 接收方的地址 * @param amount 交易数量 * @return
返回该交易事务的块的索引 */ public int newTransactions(String sender, String recipient, long
amount) { Map<String, Object> transaction =new HashMap<String, Object>();
transaction.put("sender", sender); transaction.put("recipient", recipient);
transaction.put("amount", amount); getCurrentTransactions().add(transaction);
return (Integer) lastBlock().get("index") + 1; } /** * 生成区块的 SHA-256格式的 hash值 *
@param block 区块 * @return 返回该区块的hash */ public static Object hash(Map<String,
Object> block) {return new Encrypt().Hash(new JSONObject(block).toString()); }
/** * 注册节点 * @param address 节点地址 * @throws MalformedURLException */ public void
registerNode(String address) throws MalformedURLException { URL url = new
URL(address); String node = url.getHost() +":" + (url.getPort() == -1 ?
url.getDefaultPort() : url.getPort()); nodes.add(node); }/** *
验证是否为有效链,遍历每个区块验证hash和proof,来确定一个给定的区块链是否有效 * @param chain * @return */ public
boolean vaildChain(List<Map<String,Object>> chain) { Map<String,Object>
lastBlock = chain.get(0); int currentBlockIndex = 1; while (currentBlockIndex <
lastBlock.size()) { Map<String,Object> currentBlock =
chain.get(currentBlockIndex);//检查区块的hash是否正确 if (!currentBlock.get(
"previous_hash").equals(hash(lastBlock))) { return false; } lastBlock =
currentBlock; currentBlockIndex ++; }return true; } /** * 使用网络中最长的链.
遍历所有的邻居节点,并用上一个方法检查链的有效性, * 如果发现有效更长链,就替换掉自己的链 * @return 如果链被取代返回true,
否则返回false * @throws IOException */ public boolean resolveConflicts() throws
IOException {//获得当前网络上所有的邻居节点 Set<String> neighbours = this.nodes;
List<Map<String, Object>> newChain =null; // 寻找最长的区块链0 long maxLength = this
.chain.size();// 获取并验证网络中的所有节点的区块链 for (String node : neighbours) { URL url =
new URL("http://" + node + "/chain"); HttpURLConnection connection =
(HttpURLConnection) url.openConnection(); connection.connect();if
(connection.getResponseCode() ==200) { BufferedReader bufferedReader = new
BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
StringBuffer responseData =new StringBuffer(); String response = null; while
((response = bufferedReader.readLine()) !=null) {
responseData.append(response); } bufferedReader.close(); JSONObject jsonData =
new JSONObject(responseData.toString()); long length = jsonData.getLong(
"blockLength"); List<Map<String, Object>> chain = (List) jsonData.getJSONArray(
"chain").toList(); // 检查长度是否长,链是否有效 if (length > maxLength &&
vaildChain(chain)) { maxLength = length; newChain = chain; } } }//
如果发现一个新的有效链比我们的长,就替换当前的链 if (newChain != null) { this.chain = newChain; return
true; } return false; }
* Proof 类 ,计算工作量证明 /** * 计算当前区块的工作量证明 * @param last_proof 上一个区块的工作量证明 *
@return */ public long ProofOfWork(long last_proof){ long proof = 0; while
(!(vaildProof(last_proof,proof))) { proof ++; }return proof; } /** *
验证证明,是否拼接后的Hash值以4个0开头 * @param last_proof 上一个区块工作量证明 * @param proof 当前区块的工作量证明
* @return */ public boolean vaildProof(long last_proof, long proof) { String
guess = last_proof +"" + proof; String guess_hash = new Encrypt().Hash(guess);
boolean flag = guess_hash.startsWith("0000"); return flag; }
* Encrypt 类 ,Hash计算工具类 public class Encrypt { /** * 传入字符串,返回 SHA-256 加密字符串 *
@param strText * @return */ public String Hash(final String strText) { // 返回值
String strResult =null; // 是否是有效字符串 if (strText != null && strText.length() > 0
) {try { // 创建加密对象,传入要加密类型 MessageDigest messageDigest =
MessageDigest.getInstance("SHA-256"); // 传入要加密的字符串
messageDigest.update(strText.getBytes());// 执行哈希计算,得到 byte 数组 byte byteBuffer[]
= messageDigest.digest();// 將 byte 数组转换 string 类型 StringBuffer strHexString =
new StringBuffer(); // 遍历 byte 数组 for (int i = 0; i < byteBuffer.length; i++) {
// 转换成16进制并存储在字符串中 String hex = Integer.toHexString(0xff & byteBuffer[i]); if
(hex.length() ==1) { strHexString.append('0'); } strHexString.append(hex); } //
得到返回結果 strResult = strHexString.toString(); } catch (NoSuchAlgorithmException
e) { e.printStackTrace(); } }return strResult; } }
* FullChain 类,输出整条链的信息。 /** * @Author: cfx * @Description:
该Servlet用于输出整个区块链的数据(Json) * @Date: Created in 2018/5/9 17:24 */ @WebServlet(
"/chain") public class FullChain extends HttpServlet{ @Override protected void
doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException { BlockChain blockChain =
BlockChain.getInstance(); Map<String,Object> response =new HashMap<String,
Object>(); response.put("chain",blockChain.getChain()); response.put(
"blockLength",blockChain.getChain().size()); JSONObject jsonObject = new
JSONObject(response); resp.setContentType("application/json"); PrintWriter
printWriter = resp.getWriter(); printWriter.println(jsonObject);
printWriter.close(); } }
* InitialID 类 ,初始化时执行,随机的uuid作为矿工的账户地址。 /** * @Author: cfx * @Description:
初始化时,使用UUID来作为节点ID * @Date: Created in 2018/5/9 17:17 */ @WebListener public
class InitialID implements ServletContextListener { public void
contextInitialized(ServletContextEvent sce) { ServletContext servletContext =
sce.getServletContext(); String uuid = UUID.randomUUID().toString().replace("-",
""); servletContext.setAttribute("uuid", uuid); System.out.println("uuid is : "
+servletContext.getAttribute("uuid")); } public void contextDestroyed
(ServletContextEvent sce) { } }
* Register 类 ,节点注册类,记录网络上所有的节点,用户共识算法,保证所有的节点上的账本都是一致的。 /** * @Author: cfx *
@Description: 注册网络节点 * @Date: Created in 2018/5/10 11:26 */ @WebServlet(
"/nodes/register") public class Register extends HttpServlet { @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException { req.setCharacterEncoding("utf-8"); //
读取客户端传递过来的数据并转换成JSON格式 BufferedReader reader = req.getReader(); String input =
null; StringBuffer requestBody = new StringBuffer(); while ((input =
reader.readLine()) !=null) { requestBody.append(input); } JSONObject jsonValue =
new JSONObject(requestBody.toString()); BlockChain blockChain =
BlockChain.getInstance(); blockChain.registerNode(jsonValue.getString("nodes"
)); PrintWriter printWriter = resp.getWriter(); printWriter.println(new
JSONObject().append("message","The Nodes is : " + blockChain.getNodes()));
printWriter.close(); } }
* NewTransaction 类,新建交易类。 /** * @Author: cfx * @Description:
该Servlet用于接收并处理新的交易信息 * @Date: Created in 2018/5/9 17:22 */ @WebServlet(
"/transactions/new") public class NewTransaction extends HttpServlet { protected
void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException { req.setCharacterEncoding("utf-8"); //
读取客户端传递过来的数据并转换成JSON格式 BufferedReader reader = req.getReader(); String input =
null; StringBuffer requestBody = new StringBuffer(); while ((input =
reader.readLine()) !=null) { requestBody.append(input); } JSONObject jsonValues
=new JSONObject(requestBody.toString()); // 检查所需要的字段是否位于POST的data中 String[]
required = {"sender", "recipient", "amount" }; for (String string : required) {
if (!jsonValues.has(string)) { // 如果没有需要的字段就返回错误信息 resp.sendError(400, "Missing
values"); } } // 新建交易信息 BlockChain blockChain = BlockChain.getInstance(); int
index = blockChain.newTransactions(jsonValues.getString("sender"),
jsonValues.getString("recipient"), jsonValues.getLong("amount")); //
返回json格式的数据给客户端 resp.setContentType("application/json"); PrintWriter
printWriter = resp.getWriter(); printWriter.println(new JSONObject().append(
"message", "Transaction will be added to Block " + index));
printWriter.close(); } }
* Mine , 挖矿类。 /** * @Author: cfx * @Description:
该Servlet用于运行工作算法的证明来获得下一个证明,也就是所谓的挖矿 * @Date: Created in 2018/5/9 17:21 */
@WebServlet("/mine") public class Mine extends HttpServlet{ @Override protected
void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException { BlockChain blockChain =
BlockChain.getInstance();//计算出工作量证明 Map<String,Object> lastBlock =
blockChain.lastBlock(); Long last_proof = Long.parseLong(lastBlock.get("proof")
+""); Long proof = new Proof().ProofOfWork(last_proof);
//奖励计算出工作量证明的矿工1个币的奖励,发送者为"0"表明这是新挖出的矿。 String uuid = (String) this
.getServletContext().getAttribute("uuid"); blockChain.newTransactions("0",uuid,1
);//构建新的区块 Map<String,Object> newBlock = blockChain.newBlock(proof,null);
Map<String, Object> response =new HashMap<String, Object>(); response.put(
"message", "New Block Forged"); response.put("index", newBlock.get("index"));
response.put("transactions", newBlock.get("transactions")); response.put("proof"
, newBlock.get("proof")); response.put("previous_hash", newBlock.get(
"previous_hash")); // 返回新区块的数据给客户端 resp.setContentType("application/json");
PrintWriter printWriter = resp.getWriter(); printWriter.println(new
JSONObject(response)); printWriter.close(); } }
* Consensus 类 ,通过判断不同节点上链的长度,来找出最长链,这就是一致性共识算法。 /** * @Author: cfx *
@Description: 一致性共识算法,解决共识冲突,保证所有的节点都在同一条链上(最长链) * @Date: Created in 2018/5/10
11:38 */ @WebServlet("/nodes/resolve") public class Consensus extends
HttpServlet { @Override protected void doGet(HttpServletRequest req,
HttpServletResponse resp)throws ServletException, IOException { BlockChain
blockChain = BlockChain.getInstance();boolean flag =
blockChain.resolveConflicts(); System.out.println("是否解决一致性共识冲突:" + flag); } }
运行结果

以下是本人之前的测试记录:
首次请求/chain: 初始化Blockchain { "chain": [ { "index": 1, "proof": 100,
"transactions": [], "timestamp": 1526284543591, "previous_hash": "0" } ],
"chainLenth": 1 } 请求/nodes/register,进行网络节点的注册。 request: { "nodes":
"http://lcoalhost:8080" } response: {"message":["All Nodes are:[lcoalhost:8080]"
]} 请求/mine,进行挖矿。 {"index": 2, "proof": 35293, "message": "New Block Forged",
"transactions": [ { "amount": 1, "sender": "0", "recipient":
"e91467fe51bd43b8ad7892b3bc09bd4e" } ], "previous_hash":
"c4b2bb2f6e042680aed249309791cac96da6c1f65b811c306088723ae3c73f66" }
请求/chain,查看链上所有区块的数据 {"chain": [ { "index": 1, "proof": 100, "transactions": [],
"timestamp": 1526284543591, "previous_hash": "0" }, { "index": 2, "proof": 35293
,"transactions": [ { "amount": 1, "sender": "0", "recipient":
"e91467fe51bd43b8ad7892b3bc09bd4e" } ], "timestamp": 1526284661678,
"previous_hash":
"c4b2bb2f6e042680aed249309791cac96da6c1f65b811c306088723ae3c73f66" } ],
"chainLenth": 2 } 请求/transactions/new,新建交易。 request: { "sender":
"d4ee26eee15148ee92c6cd394edd974e", "recipient": "someone-other-address",
"amount": 6 } response: { "message": [ "Transaction will be added to Block 3" ]
} 请求/mine,计算出工作量证明。将上面的交易记录到账本之中。 {"index": 3, "proof": 35089, "message": "New
Block Forged", "transactions": [ { "amount": 6, "sender":
"d4ee26eee15148ee92c6cd394edd974e", "recipient": "someone-other-address" }, {
"amount": 1, "sender": "0", "recipient": "e91467fe51bd43b8ad7892b3bc09bd4e" } ],
"previous_hash":
"a12748a35d57a4a371cefc4a8c294236d69c762d28b889abb2ae34a31d2b7597" }
请求/chain,查看链上所有区块的数据 {"chain": [ { "index": 1, "proof": 100, "transactions": [],
"timestamp": 1526284543591, "previous_hash": "0" }, { "index": 2, "proof": 35293
,"transactions": [ { "amount": 1, "sender": "0", "recipient":
"e91467fe51bd43b8ad7892b3bc09bd4e" } ], "timestamp": 1526284661678,
"previous_hash":
"c4b2bb2f6e042680aed249309791cac96da6c1f65b811c306088723ae3c73f66" }, { "index":
3, "proof": 35089, "transactions": [ { "amount": 6, "sender":
"d4ee26eee15148ee92c6cd394edd974e", "recipient": "someone-other-address" }, {
"amount": 1, "sender": "0", "recipient": "e91467fe51bd43b8ad7892b3bc09bd4e" } ],
"timestamp": 1526284774452, "previous_hash":
"a12748a35d57a4a371cefc4a8c294236d69c762d28b889abb2ae34a31d2b7597" } ],
"chainLenth": 3 }
存在的问题


有一个问题没有解决,就是我们启动多实例来模拟不同的网络节点时,并不能解决节点加入同一个Set的问题,也就是说根本无法通过节点本身来获得其他网络节点,进而判断最长链。所以/nodes/resolve请求暂时时无用的。期间也有想方法解决,比如通过所谓的“第三方”–数据库,当一个节点注册时,保存到数据库中;当第二个节点加入时,也加入到数据库中…当需要请求解决一致性算法时,去数据库中读取节点信息遍历即可。但是,自己没有去实现。这是我的想法,毕竟是两个不相干的实例。如果有朋友有其他的解决方案,请一定要告诉我!谢谢。

总结


通过简单的Demo实现区块链,当然其中简化了大量的实现细节,所以说其实并没有多少实际参考价值。但是意义在于,能帮助我们更容易的理解区块链,为之后的学习打下夯实的基础。

项目源码

Java从零开始创建区块链Demo <https://github.com/mokeychan/blockchainforjava>

参考文章

https://learnblockchain.cn/2017/11/04/bitcoin-pow/
<https://learnblockchain.cn/2017/11/04/bitcoin-pow/>
http://blog.51cto.com/zero01/2086195 <http://blog.51cto.com/zero01/2086195>等。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信