主页 > imtoken钱包安卓版手机下载 > 编程小白模拟一个简单的比特币系统,带你写一波! (带代码)

编程小白模拟一个简单的比特币系统,带你写一波! (带代码)

出品 | 区块链训练营(blockchain_camp)

封面 | 视觉中国CSDN付费下载

如果有P2P Demo,我们如何将其应用于区块链?

今天就来试试吧!

首先,我们需要模拟网络中的多个节点相互通信。 我们假设现在的情况是有两个节点AB,整个过程如下图所示:

案例分析比特币交易的风险及其成因_比特币模拟交易平台_比特币模拟交易

比特币模拟交易_案例分析比特币交易的风险及其成因_比特币模拟交易平台

梳理流程

下面把整个过程梳理一下,弄清楚在P2P网络中需要做什么。

启动节点A。A首先创建一个创世块

创建钱包A1。 调用A节点提供的API创建钱包。 此时A1的球币为0。

A1挖矿。 调用节点A提供的挖矿API生成新区块,同时有系统奖励给A1钱包的球币。

比特币模拟交易_案例分析比特币交易的风险及其成因_比特币模拟交易平台

启动节点B,节点B需要和A同步信息,当前区块链,当前交易池,当前所有钱包的公钥。 创建钱包B1和A2,调用节点A和B的API,广播(通知各节点)创建的钱包(公钥)。 目前只有两个节点比特币模拟交易,所以A需要告诉B,A2的钱包。 B需要告诉A,B1的钱包。 A1 转账给 B1。 调用A提供的API,同时广播交易。 A2矿业会计。 调用A提供的API,同时广播新生成的区块。

综上所述,节点刚刚加入区块链网络,需要同步其他节点的数据。

已经在网络中的节点在以下情况下需要通知网络中的其他节点

P2P的大致流程是以下几点,我们后面的实现会结合这个流程。

client→server发送消息,一般是请求数据; 服务端收到消息后,向客户端发送消息(调用服务,处理后返回数据); 客户端接收消息并处理数据(调用服务,处理数据)。

案例分析比特币交易的风险及其成因_比特币模拟交易平台_比特币模拟交易

相关代码

在实现过程中,由于消息类型较多,封装了一个消息对象来传输消息,并对消息类型进行编码,统一处理。 消息对象 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中,onOpen函数会在WebSocketContainer.connectToServer时被回调。 假设我们只查询钱包公钥信息,此时服务器会收到相应的请求。

@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、Server收到消息后,向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 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.客户端接收消息并处理数据(调用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 wallets = "\"\"".equals(msg)?new ArrayList<>:JSON.parseArray(msg, Wallet.class);
wallets.forEach(wallet -> {
blockService.addOtherWallet(walletService.getWalletAddress(wallet.getPublicKey),wallet );
});
}

在具体实现中,由于使用了注入服务的方式,在使用@Autowired注解向Server(@ServerEndpoint)和Client(@ClientEndpoint)注入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 != ) {
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 clazz) {
return getApplicationContext.getBean(clazz);
}

/**
* 通过name,以及Clazz返回指定的Bean
*/
public static T getBean(String name, Class 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 为  p2PService = SpringUtil.getBean(P2PService.class);//新增这句话从IVO容器中获取bean p2PService.sendMsg(session, p2PService.queryWalletMsg);}

你好节点,在测试期间作为服务器:

案例分析比特币交易的风险及其成因_比特币模拟交易_比特币模拟交易平台

测试节点在测试期间充当客户端。

比特币模拟交易_比特币模拟交易平台_案例分析比特币交易的风险及其成因

至此,我们就实现了P2P网络中Server节点和Client节点的交互过程。 建议大家试试看,在评论区和我们一起讨论吧!

☞不用掉一根头发! 使用 Flutter + Dart 快速构建漂亮的移动应用

☞看我发现了什么好东西? Java 可选,绝对值得学习 | 原力计划

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

☞我最喜欢的云端IDE推荐!

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

☞返鄂工人自述:回武汉上班,先飞合肥,公司包车接他们