主页 > 最新imtoken官方下载 > 编程小白模拟一个简单的比特币系统,带你写一波! (带代码) |精选博客

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

最新imtoken官方下载 2023-01-17 16:25:54

制作 |区块链营(blockchain_camp)

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

如果有p2p的demo,我们如何应用到区块链当中?

今天就试试吧!

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

@ >

合并流程

我们梳理一下整个流程,明确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的大致流程如下,我们后面的实现会结合这个流程。

客户端→服务器发送消息,通常是请求数据

服务器收到消息后,向客户端发送消息(调用服务,处理后返回数据)

客户端接收消息并处理数据(调用服务处理数据)

相关代码

在执行过程中,由于消息的种类很多。它封装了一个消息对象来传递消息,对消息类型进行编码,统一处理。消息对象Message实现了Serializable接口,使其对象可序列化:

public class Message implements Serializable {/**     * 消息内容,就是我们的区块链、交易池等所需要的信息,使用JSON.toString转化到的json字符串     */private String data;/**     * 消息类型     */private int 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;

因为代码太多,这里就不一一贴了。以客户端同步其他节点的钱包信息为例,结合以上p2p网络交互的三个步骤,介绍一下相关实现。

1、客户端→服务器发送消息,通常是请求数据

在客户端节点的启动类中,首先创建客户端对象,调用客户端内部方法,连接服务端。

启动类main方法中的关键代码,(端口参数在args中配置):

P2PClient p2PClient = new P2PClient();String url = "ws://localhost:"+args[0]+"/test";       p2PClient.connectToPeer(url);

P2PClient中的connectToPeer方法

p>

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时回调。假设我们只查询钱包公钥信息,那么服务器就会收到相应的请求。

@OnOpenpublic void onOpen(Session session) {this.session = session;    p2PService.sendMsg(session, p2PService.queryWalletMsg());}

挖比特币程序

注意:我把解析消息相关的操作封装成一个服务,方便服务端和客户端统一使用。给出对应的queryWalletMsg方法:

public String queryWalletMsg() {return JSON.toJSONString(new Message(QUERY_WALLET));}

还有前面提到的sendMsg方法:

@Overridepublic void sendMsg(Session session, String msg) {session.getAsyncRemote().sendText(msg);}

2、服务器收到消息后,向客户端发送消息(调用服务,处理后返回数据)

服务器收到消息,进入P2PServer中的OnMessage方法

/** * 收到客户端发来消息 * @param msg  消息对象 */@OnMessagepublic void onMessage(Session session, String msg) {    p2PService.handleMessage(session, msg);}

p2PService.handleMessage就是解析接收到的消息(msg),根据类型调用其他方法(一个巨大的switch语句,这里介绍一小部分),这里我们从客户端收到信息码QUERY_WALLET。

@Overridepublic 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));}

这里我将区块链的相关操作封装成一个服务。下面给出findAllWallets的具体实现,其实就是遍历钱包集合,统计钱包公钥,没什么难度。

挖比特币程序

@Overridepublic 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);}

获取数据后,返回给客户端:

所以在我们的responseWallets()方法中挖比特币程序,最后一句新建了一个消息对象,并将信息码设置为RESPONSE_WALLET,并在handleMessage中调用sendmsg方法返回给客户端。

case QUERY_WALLET:        sendMsg(session, responseWallets());        break;

3、客户端接收消息并处理数据(调用服务处理数据)

客户端收到请求并获取数据,进入P2PClient中的OnMessage方法

@OnMessagepublic void onMessage(String msg) {    p2PService.handleMessage(this.session, msg);}

同样进入我们上面提到的p2PService.handleMessage方法,此时收到的信息码是RESPONSE_WALLET,进入handleWalletResponse方法

case RESPONSE_WALLET:        handleWalletResponse(message.getData());        break;

handleWalletResponse的实现,解析接收到的钱包公钥信息,存储在客户端节点的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 );    });}

在具体实现中,由于注入服务的方式,在服务端(@ServerEndpoint)和客户端(@ClientEndpoint)都使用了@Autowired注解。在注入bean时,由于Spring boot单例的特性,而websocket每次都会创建一个新对象,在使用服务的时候会导致空指针异常。因此,我们创建了一个工具类 Springtil,每次我们需要服务的时候,从 Spring 容器中获取我们需要的 bean。工具类代码如下。

挖比特币程序

public class SpringUtil implements ApplicationContextAware {public static ApplicationContext applicationContext;    @Overridepublic 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 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 为 null    p2PService = SpringUtil.getBean(P2PService.class);//新增这句话从IVO容器中获取bean    p2PService.sendMsg(session, p2PService.queryWalletMsg());}

你好节点,测试作为服务器

测试节点,并在测试期间作为客户端。

至此,我们已经实现了p2p网络中服务端节点和客户端节点的交互过程。我建议你尝试一下,并在评论中与我们讨论!

“原力计划[第二季]-学习挑战”正式开始!

即日起至3月21日,千万流量支持原作者,更有专属【勋章】等你挑战