编程小白模拟简易比特币系统,手把手带你写一波!(附代码)

2020 年 3 月 15 日 CSDN

作者 | VV一笑ヽ
责编 | Carol
出品 | 区块链大本营(blockchain_camp)
封面 | CSDN 付费下载于视觉中国
如果有一个 P2P的D emo,我们要怎么才能应用到区块链当中?
今天就来一起尝试一下吧!
首先,我们需要模拟网络中的多个节点相互通讯,我们假设现在的情况是有AB两个节点整个过程如下图所示:

梳理流程

让我们来梳理一下整个流程,明确在P2P网络中需要做的事情。
  1. 启动节点A。A首先创建一个创世区块
  2. 创建钱包A1。调用节点A提供的API创建一个钱包,此时A1的球球币为0。
  3. A1挖矿。调用节点A提供的挖矿API,生成新的区块,同时为A1的钱包有了系统奖励的球球币。
  4. 启动节点B。节点B要向A同步信息,当前的区块链,当前的交易池,当前的所有钱包的公钥。
  5. 创建钱包B1、A2,调用节点A和B的API,要广播(通知每一个节点)出去创建的钱包(公钥),目前节点只有两个,因此A需要告诉B,A2的钱包。B需要告诉A,B1的钱包。
  6. A1转账给B1。调用A提供的API,同时广播交易
  7. A2挖矿记账。调用A提供的API,同时广播新生成的区块
总结一下,就是节点刚开始加入到区块链网络中,需要同步其他节点的
  • 区块链信息
  • 钱包信息
  • 交易信息
已经处于网络中的某个节点,在下述情况下需要通知网络中的其他节点
  • 发生新的交易
  • 创建新的钱包
  • 挖矿产生新区块
P2P的大致流程为下方几点,我们后边的实现会结合这个过程。
  1. client→server 发送消息,一般是请求数据;
  2. server收到消息后,向client发送消息 (调用service,处理后返回数据);
  3. client收到消息处理数据(调用service,对数据处理)。

相关代码

在实现的过程中,由于消息类型较多,封装了一个消息对象用来传输消息,对消息类型进行编码,统一处理,消息对象Message,实现了Serializable接口,使其对象可序列化:
   
   
     
public  class Message implements Serializable {
/**
     * 消息内容,就是我们的区块链、交易池等所需要的信息,使用JSON.toString转化到的json字符串
     */

private String data;
/**
     * 消息类型
     */

private int type;
}
涉及到的消息类型(Type)有:
   
   
     
/**
 * 查询最新的区块
 */

private final  static  int QUERY_LATEST_BLOCK =  0;
/**
 * 查询整个区块链
 */

private final  static  int QUERY_BLOCK_CHAIN =  1;
/**
 * 查询交易集合
 */

private final  static  int QUERY_TRANSACTION =  2;
/**
 * 查询已打包的交易集合
 */

private final  static  int QUERY_PACKED_TRANSACTION =  3;
/**
 * 查询钱包集合
 */

private final  static  int QUERY_WALLET =  4;
/**
 * 返回区块集合
 */

private final  static  int RESPONSE_BLOCK_CHAIN =  5;
/**
 * 返回交易集合
 */

private final  static  int RESPONSE_TRANSACTION =  6;
/**
 * 返回已打包交易集合
 */

private final  static  int RESPONSE_PACKED_TRANSACTION =  7;
/**
 * 返回钱包集合
 */

private final  static  int RESPONSE_WALLET =  8;
由于代码太多,就不全部粘在这里了,以Client同步其他节点钱包信息为例,结合上面的 P2P 网络交互的三个步骤,为大家介绍下相关的实现。
1、client→server 发送消息,一般是请求数据
在Client节点的启动类首先创建Client对象,调用Client内部方法,连接Server。
启动类Main方法中关键代码,(端口参数配置在Args中):
   
   
     
P2PClient p2PClient =  new P2PClient();
String url =  "ws://localhost:"+args[ 0]+ "/test";       
p2PClient.connectToPeer(url);
P2PClient 中的 connectToPeer 方法:
   
   
     
public void connectToPeer(String url) throws IOException, DeploymentException {
    WebSocketContainer container = ContainerProvider.getWebSocketContainer();
    URI uri = URI.create(url);
this.session = container.connectToServer(P2PClient.class, uri);
}
P2PClient 中, WebSocketContainer.connectToServer 的时候会回调 onOpen 函数,假设我们只查询钱包公钥信息,此时服务端会接收到相应的请求。
   
   
     
@OnOpen
public void onOpen(Session session) {
this.session = session;
    p2PService.sendMsg(session, p2PService.queryWalletMsg());
}
注意:我把解析消息相关的操作封装到了一个Service 中,方便Server和Client的统一使用。给出相应的 queryWalletMsg 方法:
   
   
     
public String queryWalletMsg() {
return JSON.toJSONString( new Message(QUERY_WALLET));
}
以及之前提到的 sendMsg 方法:
   
   
     
@ Override
public void sendMsg(Session session, String msg) {
session .getAsyncRemote() .sendText( msg);
}
2、S erver收到消息后,向Client发送消息(调用Service,处理后返回数据
Server收到消息,进入 P2PServer OnMessage 方法
   
   
     
/**
 * 收到客户端发来消息
 * @param msg  消息对象
 */

@OnMessage
public void onMessage(Session session, String msg) {
    p2PService.handleMessage(session, msg);
}
p2PService.handleMessage 就是解析接收到的消息(Msg),根据类型的不同调用其他的方法(一个巨型Switch语句,这里就介绍一小部分),这里我们接收到了Client传来的信息码 QUERY_WALLET
   
   
     
@Override
public void handleMessage(Session session, String msg) {
    Message message = JSON.parseObject(msg, Message.class);
switch (message.getType()){
case QUERY_WALLET:
            sendMsg(session, responseWallets());
break;
case RESPONSE_WALLET:
            handleWalletResponse(message.getData());
break;
            ......
    }
根据信息码是 QUERY_WALLET ,调用  responseWallets() 方法,得到数据
   
   
     
private  String responseWallets() {
String wallets = blockService.findAllWallets();
return  JSON.toJSONString( new Message(RESPONSE_WALLET, wallets));
}
这里我把区块链的相关操作也封装到了一个Service中,下面给出 findAllWallets 的具体实现,其实就是遍历钱包集合,统计钱包公钥,没有什么难度。
   
   
     
@Override
public String findAllWallets() {
    List<Wallet> wallets =  new ArrayList<>();
    myWalletMap.forEach( (address, wallet) ->{
        wallets.add(Wallet.builder().publicKey(wallet.getPublicKey()).build());
    });
    otherWalletMap.forEach( (address, wallet) ->{
        wallets.add(wallet);
    });
return JSON.toJSONString(wallets);
}
得到数据之后,返回给Client:
因此我们的  responseWallets() 方法中,最后一句话新建了一个Message对象,并设置了信息码为 RESPONSE_WALLET ,在 handleMessage 中调用了 sendmsg 方法回传给Client。
   
   
     
case QUERY_WALLET:
        sendMsg(session, responseWallets());
         break;
3、 Clien t收到消息处理数据(调用Service,对数据处理)
Client收到了请求得到的数据,进入 P2PClient 中的 OnMessage 方法:
   
   
     
@OnMessage
public void onMessage(String msg) {
    p2PService.handleMessage( this.session, msg);
}
同样进入我们上面提到的 p2PService.handleMessage 方法,此时收到的信息码为 RESPONSE_WALLET ,进入 handleWalletResponse 方法:
   
   
     
case RESPONSE_WALLET:
        handleWalletResponse( message.getData());
         break;
handleWalletResponse 的实现, 解析接收到的钱包公钥信息,并存储到Client节点的 blockService 中。
   
   
     
private void handleWalletResponse(String msg{
    List<Wallet> wallets =  "\"[]\"". equals(msg)? new ArrayList<>():JSON.parseArray(msg, Wallet.class);
    wallets.forEach(wallet -> {
        blockService.addOtherWallet(walletService.getWalletAddress(wallet.getPublicKey()),wallet );
    });
}
在具体实现中,由于使用到了注入服务的方式,在向Server(@ServerEndpoint)和Client(@ClientEndpoint)中使用@Autowired 注解注入Bean的时候,由于Spring Boot单例的特点。
而Websocket每次都会创建一个新的对象,所以在使用服务的时候会导致出现空指针异常,因此,我们创建了一个工具类Spring til,每次需要服务时,都从Spring容器中获取到我们所需要的Bean,下面给出工具类代码。
   
   
     
public  class  SpringUtil  implements  ApplicationContextAware {
public  static ApplicationContext applicationContext;
    @ Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
{
if (SpringUtil.applicationContext !=  null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }
/**
     * 获取applicationContext
     */

public static ApplicationContext getApplicationContext() {
return applicationContext;
    }

/**
     * 通过name获取 Bean.
     */

public static Object getBean(String name{
return getApplicationContext().getBean(name);
    }
/**
     * 通过class获取Bean.
     */

public  static <T>  getBean(Class<T> clazz{
return getApplicationContext().getBean(clazz);
    }

/**
     * 通过name,以及Clazz返回指定的Bean
     */

public  static <T>  getBean(String name, Class<T> clazz{
return getApplicationContext().getBean(name, clazz);
    }
}
因此测试之前我们首先需要设定 SpringUtil 中的 applicationContext ,下面给出启动类(为了简单测试,两个节点共用一个启动类,根据Args的不同来分别处理)以及相关节点的配置。
   
   
     
public static void main(String[] args{
    System. out.println( "Hello world");
    SpringUtil.applicationContext  = SpringApplication.run(Hello.class, args);
if (args.length> 0){
        P2PClient p2PClient =  new P2PClient();
        String url =  "ws://localhost:"+args[ 0]+ "/test";
try {
            p2PClient.connectToPeer(url);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
使用时,我们需要手动获取Bean
   
   
     
//之前是这样 //@Autowired //private P2PService p2PService; //改正后,去掉Autowired,每次使用都手动获取beanprivate P2PService p2PService;@OnOpenpublic void onOpen(Session session) { //如果不使用那些,在这里会报空指针异常,p2PService 为  null p2PService = SpringUtil.getBean(P2PService.class); //新增这句话从IVO容器中获取bean p2PService.sendMsg(session, p2PService.queryWalletMsg());}
Hello节点,测试时作为Server:
Test节点,测试时作为Client。
到此,我们就实现了P2P网络中Server节点与Client节点的交互过程。建议你也可以尝试一下,然后在评论区和我们讨论哦!
推荐阅读 

不用掉一根头发!用 Flutter + Dart 快速构建一款绝美移动 App

看我发现了什么好东西?Java Optional,绝对值得一学 | 原力计划

腾讯提结合ACNet进行细粒度分类,效果达到最新SOTA | CVPR 2020

我最喜欢的云 IDE 推荐!

智能合约编写之Solidity的高级特性

返鄂复工人员自述:回武汉上班,要先飞合肥,再由公司包车接回去

你点的每一个在看,我认真当成了喜欢
登录查看更多
0

相关内容

区块链(Blockchain)是由节点参与的分布式数据库系统,它的特点是不可更改,不可伪造,也可以将其理解为账簿系统(ledger)。它是比特币的一个重要概念,完整比特币区块链的副本,记录了其代币(token)的每一笔交易。通过这些信息,我们可以找到每一个地址,在历史上任何一点所拥有的价值。

知识荟萃

精品入门和进阶教程、论文和代码整理等

更多

查看相关VIP内容、论文、资讯等
【2020新书】实战R语言4,323页pdf
专知会员服务
98+阅读 · 2020年7月1日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
229+阅读 · 2020年5月21日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
160+阅读 · 2020年5月14日
必读的10篇 CVPR 2019【生成对抗网络】相关论文和代码
专知会员服务
31+阅读 · 2020年1月10日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
资源|Blockchain区块链中文资源阅读列表
专知会员服务
43+阅读 · 2019年11月20日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
20+阅读 · 2019年11月7日
用 Python 开发 Excel 宏脚本的神器
私募工场
26+阅读 · 2019年9月8日
手把手教你入门深度强化学习(附链接&代码)
THU数据派
75+阅读 · 2019年7月16日
手把手教你用Python做一个哄女友神器,小白可上手
网易智能菌
5+阅读 · 2019年6月15日
手把手教你用Python实现“坦克大战”,附详细代码!
机器学习算法与Python学习
11+阅读 · 2019年6月8日
浅显易懂的分布式TensorFlow入门教程
专知
7+阅读 · 2018年6月22日
手把手教你用Python创建微信聊天机器人
新智元
4+阅读 · 2018年3月14日
Python3爬虫之入门和正则表达式
全球人工智能
7+阅读 · 2017年10月9日
Teacher-Student Training for Robust Tacotron-based TTS
Arxiv
3+阅读 · 2018年10月8日
Arxiv
3+阅读 · 2017年11月20日
VIP会员
相关VIP内容
【2020新书】实战R语言4,323页pdf
专知会员服务
98+阅读 · 2020年7月1日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
229+阅读 · 2020年5月21日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
160+阅读 · 2020年5月14日
必读的10篇 CVPR 2019【生成对抗网络】相关论文和代码
专知会员服务
31+阅读 · 2020年1月10日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
资源|Blockchain区块链中文资源阅读列表
专知会员服务
43+阅读 · 2019年11月20日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
20+阅读 · 2019年11月7日
相关资讯
用 Python 开发 Excel 宏脚本的神器
私募工场
26+阅读 · 2019年9月8日
手把手教你入门深度强化学习(附链接&代码)
THU数据派
75+阅读 · 2019年7月16日
手把手教你用Python做一个哄女友神器,小白可上手
网易智能菌
5+阅读 · 2019年6月15日
手把手教你用Python实现“坦克大战”,附详细代码!
机器学习算法与Python学习
11+阅读 · 2019年6月8日
浅显易懂的分布式TensorFlow入门教程
专知
7+阅读 · 2018年6月22日
手把手教你用Python创建微信聊天机器人
新智元
4+阅读 · 2018年3月14日
Python3爬虫之入门和正则表达式
全球人工智能
7+阅读 · 2017年10月9日
Top
微信扫码咨询专知VIP会员