比特币初学者,第3部分:BitCoinJ API

对于Java开发人员,BitCoinJ是开发与比特币网络交互的应用程序的切入点。在这篇由三部分组成的系列文章的最后一篇文章中,Dirk Merkel可以帮助您在Eclipse开发环境中设置BitCoinJ,然后通过几个简短的练习来熟悉比特币事务协议的轻量级实现。

这个由三部分组成的系列的前几部分介绍了比特币的概念和技术框架,虚拟货币点对点网络。本文是BitCoinJ API的教程介绍,假定您熟悉比特币地址,交易,块和块链。

BitCoinJ是比特币协议的开源Java实现。因此,如果您想编写与比特币网络交互的Java应用程序,那么这是一个非常方便的工具。为了探索BitCoinJ API,我们将构建各种示例应用程序,说明在Java中构建更复杂比特币应用程序所需的编程步骤。在使用Maven在Eclipse IDE中构建和设置项目之后,我们将练习创建比特币地址,将其存储在钱包中,并将钱包保存到磁盘。然后,我们将建立与比特币测试网络的连接并检索它的起源区块。最后,我们将通过发送一些比特币到测试网络上的地址,将我们的示例代码绑定到一起。

关于BitCoinJ

BitCoinJ是比特币协议的Java实现。由Mike Hearn撰写,BitCoinJ不是原始比特币客户端的完整实现,而是更轻量级和可访问的版本。BitCoinJ目前仍在开发中(目前为v.0.3),但不应该用于移动大量比特币。

开始使用BitCoinJ

BitCoinJ由谷歌代码托管在Subversion存储库中,可以匿名签出。一旦你检查出BitCoinJ项目的主干,你就可以很容易地保持它的更新。但是,您不会承担任何更改。

您可以使用内置于您最喜欢的IDE中的Subversion客户端,或者直接从命令行检出项目,就像我那样:

图1.从命令行检出BitCoinJ(点击放大)

一旦你有了代码,你将用Maven,BitCoinJ的构建系统编译它。Maven采用生命周期方法构建项目,并且可以通过许多核心和第三方插件高度扩展。Maven做得非常好的是管理依赖关系。如果您查看BitCoinJ根目录中的Maven pom.xml文件,您会发现它只使用少量的依赖关系; 其中包括用于单元测试的JUnit和EasyMock,用于日志记录的SLF4J以及用于加密操作(如散列和签名)的Bouncy Castle Crypto API。

在命令行中,run mvn clean package和Maven将检索这些和其他依赖项,编译项目,运行单元测试套件并将编译后的代码打包到快照JAR文件中。如图2所示,Maven首先执行清理生命周期以摆脱以前版本中的任何工件。然后它执行默认生命周期的阶段直至包括包阶段。

在这个全面的12部分课程中学习从初级概念到高级设计模式的Java!]

图2. Maven编译BitCoinJ及其依赖关系(点击放大)

Maven有一些更有帮助的技巧。首先,执行mvn site:site构建BitCoinJ文档,包括关于依赖性,问题跟踪,邮件列表,许可证,开发团队,源代码库等的页面。这些网页往往是信息性的,但基本。执行mvn javadoc:javadoc生成项目的文档,当我们开始练习BitCoinJ API时,它将派上用场。

如何通过使用'加速'Python来增加价值

BrandPost 由英特尔赞助

如何通过使用'加速'Python来增加价值

文档显示API分为四个包:

  • 发现涉及对等网络发现/通信。
  • 商店包含用于存储块和块链的数据结构。
  • 例子包括一些基于BitCoinJ的简单应用程序(这些启发了我自己的这篇文章的例子)。
  • Core包含BitCoinJ的大部分类和功能,包括与对等节点通信的类,下载块链以及发送和接收事务。

在Eclipse中设置示例项目

我们将在Eclipse中为本文开发示例代码,使用Maven将BitCoinJ作为依赖项进行管理。幸运的是,BitCoinJ拥有一个持续集成环境,可以构建项目,收集并报告各种工件,并将快照JAR存放到项目自己的基于Nexus的Maven存储库中。

图3显示了创建一个新的Maven项目并选择生成基本Maven项目的“quickstart”原型的Eclipse项目创建对话框。我为这个项目编写的代码位于一个名为package的包中com.waferthin.bitcoinj,该包通过Maven构建生成一个0.0.1-SNAPSHOT。

图3.在Eclipse中创建Maven项目(点击放大)

单击完成指示向导创建项目,这意味着将“Hello World”主类放到项目目录中 - src/main/java/com/waferthin/bitcoinj在我的情况下命名。

最后,我们需要告诉Maven项目依赖于BitCoinJ快照,如清单1所示。我编辑了Maven的向导生成的pom.xml文件,以声明BitCoinJ的Nexus存储库的位置和名称(第18行至第28行)并设置版本依赖于版本(第39至45行):

清单1. BitCoinJ项目的Maven pom.xm

001|<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
002|  <modelVersion>4.0.0</modelVersion>
003|
004|  <groupId>com.waferthin.bitcoinj.explored</groupId>
005|  <artifactId>bitcoinj-explored</artifactId>
006|  <version>0.0.1-SNAPSHOT</version>
007|  <packaging>jar</packaging>
008|
009|  <name>bitcoinj-explored</name>
010|  <url>http://maven.apache.org</url>
011|
012|  <properties>
013|    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
014|  </properties>
015|
016|  <repositories>
017|    <!-- declare BitCoinJ repository name and location -->
018|    <repository>
019|      <id>bitcoinj-release</id>
020|      <releases/>
021|      022|<url>http://nexus.bitcoinj.org/content/repositories/releases</url>
023|    </repository>
024|    <repository>
025|      <id>bitcoinj-snapshot</id>
026|      <snapshots/>
027|      <url>http://nexus.bitcoinj.org/content/repositories/snapshots</url>
028|    </repository>
029|  </repositories>
030|
031|  <dependencies>
032|    <dependency>
033|      <groupId>junit</groupId>
034|      <artifactId>junit</artifactId>
035|      <version>3.8.1</version>
036|      <scope>test</scope>
037|    </dependency>
038|
039|    <!-- declare BitCoinJ dependency w/ version -->
040|    <dependency>
041|      <groupId>com.google</groupId>
042|      <artifactId>bitcoinj</artifactId>
043|      <version>0.3-SNAPSHOT</version>
044|      <scope>compile</scope>
045|    </dependency>
046|  </dependencies>
047|</project>

这里的所有都是它的。在下一节中,我们将BitCoinJ类导入到我们的代码中,并使用Maven构建一个BitCoinJ项目,而不必复制实际的JAR文件。

创建一个比特币地址

要发送或接收比特币,您需要一个地址。地址来源于公私密钥对的公共部分(请参阅“ 比特币为初学者,第2部分:比特币为技术和网络 ”)。比特币使用的密码术称为椭圆曲线密码术(ECC)。我们大多数人所知道的公钥密码术是基于找到大整数的主要因素的困难。相反,ECC是基于找到椭圆曲线的离散对数的困难。(更详细地解释这不仅会使我们陷入高等代数的漏洞,而且会很快超过我的大学数学。幸运的是,为了使用BitCoinJ的ECKey类来表示和生成密钥,我们不需要了解更多信息对。)

在清单2的第20行中,我们通过实例化一个类型的对象来创建一个新的椭圆曲线密钥对ECKey。请注意,该类的默认toString()方法将被覆盖,以便在第23行中使用以十六进制表示法返回公钥和私钥。

清单2.使用ECKey创建一个椭圆曲线密钥对

001|package com.waferthin.bitcoinj;
002|
003|import com.google.bitcoin.core.ECKey;
004|import com.google.bitcoin.core.NetworkParameters;
005|import com.google.bitcoin.core.Address;
006|
007|public class CreateAddress {
    
    
008|
009|    public static void main(String[] args) throws Exception {
    
    
010|
011|        // use test net by default
012|        String net = "test";
013|        
014|        if (args.length >= 1 && (args[0].equals("test") || args[0].equals("prod"))) {
    
    
015|            net = args[0];
016|            System.out.println("Using " + net + " network.");
017|        }
018|        
019|        // create a new EC Key ...
020|        ECKey key = new ECKey();
021|
022|        // ... and look at the key pair
023|        System.out.println("We created key:\n" + key);
024|        
025|        // either test or production net are possible
026|        final NetworkParameters netParams;
027|        
028|        if (net.equals("prod")) {
    
    
029|            netParams = NetworkParameters.prodNet();
030|        } else {
    
    
031|            netParams = NetworkParameters.testNet();
032|        }
033|        
034|        // get valid Bitcoin address from public key
035|        Address addressFromKey = key.toAddress(netParams);
036|        
037|        System.out.println("On the " + net + " network, we can use this address:\n" + addressFromKey);
038|    }
039|}

您可能会记得,比特币密钥对的公共部分应该是一个地址。但是,由上述代码生成的密钥的公开部分最初看起来不像比特币客户端在其UI中显示的地址。我们以前在比特币交易中看到的地址表格是通过对公钥进行重复哈希运算得到的。这种形式包括一个标志,表明关键所属的两个比特币网络中的哪一个 - 比特币的生产网络或其测试网络。(请参阅比特币wiki页面,了解比特币密钥对算法创建的更详细说明。)

区分比特币网络

目前有两个比特币网络,一个用于生产,一个用于开发。两个网络都有自己的创世区块和后续的区块链。在本文稍后的部分,我们将使用比特币测试网来执行比特币交易。现在,你只需要知道网络是由分化前挂起一个字节的输入到ECC算法的加密哈希之一:0x6f表示生产网络,并为0x00的测试之一。

我们不需要应用自己的密码哈希序列,因为ECKey类为该toAddress()方法提供了相同的功能。调用该方法并通过NetworkParameters对象传递网络类型后(参见清单2中的第26行),该toAddress()方法返回一个Address对象。该对象的toString()方法将产生一个真正的比特币地址。编译并执行课程后,我会得到比特币测试网络的以下地址:

mpJ9UDd4qtNhMiGefK8NM1V5PMq9jMb7ck

Testnet地址通常以mn开头,而生产地址以1开头。尝试在你自己的机器上执行相同的代码,你会得到一个不同的,唯一的地址。

钱包和钥匙

如果你参与比特币经济,你可能会把所有的财富都留在钱包里。该钱包无非是包含表示所有的比特币交易和未使用的地址的高速缓存序列化对象的本地数据文件的更多。您的收入和离任交易金额的总和就是您的钱包中的比特币数量。在本节中,我们将使用BitCoinJ的Wallet对象来创建一个钱包数据文件,用五个地址填充它,并将其保存到磁盘。

Wallet类实现了Serializable接口,使我们能够坚持它到磁盘或其他一些更持久的存储介质。具体来说,方法loadFromFile(File)和相应的saveToFile(File)读写钱包文件。我们将使用loadFromFile(File)将新创建的钱包对象写入文件。

请注意,BitCoinJ钱包文件与官方比特币客户端创建的钱包文件不兼容。

创建和存储密钥

Wallet类有一个名为公共成员keychain是一个ArrayList类型的ECKey,它是用来存放在钱包的所有EC密钥对。该addKey(ECKey)方法用于添加密钥对,但目前没有用于删除它们的方法。这很有意义,因为用户或程序不应该容易地删除私钥:需要私钥才能访问通过其相应公钥发送的资金。如果没有密钥对或在某处备份,任何发送的资金将永远丢失。

给定一个公钥或它的散列,你可以检查一个匹配的密钥对是否在keychain中,并使用以下便利方法检索它:

findKeyFromPubHash(byte[] pubkeyHash)
isPubKeyHashMine(byte[] pubkeyHash)
findKeyFromPubKey(byte[] pubkey)
isPubKeyMine(byte[] pubkey)

请注意,Wallet该类的唯一构造函数将NetworkParameters对象作为参数。这可确保您无法将来自同一钱包中的生产和测试网络的交易混合在一起。

钥匙,钱包...

在清单3中,我们默认使用testnet(第13行)并声明一个Wallet对象(第16行),稍后需要一个File对象(第17行)将其自身保存到磁盘。在我们的主要try-catch块中,我们然后初始化Wallet对象以使用测试网(第20行)。接下来,我们循环五次,每次创建一个全新的密钥对,并以ECKey对象的形式添加到钱包(第26行)。

在我们的钱包里有东西,然后我们继续将它保存到磁盘(第30行)。如果您现在查看文件系统,则会看到一个名为的文件test.wallet。在第37行,我们抓住钥匙串中的第一个钥匙ArrayList并输出(第40行),然后是整个钱包的转储(第43行)。最后,我们要确定某个特定密钥是否已添加到钱包中,因此我们要求钱包将给定哈希与钱包中公钥的哈希值进行比较(第47行)。

清单3. CreateWallet

001|package com.waferthin.bitcoinj;
002|
003|import java.io.File;
004|import java.io.IOException;
005|
006|import com.google.bitcoin.core.*;
007|
008|public class CreateWallet {
    
    
009|
010|    public static void main(String[] args) {
    
    
011|
012|        // work with testnet
013|        final NetworkParameters netParams = NetworkParameters.testNet();
014|
015|        // Try to read the wallet from storage, create a new one if not possible.
016|        Wallet wallet = null;
017|        final File walletFile = new File("test.wallet");
018|        
019|        try {
    
    
020|            wallet = new Wallet(netParams);
021|            
022|            // 5 times
023|            for (int i = 0; i < 5; i++) {
    
    
024|                
025|                // create a key and add it to the wallet
026|                wallet.addKey(new ECKey());
027|            }
028|            
029|            // save wallet contents to disk
030|            wallet.saveToFile(walletFile);
031|            
032|        } catch (IOException e) {
    
    
033|            System.out.println("Unable to create wallet file.");
034|        }
035|        
036|        // fetch the first key in the wallet directly from the keychain ArrayList
037|        ECKey firstKey = wallet.keychain.get(0);
038|
039|        // output key 
040|        System.out.println("First key in the wallet:\n" + firstKey);
041|        
042|        // and here is the whole wallet
043|        System.out.println("Complete content of the wallet:\n" + wallet);
044|        
045|        // we can use the hash of the public key
046|        // to check whether the key pair is in this wallet
047|        if (wallet.isPubKeyHashMine(firstKey.getPubKeyHash())) {
    
    
048|            System.out.println("Yep, that's my key.");
049|        } else {
    
    
050|            System.out.println("Nope, that key didn't come from this wallet.");
051|        }
052|    }
053|}

这是这个CreateWallet班级产生的输出:

First key in the wallet:
pub:04cf1b56e809dd615663d918824688264d81dbd3642550a82545df5bfa0dfeb5c1e1ba0d97fa278853e121678d13eb2f7e061281957933e9d4bd03893e80e14e0e priv:00e657901ce15ec38c234060e79dd99cda6f1d82ef4b3c90da65c84d6e9ce06a9e
Complete content of the wallet:
Wallet containing 0.00 BTC in:
  0 unspent transactions
  0 spent transactions
  0 pending transactions
  0 inactive transactions
  0 dead transactions

Keys:
  addr:n1QCia54L1MDR4xjQStM48vGnfQf62kh8p pub:04cf1b56e809dd615663d918824688264d81dbd3642550a82545df5bfa0dfeb5c1e1ba0d97fa278853e121678d13eb2f7e061281957933e9d4bd03893e80e14e0e priv:00e657901ce15ec38c234060e79dd99cda6f1d82ef4b3c90da65c84d6e9ce06a9e
  addr:moMovkB3uGTZrf66f5WddcNVhq6MuZUajJ pub:0423a79d67d612e44d06228bc47c9ac1ead8124929280ac827c2aa301075e27909ec2b5502eee7be40e08fba42a7e5c2daa91b1641fe1c0368068f65ea14c27d60 priv:00862ef8769d848ae56eebd211652f72667b353093f5db6139e81b7a787544a039
  addr:mrukZip4D6TG8TxEhhBLkKZvQY4fCPZxZn pub:04c11cdfbc6fbf838a03bd4d49df1957330a4105529054002f87a83d726a632ad7da257c0997aa52fa484382c675d6835f9f78b3c65366a2f36953e992404de63f priv:460343ee1e983fd0ad0ca0023f1b1aa4269ba34c7f17e8463856085669c0bc23
  addr:mgro3Ned1KakmVbP8hPf4iDDo6KDTpCzGL pub:046212ef2216273af27185493de1197ee4a21fef46dee0c27f026b765473c0cd1e1d62ea9f1ea2dbb16103b3c96bb31d78e102b2387ac0b03d67d671226dbb2117 priv:5a42597d63515df1a60ac0090ae55f52e0a8e6ec74e320041cb52605a643c178
  addr:myQqSQpk5mN1e4it1r3dxdyB6Cuu1tWjjo pub:04222d33fb1f89a217804a0400d34b48c08808fbbd22af5e408f04e45597ca3c061ca6dcfe04e4b483c3099caa718e4e5ba6260501b7d24ae9dcfa15844afffe13 priv:00ec4b1abee48a43fe4b12eff1e8fdacabd392554dc12f1cf1156467218cd92189

Yep, that's my key.

您可能已经注意到了该Wallet对象的toString()方法还报告了各类型的交易,如unspentspentpendinginactive,和dead。在本文结束之前,我们将使用钱包来生成实际的交易并将其发送给网络以发送给收件人。

获得创世区块

接下来,我们将连接到比特币网络上的对等节点并向其请求一个块。具体来说,我们将连接到本地运行的比特币节点并检索测试网络的创始块。的成因块是块的块中的链的序列中的第一个。如果您记得,块链是一个类似于链接列表的数据结构,该链接列表包含分层组织的事务散列块以及事务本身。

每个块标题包含一个散列,它是指向块链中前一个块的指针。我们可以使用哈希指针从网络对端请求一个块。这样做可以让我们遍历区块链并检索重建比特币经济完整交易历史所需的所有区块。没有有效散列指针的唯一块是生成块,它没有先前的块。在这个例子中,我们将学习如何通过哈希来检索生成块,但我们可以使用相同的技术来检索任何其他块或遍历块链。

您也可以使用比特币区块浏览器查看测试网的创世区块:http ://blockexplorer.com/testnet/b/0 。

我们可以下载所有以正确顺序构成块链的块,而不是要求节点返回一个块。但在查看代码本身之前,我们需要详细研究几个类。

BlockChain和BlockStore

BlockChain类理解的那种数据结构的嵌段链表示。使用该add(Block)方法,可以将任意数量的块添加到链中,并且BlockChain类将验证块的有效性并尝试将其放置在块链表中的正确位置。随着比特币经济的扩张,区块链将继续延长。客户端下载,存储和访问完整的块链。有了这些对磁盘空间和内存的要求,BitCoinJ的开发人员决定将链中块的实际存储与数据结构和逻辑,即BlockStore接口解耦,这是有道理的。

BlockStore包含四个方法签名,必须由提供实际块存储的任何类实现:put(StoredBlock)get(Sha256Hash)允许开发人员分别添加和检索块。getChainHead()并且setChainHead(StoredBlock)是链中头部最近块的访问方法(如前所述,BitCoinJ并不是比特币协议的完整实现,一个显着的区别是它只存储块头并丢弃底层事务通常是块的一部分。)

目前有三个类实现BlockStore接口并提供块存储。MemoryBlockStore将块头保存在内存中,这是快速和方便的,但不能随着块链增长而持续。DiskBlockStore功能类似MemoryBlockStore,但也可以将标题保存到磁盘。最后,这BoundedOverheadBlockStore是一个旨在用于资源受限环境的实现,为性能可预测性交易不断的资源使用情况。

鉴于我们不会下载整个区块链,MemoryBlockStore对我们的例子来说,这将工作得很好。以上三个BlockStore实现是比特币项目当前包含的实现。然而,其他各种正在开发的产品包括基于SQLite和各种NoSQL的产品。这些通常是荣耀HashMap的,因此非常适合比特币。

窥视

我们需要了解的另一个类是我们的例子Peer。它处理我们的客户端和网络上特定节点之间的高级通信。引擎盖下,这取决于类,如NetworkConnectionBlockBlockChain做其工作。在BitCoinJ的标准使用中,应用程序将连接到任意数量的节点并实例化许多Peer对象来处理连接。在对象connect()上调用该方法后Peer,处理来自其他节点的消息的代码运行在一个循环中,并且需要在一个线程中运行,这将在下一个示例中看到。

从网络对端请求创世区块

鉴于BitCoinJ的内部知识,我们可以快速地通过下一个示例。清单4首先实例化一个代表testnet参数的对象(第19行),并使用它来初始化一个MemoryBlockStore对象(第22行)。我们声明一个BlockChain对象(第25行)并在try-catch块内部初​​始化它(第30行)。该BlockChain对象需要一个NetworkParametersBlockStore对象作为参数。

然后我们实例化一个Peer对象,同时告诉它使用testnet,连接到本地主机上的节点,并使用先前实例化的BlockChain对象进行存储(第33行)。在实际连接到对等节点(第36行)后,我们执行Peer对象的消息处理循环(第39行)。更典型的用例是有多个对等节点连接,在这种情况下,我们可能会Peer在自己的线程中运行每个对象的消息处理循环。因为我们直接处理单个节点,所以在我们的示例中这不是必需的。在比特币块浏览器网站上找到testnet的起源块的哈希值后,我们创建一个Sha256Hash对象(第50行),并要求对等对象向对等节点请求带有相应散列的块(第54行)。在接收到块(第58行)后,我们输出它,这个Block类的覆盖toString()方法做得非常好(第59行)。

清单4. FetchGenesisBlock

001|package com.waferthin.bitcoinj;
002|
003|import java.io.IOException;
004|import java.net.InetAddress;
005|import java.net.UnknownHostException;
006|import java.util.concurrent.ExecutionException;
007|import java.util.concurrent.Future;
008|
009|import com.google.bitcoin.core.*;
010|import com.google.bitcoin.store.BlockStore;
011|import com.google.bitcoin.store.BlockStoreException;
012|import com.google.bitcoin.store.MemoryBlockStore;
013|
014|public class FetchGenesisBlock {
    
    
015|
016|    public static void main(String[] args) {
    
    
017|
018|        // work with testnet
019|        final NetworkParameters netParams = NetworkParameters.testNet();
020|
021|        // data structure for block chain storage
022|        BlockStore blockStore = new MemoryBlockStore(netParams);
023|
024|        // declare object to store and understand block chain
025|        BlockChain chain;
026|        
027|        try {
    
    
028|            
029|            // initialize BlockChain object
030|            chain = new BlockChain(netParams, blockStore);
031|            
032|            // instantiate Peer object to handle connections
033|            final Peer peer = new Peer(netParams, new PeerAddress(InetAddress.getLocalHost()), chain);
034|
035|            // connect to peer node on localhost
036|            peer.connect();
037|            
038|            // run Peer's message handling loop in a thread
039|            new Thread(new Runnable() {
    
    
040|                public void run() {
    
    
041|                    try {
    
    
042|                        peer.run();
043|                    } catch (PeerException e) {
    
    
044|                        throw new RuntimeException(e);
045|                    }
046|                }
047|            }).start();
048|
049|            // we found the hash of the genesis block on Bitcoin Block Explorer 
050|            Sha256Hash blockHash = new Sha256Hash("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008");
051|            
052|            // ask the node to which we're connected for the block
053|            // and wait for a response
054|            Future<Block> future = peer.getBlock(blockHash);
055|            System.out.println("Waiting for node to send us the requested block: " + blockHash);
056|            
057|            // get and use the Block's toString() to output the genesis block
058|            Block block = future.get();
059|            System.out.println("Here is the genesis block:\n" + block);
060|            
061|            // we're done; disconnect from the peer node
062|            peer.disconnect();
063|
064|        // handle the various exceptions; this needs more work
065|        } catch (BlockStoreException e) {
    
    
066|            e.printStackTrace();
067|        } catch (UnknownHostException e) {
    
    
068|            e.printStackTrace();
069|        } catch (PeerException e) {
    
    
070|            e.printStackTrace();
071|        } catch (IOException e) {
    
    
072|            e.printStackTrace();
073|        } catch (InterruptedException e) {
    
    
074|            e.printStackTrace();
075|        } catch (ExecutionException e) {
    
    
076|            e.printStackTrace();
077|        }
078|    }
079|}

以下是运行我们的示例生成的输出。

Waiting for node to send us the requested block: 00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008
Here is the genesis block:
v1 block: 
   previous block: 0000000000000000000000000000000000000000000000000000000000000000
   merkle root: 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
   time: [1296688602] Wed Feb 02 15:16:42 PST 2011
   difficulty target (nBits): 487063544
   nonce: 384568319
   with 1 transaction(s):
     == COINBASE TXN (scriptSig [4]ffff001d ?(4) [69]5468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73 )  (scriptPubKey [65]04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f CHECKSIG )

发送比特币

对于我们最后的练习,我们将创建一个小应用程序,将比特币从我们的钱包发送到比特币地址。对于这个任务,我们将使用这个Wallet类的sendCoins(Peer peer, Address to, BigInteger nanocoins)方法。

如果你想试验测试网络,你可以从比特币水龙头免费获得比特币

与其他示例不同,这次我们将从命令行获取所有输入。我们的应用程序将按照给定的顺序需要以下参数:

  1. 哪个网络(“测试”或“产品”)
  2. 包含比特币的钱包文件的名称
  3. 要发送的纳米比特币的数量
  4. 比特币地址的收件人

在清单5中,我们处理命令行参数(线18至29)并初始化我们以前看过的各种组件,包括的实例NetworkParametersBlockChainWallet,和Peer类。发送的金额被指定为a BigInteger,其中每个单位代表比特币的1/1000000th(第59行)。我们Address基于标准客户端提供的比特币地址实例化对象(第71行)。然后我们调用Wallet该类的sendCoins()方法来启动事务(第74行)。返回值为null表示钱包中没有足够的资金用于请求的交易。否则,我们会在比特币区块资源管理器网站上输出确认信息并链接到我们的发送交易。

清单5. SendCoins

001|package com.waferthin.bitcoinj;
002|
003|import java.io.File;
004|import java.io.IOException;
005|import java.math.BigInteger;
006|import java.net.InetAddress;
007|import java.net.UnknownHostException;
008|
009|import com.google.bitcoin.core.*;
010|import com.google.bitcoin.store.BlockStore;
011|import com.google.bitcoin.store.BlockStoreException;
012|import com.google.bitcoin.store.MemoryBlockStore;
013|
014|public class SendCoins {
    
    
015|
016|    public static void main(String[] args) {
    
    
017|
018|        if (args.length != 4) {
    
    
019|            System.out.println("Usage: java SendCoins prod|test wallet amount recipient");
020|            System.exit(1);
021|        }
022|        
023|        // we get the following from the command line ...
024|        // (this is not secure - needs validation)
025|        String network          = args[0];  // "test" or "prod"
026|        String walletFileName   = args[1];  // wallet file name
027|        String amountToSend     = args[2];  // milli-BTC
028|        String recipient        = args[3];  // Bitcoin address
029|
030|        // the Bitcoin network to use
031|        final NetworkParameters netParams;
032|        
033|        // check for production Bitcoin network ...
034|        if (network.equalsIgnoreCase("prod")) {
    
    
035|            netParams = NetworkParameters.prodNet();
036|        // ... otherwise use the testnet
037|        } else {
    
    
038|            netParams = NetworkParameters.testNet();
039|        }
040|
041|        // data structure for block chain storage
042|        BlockStore blockStore = new MemoryBlockStore(netParams);
043|
044|        // declare object to store and understand block chain
045|        BlockChain chain;
046|        
047|        // declare wallet
048|        Wallet wallet;
049|        
050|        try {
    
    
051|            
052|            // wallet file that contains Bitcoins we can send
053|            final File walletFile = new File(walletFileName);
054|
055|            // load wallet from file
056|            wallet = Wallet.loadFromFile(walletFile);
057|            
058|            // how man milli-Bitcoins to send
059|            BigInteger btcToSend = new BigInteger(amountToSend);
060|                    
061|            // initialize BlockChain object
062|            chain = new BlockChain(netParams, wallet, blockStore);
063|
064|            // instantiate Peer object to handle connections
065|            final Peer peer = new Peer(netParams, new PeerAddress(InetAddress.getLocalHost()), chain);
066|
067|            // connect to peer node on localhost
068|            peer.connect();
069|            
070|            // recipient address provided by official Bitcoin client
071|            Address recipientAddress = new Address(netParams, recipient);
072|
073|            // tell peer to send amountToSend to recipientAddress
074|            Transaction sendTxn = wallet.sendCoins(peer, recipientAddress, btcToSend);
075|            
076|            // null means we didn't have enough Bitcoins in our wallet for the transaction
077|            if (sendTxn == null) {
    
    
078|                System.out.println("Cannot send requested amount of " + Utils.bitcoinValueToFriendlyString(btcToSend)
079|                                + " BTC; wallet only contains " + Utils.bitcoinValueToFriendlyString(wallet.getBalance()) + " BTC.");
080|            } else {
    
    
081|                
081|                // once communicated to the network (via our local peer),
083|                // the transaction will appear on Bitcoin explorer sooner or later
084|                System.out.println(Utils.bitcoinValueToFriendlyString(btcToSend) + " BTC sent. You can monitor the transaction here:\n"
085|                                + "http://blockexplorer.com/tx/" + sendTxn.getHashAsString());
086|            }
087|
088|            // save wallet with new transaction(s)
089|            wallet.saveToFile(walletFile);
090|
091|        // handle the various exceptions; this needs more work
092|        } catch (BlockStoreException e) {
    
    
093|            e.printStackTrace();
094|        } catch (UnknownHostException e) {
    
    
095|            e.printStackTrace();
096|        } catch (PeerException e) {
    
    
097|            e.printStackTrace();
098|        } catch (AddressFormatException e) {
    
    
099|            e.printStackTrace();
100|        } catch (IOException e) {
    
    
101|            e.printStackTrace();
102|        }
103|    }
104|}

以下是运行命令行的输出java com.waferthin.bitcoinj.SendCoins prod sendcoins-prodnet.wallet 2000000 13RwREqwoNhVkcbpKSUMg9Ea6JnNuy85Qz::

0.02 BTC sent. You can monitor the transaction here:
http://blockexplorer.com/tx/078e0fc3577a039afac327c6509aa0e0cc680a51011ebb67486b5fc05e28a139

图4显示了在收件人地址收到的硬币。

图4.收到硬币(点击放大)

几分钟后,我们可以在应用程序提供的Bitcoin Block Explorer链接上在线查看交易:

图5.使用Bitcoin Block Explorer查看交易(点击放大)

结论是

这就是我们对BitCoinJ项目的旋风之旅。本文中还没有涉及很多功能,但对BitCoinJ提供的基本组件有深入的了解后,您应该可以填写空白。一个值得的后续练习是下载整个块链,并在输出关于每个块的一些信息时迭代它。另外,弄清楚如何接收比特币将是另一个有益的练习。

希望您可以将本文中学到的知识作为创建更复杂应用程序的起点。也许你甚至可以开始贡献BitCoinJ项目本身

如果您对bitcoinj交易记录接收也比较感兴趣,或者有其它疑问可以发我邮件:[email protected]

此项目下载地址及及demo:https://download.csdn.net/download/weixin_39842528/10656189

技术交流个人微信:

猜你喜欢

转载自blog.csdn.net/weixin_39842528/article/details/80633944