概述
前言
为了更好的理解区块链的底层实现原理,决定自己动手模拟实现一条区块链。
思路分析
通过之前的学习,从文本知识的角度,我们知道,创世区块、记账原理、挖矿原理、工作量证明、共识机制等等区块链的相关知识。
创建一条区块链,首先默认构造创世区块。在此基础上,我们可以发布交易,并进行挖矿,计算出工作量证明,将交易记录到区块中,每成功的挖一次矿,块高就+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://learnblockchain.cn/2017/11/04/bitcoin-pow/
http://blog.51cto.com/zero01/2086195等。
最后
以上就是精明枫叶为你收集整理的Java搭建区块链的全部内容,希望文章能够帮你解决Java搭建区块链所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复