java開發比特幣類庫bitcoinj入門指南
bitcoinj是一個使用比特幣協議的庫。它可以維護錢包,傳送/接收交易而無需比特幣核心的本地副本,並具有許多其他高階功能。它是用Java實現的,但可以通過任何JVM相容語言中使用:包括Python和JavaScript中的示例。
它附帶完整的文件,並建立了許多大型,眾所周知的比特幣應用程式和服務。下面我們來看看如何使用它。
初始設定
bitcoinj內建了日誌記錄和斷言。無論是否指定了-ea標誌,都會預設檢查斷言。記錄由slf4j.org/" target="_blank" rel="nofollow,noindex">SLF4J
庫處理。它允許你選擇你更喜歡使用的日誌系統,例如JDK日誌記錄,Android日誌記錄等。預設情況下,我們使用簡單的logger來列印stderr
感興趣的大部分內容。你可以通過切換lib目錄中的jar檔案來選擇一個新的logger。
bitcoinj使用Maven作為其構建系統,並通過git分發。你可以使用原始碼/ jar下載,但直接從源儲存庫獲取它更安全。
要獲取程式碼並安裝它,請抓取Maven 或Gradle ,並將其新增到你的路徑中。還要確保安裝了git。可能你的Java IDE也有一些Maven/Gradle和Git整合,但是通過命令列使用它們還是非常有用。
現在獲取最新版本的程式碼。你可以使用使用Maven 或使用Gradle 頁面上的說明——只需在那裡執行命令,你就可以獲得正確的程式碼版本(除非此網站本身已被洩露)。這是為了防止受損映象或原始碼下載——因為git使用源樹雜湊工作,如果以正確的方式獲得源雜湊,則可以保證最終得到正確的程式碼。
你可以在這裡 閱讀完整的程式。
基本結構
bitcoinj應用程式使用以下物件:
-
NetworkParameters
例項,用於選擇你所在的網路(生產或測試)。 -
用於儲存
ECKeys
和其他資料的Wallet
例項。 -
用於管理網路連線的
PeerGroup
例項。 -
一個
BlockChain
例項,它管理共享的全域性資料結構,使比特幣工作。 -
一個
BlockStore
例項,它將塊鏈資料結構儲存在某個位置,就像在磁碟上一樣。 - WalletEventListener實現,用於接收錢包交易。
為了簡化設定,還有一個WalletAppKit
物件可以建立上述物件並將它們連線在一起。雖然可以手動執行此操作(對於大多數“真實”應用程式),但此演示應用程式會顯示如何使用應用程式工具包。
讓我們看看程式碼,看看它是如何工作的。
設定
我們使用實用程式函式將log4j配置為具有更緊湊,更簡潔的日誌格式。然後我們檢查命令列引數。
BriefLogFormatter.init(); if (args.length < 2) { System.err.println("Usage: address-to-send-back-to [regtest|testnet]"); return; }
然後我們根據可選的命令列引數選擇我們將要使用的網路:
// Figure out which network we should connect to. Each one gets its own set of files. NetworkParameters params; String filePrefix; if (args[1].equals("testnet")) { params = TestNet3Params.get(); filePrefix = "forwarding-service-testnet"; } else if (args[1].equals("regtest")) { params = RegTestParams.get(); filePrefix = "forwarding-service-regtest"; } else { params = MainNetParams.get(); filePrefix = "forwarding-service"; }
有多個獨立的,獨立的比特幣網路:
- 人們買賣東西的主要或“生產”網路。
- 公共測試網路(testnet)不時被重置並存在以供我們使用新功能。
- 迴歸測試模式,它不是公共網路,需要你自己執行帶有-regtest標誌的比特幣守護程序。
每個網路都有自己的創世塊,自己的埠號和自己的地址字首位元組,以防止你不小心嘗試通過網路傳送比特幣(這將無法正常工作)。這些事實被封裝到NetworkParameters
單例物件中。如你所見,每個網路都有自己的類,你可以通過在其中一個物件上呼叫get()
來獲取相關的NetworkParameters
物件。
強烈建議你在testnet上或使用regtest模式開發軟體。如果你不小心丟失了測試比特幣,這沒什麼大不了的,因為它們毫無價值,你可以從TestNet Faucet 免費獲得大量的比特幣。確保在完成後將比特幣送回水龍頭,以便其他人也可以使用它們。
在regtest模式下,沒有公共基礎設施,但是你可以隨時獲得一個新的塊而不必等待一個通過在regtest模式bitcoind執行的同一臺機器上執行bitcoind -regtest setgenerate true
。
金鑰和地址
比特幣交易通常將錢匯入公共橢圓曲線鍵。發件人建立包含收件人地址的交易,其中地址是其公鑰雜湊的編碼形式。接收者然後簽署一個交易,用他們自己的私鑰宣告比特幣。金鑰用ECKey
類表示。ECKey
可以包含私鑰,或只包含缺少私有部分的公鑰。請注意,在橢圓曲線加密中,公鑰是從私鑰派生的,因此知道私鑰本身也意味著知道公鑰。這與你可能熟悉的其他一些加密系統不同,例如RSA。
地址是公鑰的文字編碼。實際上,它是公鑰的160位hash,具有版本位元組和一些校驗和位元組,使用名為base58的比特幣特定編碼編碼到文字中。Base58旨在避免在寫下時可能相互混淆的字母和數字,例如1和大寫i。
// Parse the address given as the first parameter. forwardingAddress = new Address(params, args[0]);
由於地址對要為其使用金鑰的網路進行編碼,因此我們需要在此處傳遞網路引數。第二個引數只是使用者提供的字串。如果建構函式不可解析或者網路錯誤,它將丟擲錢包應用套件例外。
bitcoinj由各種層組成,每層都在比最後一層更低的層次上執行。想要傳送和接收資金的典型應用程式至少需要BlockChain
,BlockStore
,PeerGroup
和Wallet
。所有這些物件需要相互連線,以便資料正確流動。閱讀如何融合在一起
,瞭解有關資料如何通過基於bitcoinj的應用程式流動的更多資訊。
為了簡化這個過程(通常是樣板檔案),我們提供了一個名為WalletAppKit
的高階打包器。它在簡化的支付驗證模式(而不是完全驗證)中配置bitcoinj,這是此時選擇的最合適的模式。除非你是專家並且希望嘗試(不完整的,可能是錯誤的)完整模式,它提供了一些簡單的屬性和鉤子,允許你修改預設配置。
將來,可能會有更多的工具包為不同型別的應用程式配置bitcoinj,這些應用程式可能有不同的需求。但就目前而言,只有一個。
// Start up a basic app using a class that automates some boilerplate. Ensure we always have at least one key. kit = new WalletAppKit(params, new File("."), filePrefix) { @Override protected void onSetupCompleted() { // This is called in a background thread after startAndWait is called, as setting up various objects // can do disk and network IO that may cause UI jank/stuttering in wallet apps if it were to be done // on the main thread. if (wallet().getKeyChainGroupSize() < 1) wallet().importKey(new ECKey()); } }; if (params == RegTestParams.get()) { // Regression test mode is designed for testing and development only, so there's no public network for it. // If you pick this mode, you're expected to be running a local "bitcoind -regtest" instance. kit.connectToLocalHost(); } // Download the block chain and wait until it's done. kit.startAsync(); kit.awaitRunning();
該工具包有三個引數 - NetworkParameters(幾乎所有庫中的API都需要這個),一個用於儲存檔案的目錄,以及一個以任何建立檔案為字首的可選字串。如果你希望保持分隔的同一目錄中有多個不同的bitcoinj應用程式,這將非常有用。在這種情況下,檔案字首是“forwarding-service”加上網路名稱,如果不是主網路(參見上面的程式碼)。
它還提供了一個可覆蓋的方法,我們可以將自己的程式碼放入其中,以自定義它為我們建立的物件。我們在這裡覆蓋它。請注意,appkit實際上將在後臺執行緒上建立和設定物件,因此也會從後臺執行緒呼叫onSetupCompleted。
在這裡,我們只需檢查錢包是否至少有一個金鑰,如果沒有,我們會新增一個新金鑰。如果我們從磁碟載入錢包,那麼當然不會採用此程式碼路徑。
接下來,我們檢查我們是否使用regtest模式。如果我們是,那麼我們告訴套件只連線到本地主機,其中預計會在regtest模式下執行bitcoind。
最後,我們呼叫kit.startAsync()。 WalletAppKit是一種番石榴服務。 Guava是Google廣泛使用的實用程式庫,它增加了標準Java庫以及一些有用的附加功能。服務是一個可以啟動和停止的物件(但只能啟動一次),並且可以在完成啟動或關閉時接收回調。你也可以阻止呼叫執行緒,直到它以awaitRunning()啟動,這就是我們在這裡所做的。
當塊鏈完全同步時,WalletAppKit將認為自己已經啟動,這有時需要一段時間。你可以瞭解如何加快速度,但對於玩具演示應用程式,不需要實現任何額外的優化。
該工具包上有訪問器,可以訪問它配置的底層物件。在類啟動或啟動過程之前,你不能呼叫它們(它們將斷言),因為不會建立物件。
應用程式啟動後,你會注意到應用程式執行的目錄中有兩個檔案:.wallet檔案和.spvchain檔案。他們走在一起,決不能分開。
處理交易
我們想知道什麼時候收到錢,所以我們可以轉發它。這是一個交易,與bitcoinj中的大多數Java API一樣,你通過註冊事件偵聽器event listeners
來了解交易,事件偵聽器只是實現介面的物件。庫中有一些交易監聽器介面:
WalletEventListener BlockChainListener PeerEventListener TransactionConfidence.Listener
大多數應用程式不需要使用所有這些。因為每個介面都提供一組相關交易,你可能並不關心所有這些交易。
kit.wallet().addCoinsReceivedEventListener(new WalletCoinsReceivedEventListener() { @Override public void onCoinsReceived(Wallet w, Transaction tx, Coin prevBalance, Coin newBalance) { // Runs in the dedicated "user thread". } });
bitcoinj中的交易在專用的後臺執行緒中執行,該執行緒僅用於執行事件偵聽器,稱為user thread
使用者執行緒。這意味著它可以與應用程式中的其他程式碼並行執行,如果你正在編寫GUI應用程式,則意味著你不能直接修改GUI,因為你不在GUI或main
主執行緒中。但是,事件偵聽器本身不需要是執行緒安全的,因為交易將按順序排隊並執行。你也不必擔心使用多執行緒庫時通常會出現的許多其他問題(例如,重新進入庫是安全的,並且可以安全地執行阻塞操作)。
關於編寫GUI應用程式的說明
大多數小工具工具包(如Swing,JavaFX或Android)都具有所謂的執行緒關聯,這意味著你只能在單個執行緒中使用它們。要從後臺執行緒返回到主執行緒,通常會將閉包傳遞給某個實用程式函式,該函式排程在GUI執行緒空閒時執行的閉包。
為了簡化使用bitcoinj編寫GUI應用程式的任務,你可以在註冊事件偵聽器listener
時指定任意Executor
。將要求該執行程式執行事件偵聽器。預設情況下,這意味著將給定的Runnable
傳遞給使用者執行緒,但你可以像這樣覆蓋:
Executor runInUIThread = new Executor() { @Override public void execute(Runnable runnable) { SwingUtilities.invokeLater(runnable);// For Swing. Platform.runLater(runnable);// For JavaFX. // For Android: handler was created in an Activity.onCreate method. handler.post(runnable); } }; kit.wallet().addEventListener(listener, runInUIThread);
現在,listener
上的方法將自動在UI執行緒中呼叫。
因為這可能會重複且煩人,你還可以更改預設執行程式,因此所有交易始終在你的UI執行緒上執行:
Threading.USER_THREAD = runInUIThread;
在某些情況下,bitcoinj可以非常快速地生成大量交易,這在將塊鏈與具有大量交易的錢包同步時是典型的,因為每個交易都可以生成交易可信度confidence
更改交易(因為它們隱藏的很深)。未來錢包交易的工作方式很可能會改變以避免這個問題,但是現在這就是API的工作方式。如果使用者執行緒落後,則當事件偵聽器listener
呼叫在堆上排隊時,可能會發生記憶體膨脹。為避免這種情況,你可以使用Threading.SAME_THREAD
作為執行程式註冊交易處理程式,在這種情況下,它們將立即在bitcoinj控制的後臺執行緒上執行。但是,在使用此模式時必須格外小心——程式碼中出現的任何異常都可能會解開bitcoinj堆疊並導致對等斷開連線,同樣,重新進入庫可能會導致鎖定反轉或其他問題。通常你應該避免這樣做,除非你真的需要額外的表現,並確切知道你在做什麼。
收錢
kit.wallet().addCoinsReceivedEventListener(new WalletCoinsReceivedEventListener() { @Override public void onCoinsReceived(Wallet w, Transaction tx, Coin prevBalance, Coin newBalance) { // Runs in the dedicated "user thread". // // The transaction "tx" can either be pending, or included into a block (we didn't see the broadcast). Coin value = tx.getValueSentToMe(w); System.out.println("Received tx for " + value.toFriendlyString() + ": " + tx); System.out.println("Transaction will be forwarded after it confirms."); // Wait until it's made it into the block chain (may run immediately if it's already there). // // For this dummy app of course, we could just forward the unconfirmed transaction. If it were // to be double spent, no harm done. Wallet.allowSpendingUnconfirmedTransactions() would have to // be called in onSetupCompleted() above. But we don't do that here to demonstrate the more common // case of waiting for a block. Futures.addCallback(tx.getConfidence().getDepthFuture(1), new FutureCallback<TransactionConfidence>() { @Override public void onSuccess(TransactionConfidence result) { // "result" here is the same as "tx" above, but we use it anyway for clarity. forwardCoins(result); } @Override public void onFailure(Throwable t) {} }); } });
在這裡我們可以看到當我們的應用收到錢時會發生什麼,我們打印出我們收到了多少,使用靜態實用程式方法格式化為文字。
然後我們做了一些更先進的事情。我們稱之為這種方法:
ListenableFuture<TransactionConfidence> future = tx.getConfidence().getDepthFuture(1);
每個交易都有一個與之關聯的confidence
物件。confidence
的概念體現了比特幣是一個全球共識系統這一事實,該系統不斷努力就全球交易順序達成一致。因為這是一個難題(當遇到惡意行為者時),交易可能會被雙倍花費(在比特幣術語中我們說它已經dead
)。也就是說,我們有可能相信我們已經收到了錢,後來我們發現世界其他地方不同意我們的看法。
Confidence
物件包含我們可以用來做出基於風險的決策的資料,這些決策是關於我們實際收到錢的可能性。它們還可以幫助我們在信心變化或達到某個閾值時學習。
Futures
是併發程式設計中的一個重要概念,bitcoinj大量使用它們,特別是我們將Guava擴充套件用於標準的Java Future類,稱為ListenableFuture
。ListenableFuture
表示未來某種計算或狀態的結果。你可以等待它完成(阻止呼叫執行緒),或者註冊將被呼叫的回撥。期貨也可能會失敗,在這種情況下,你會收到異常而不是結果。
在這裡,我們要求depth future
。當交易被鏈中的至少那麼多塊掩埋時,這個future
就完成了。深度為1表示它出現在鏈中的頂部塊中。所以在這裡,我們說“當交易至少有一個確認時執行此程式碼”。通常你會使用一個名為Futures.addCallback
的實用工具方法,雖然還有另一種註冊監聽器的方法,可以在下面的程式碼片段中看到。
然後,當傳送給我們錢的交易確認時,我們只調用一個我們自己定義的方法叫做forwardCoins
。
這裡有一件重要的事情需要注意。depth future
可能會執行,然後交易的depth
變為小於future
的引數。這是因為在任何時候比特幣網路都可能經歷“重組”,其中最著名的鏈從一個切換到另一個。如果你的交易出現在新鏈中的其他位置,則depth
實際上可能會下降而不是向上。處理入庫付款時,你應確保如果交易信心下降,你會嘗試中止你為該資金提供的任何服務。你可以通過閱讀SPV安全模型
瞭解有關此主題的更多資訊。
處理re-orgs和double spends是一個複雜的主題,本教程未涉及。你可以通過閱讀其他文章瞭解更多資訊。
傳送比特幣
ForwardingService
的最後一部分是傳送我們剛剛收到的比特幣。
Coin value = tx.getValueSentToMe(kit.wallet()); System.out.println("Forwarding " + value.toFriendlyString() + " BTC"); // Now send the coins back! Send with a small fee attached to ensure rapid confirmation. final Coin amountToSend = value.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); final Wallet.SendResult sendResult = kit.wallet().sendCoins(kit.peerGroup(), forwardingAddress, amountToSend); System.out.println("Sending ..."); // Register a callback that is invoked when the transaction has propagated across the network. // This shows a second style of registering ListenableFuture callbacks, it works when you don't // need access to the object the future returns. sendResult.broadcastComplete.addListener(new Runnable() { @Override public void run() { // The wallet has changed now, it'll get auto saved shortly or when the app shuts down. System.out.println("Sent coins onwards! Transaction hash is " + sendResult.tx.getHashAsString()); } });
首先,我們查詢我們收到多少錢(當然,由於我們的應用程式的性質,這與上面的onCoinsReceived
回撥中的newBalance
相同)。
然後我們決定傳送多少——它與我們收到的相同,減去費用。我們不需要附加費用,但如果我們不這樣做,可能需要一段時間才能確認。預設費用很低。
要傳送比特幣,我們使用錢包sendCoins
方法。它需要三個引數:TransactionBroadcaster
(通常是PeerGroup
),傳送比特幣的地址(這裡我們使用我們之前從命令列解析的地址)以及要傳送多少錢。
sendCoins
返回一個SendResult
物件,該物件包含已建立的交易和一個ListenableFuture
,可用於查明網路何時接受付款。如果錢包沒有足夠的錢,sendCoins
方法將丟擲一個異常,其中包含一些關於缺少多少錢的資訊。
自定義傳送過程和設定費用
比特幣交易可以附加費用。這對於反拒絕服務機制很有用,但它主要是為了在通貨膨脹率下降時激勵系統後期的採礦。你可以通過自定義傳送請求來控制附加到交易的費用:
SendRequest req = SendRequest.to(address, value); req.feePerKb = Coin.parseCoin("0.0005"); Wallet.SendResult result = wallet.sendCoins(peerGroup, req); Transaction createdTx = result.tx;
請注意,在這裡,我們實際上設定了每千位元組建立的交易的費用。這就是比特幣的工作原理——交易的優先順序由費用除以大小決定,因此較大的交易要求較高的費用被視為與較小的交易“相同”。
寫在最後
bitcoinj還有許多其他功能,本教程不涉及這些功能。你可以閱讀其他文章以瞭解有關完整驗證,錢包加密等的更多資訊,當然JavaDocs還詳細介紹了完整的API。
我建議你瀏覽我們的區塊鏈教程和區塊鏈技術部落格,深入瞭解區塊鏈,比特幣,加密貨幣,以太坊,和智慧合約。
- 以太坊入門教程,主要介紹智慧合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- java以太坊開發教程,主要是針對java和android程式設計師進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智慧合約開發互動,進行賬號建立、交易、轉賬、代幣開發以及過濾器和交易等內容。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括賬戶管理、狀態與交易、智慧合約開發與互動、過濾器和交易等。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Php程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Java程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- EOS入門教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智慧合約開發與部署、使用程式碼與智慧合約互動等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
匯智網原創翻譯,轉載請標明出處。這裡是原文