Node.js和NoSQL開發比特幣加密貨幣應用程序(上)

我一直在跟蹤比特幣之類的加密貨幣相關主題有幾個月了,我對所發生的一切都很是着迷。php

做爲一名Web應用程序開發人員,我一直特別感興趣的一個主題是加密貨幣交易以及如何製做它們。從前端看,這些應用程序彷佛是用於管理賬戶,將比特幣轉換爲美圓等法訂貨幣以及將比特幣轉帳給其餘人的工具,但它們能作更多嗎?前端

咱們將看一些Node.js和NoSQL數據庫Couchbase的例子,它們涵蓋了以加密貨幣交易爲模型的主題。java

免責聲明:我不是加密貨幣專家,也沒有參與金融服務或交易所的任何開發。我是這個主題的狂熱愛好者,從本文中得到的任何內容都應該通過適當的測試和使用,風險自負。node

the take-Away

你將從這篇特定文章中得到那些內容,將沒法得到那些內容呢?讓咱們從你不會從本文中獲得的東西開始:python

  • 咱們不會配置任何銀行或信用卡服務來交易美圓等法訂貨幣。
  • 咱們不會將任何已簽名的交易廣播到比特幣網絡,最終肯定轉帳。

也就是說,如下是你能夠期待在本文中學習的一些內容:android

  • 咱們將建立一個分層肯定性(HD,hierarchical deterministic)錢包,它能夠爲給定的種子生成無限量的密鑰,每一個密鑰表明一個用戶錢包。
  • 咱們將根據主種子建立每一個包含錢包的用戶賬戶。
  • 咱們將建立表明交易所存款,取款和資金轉帳的交易,而不實際使用法訂貨幣。
  • 咱們將從比特幣網絡中查找餘額。
  • 咱們將建立在比特幣網絡上廣播的簽名交易。

咱們將在本文中看到許多能夠更好地完成的事情。若是你發現了能夠改進的內容,請務必在評論中分享。就像我說的那樣,我不是這個主題的專家,只是一個粉絲。程序員

項目要求

爲了成功完成這個項目,必須知足一些要求:web

  • 你必須安裝並配置Node.js 6+。
  • 你必須安裝Couchbase 5.1+並配置Bucket和RBAC配置文件。

重點是我將不會介紹如何啓動和運行Couchbase。這不是一個困難的過程,可是你須要一個Bucket設置一個應用程序賬戶和一個用N1QL查詢索引。mongodb

建立具備依賴關係的Node.js應用程序

在開始添加任何邏輯以前,咱們將建立一個新的Node.js應用程序並下載依賴項。在計算機上的某個位置建立項目目錄,並從該目錄中的CLI執行如下命令:數據庫

npm init -y
npm install couchbase --save
npm install express --save
npm install body-parser --save
npm install joi --save
npm install request request-promise --save
npm install uuid --save
npm install bitcore-lib --save
npm install bitcore-mnemonic --save
複製代碼

我知道我能夠在一行中完成全部的依賴安裝,但我想讓它們清楚地閱讀。那麼咱們在上面的命令中作了什麼?

首先,咱們經過建立package.json文件來初始化一個新的Node.js項目。而後咱們下載咱們的依賴項並經過--save標誌將它們添加到package.json文件中。

對於此示例,咱們將使用Express Frameworkexpressbody-parserjoi包都與接受和驗證請求數據相關。由於咱們將與公共比特幣節點進行通訊,因此咱們將使用requestrequest-promise包。很是受歡迎的bitcore-lib軟件包將容許咱們建立錢包並簽署交易,而bitcore-mnemonic軟件包將容許咱們生成可用於咱們的HD錢包密鑰的種子。最後,couchbaseuuid將用於處理咱們的數據庫。

如今咱們可能想要更好地構建咱們的項目。在項目目錄中添加如下目錄和文件(若是它們尚不存在):

package.json
config.json
app.js
routes
    account.js
    transaction.js
    utility.js
classes
    helper.js
複製代碼

咱們全部的API端點都將分爲幾類,並放在每一個適當的路由文件中。咱們沒必要這樣作,但爲了使咱們的項目更乾淨一點。咱們去刪除大量的比特幣和數據庫邏輯,咱們將把全部非數據驗證的內容添加到咱們的classes/helper.js文件中。config.json文件將包含咱們全部的數據庫信息以及咱們的助記符種子。在一個現實的場景中,這個文件應該被視爲黃金般重要,並得到儘量多的保護。app.js文件將具備咱們全部的配置和引導邏輯,用於鏈接咱們的路由,鏈接到數據庫等。

爲方便起見,咱們將爲項目添加一個依賴項並進行設置:

npm install nodemon --save-dev
複製代碼

nodemon包將容許咱們每次更改文件時熱從新加載項目。這不是一個必須的要求,但能夠爲咱們節省一些時間。

打開package.json文件並添加如下腳本以實現它:

...
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "./node_modules/nodemon/bin/nodemon.js app.js"
},
...
複製代碼

咱們能夠在此時開始咱們的應用程序的開發過程。

開發數據庫和比特幣邏輯

在開發咱們的應用程序時,在咱們開始擔憂API端點以前,咱們想要建立咱們的數據庫和比特幣相關的邏輯。

咱們將把時間花在項目的classes/helper.js文件中。打開它幷包含如下內容:

const Couchbase = require("couchbase");
const Request = require("request-promise");
const UUID = require("uuid");
const Bitcore = require("bitcore-lib");

class Helper {

    constructor(host, bucket, username, password, seed) {
        this.cluster = new Couchbase.Cluster("couchbase://" + host);
        this.cluster.authenticate(username, password);
        this.bucket = this.cluster.openBucket(bucket);
        this.master = seed;
    }

    createKeyPair(account) { }

    getWalletBalance(addresses) { }

    getAddressBalance(address) { }

    getAddressUtxo(address) { }

    insert(data, id = UUID.v4()) { }

    createAccount(data) { }

    addAddress(account) { }

    getAccountBalance(account) { }

    getMasterAddresses() { }

    getMasterKeyPairs() { }

    getMasterAddressWithMinimum(addresses, amount) { }

    getMasterChangeAddress() { }

    getAddresses(account) { }

    getPrivateKeyFromAddress(account, address) { }

    createTransactionFromAccount(account, source, destination, amount) { }

    createTransactionFromMaster(account, destination, amount) { }

}

module.exports = Helper;
複製代碼

咱們將把這個類做爲咱們應用程序的singleton來發送。在constructor方法中,咱們創建與數據庫集羣的鏈接,打開Bucket並進行身份驗證。打開的Bucket將在整個helper類中使用。

讓咱們在完成數據庫邏輯以前跳出比特幣邏輯。

若是你不熟悉HD錢包,它們本質上是一個由單個種子衍生而來的錢包。使用種子,你能夠獲得children,那些children能夠再有children,等等。

createKeyPair(account) {
    var account = this.master.deriveChild(account);
    var key = account.deriveChild(Math.random() * 10000 + 1);
    return { "secret": key.privateKey.toWIF().toString(), "address": key.privateKey.toAddress().toString() }
}
複製代碼

createKeyPair函數中的master變量表示頂級種子密鑰。每一個用戶賬戶都是該密鑰的直接子項,所以咱們根據account值派生子項。account值是人員編號,建立的每一個賬戶都將得到增量編號。可是,咱們不會生成賬戶密鑰並將其稱爲一天。相反,每一個賬戶密鑰將有10,000個可能的私鑰和公鑰,以防他們不想屢次使用同一個密鑰。一旦咱們隨機生成了一個密鑰,咱們就會返回它。

一樣,咱們有一個getMasterChangeAddress函數,以下所示:

getMasterChangeAddress() {
    var account = this.master.deriveChild(0);
    var key = account.deriveChild(Math.random() * 10 + 1);
    return { "secret": key.privateKey.toWIF().toString(), "address": key.privateKey.toAddress().toString() }
}
複製代碼

當咱們開始建立賬戶時,它們將從一開始,爲交易或Web應用程序留下零,或者你想要調用它。咱們還爲此賬戶分配了10個可能的地址。這些地址將作兩件事。第一個是他們將比特幣發送到其餘帳戶,第二個是他們將收到剩餘款項,也就是所謂的變動。請記住,在比特幣交易中,必須花費全部未花費的交易輸出(UTXO),即便它小於指望的金額。這意味着所需的金額將被髮送到目的地,剩餘部分將被髮送回這10個地址中的一個。

還有其餘方法或更好的方法嗎?固然,但這個將適用於這個例子。

爲了得到咱們使用或使用HD種子生成的任何地址的餘額,咱們可使用公共比特幣資源管理器:

getAddressBalance(address) {
    return Request("https://insight.bitpay.com/api/addr/" + address);
}
複製代碼

上面的函數將採用一個地址並以十進制格式和satoshis得到餘額。展望將來,satoshi價值是咱們惟一的相關價值。若是咱們有給定賬戶的X個地址,咱們可使用以下函數得到總餘額:

getWalletBalance(addresses) {
    var promises = [];
    for(var i = 0; i < addresses.length; i++) {
        promises.push(Request("https://insight.bitpay.com/api/addr/" + addresses[i]));
    }
    return Promise.all(promises).then(result => {
        var balance = result.reduce((a, b) => a + JSON.parse(b).balanceSat, 0);
        return new Promise((resolve, reject) => {
            resolve({ "balance": balance });
        });
    });
}
複製代碼

在上面的getWalletBalance函數中,咱們正在爲每一個地址發出請求,當它們所有完成時,咱們能夠添加餘額並返回它們。

可以傳輸加密貨幣須要的不只僅是地址餘額。相反,咱們須要知道給定地址的未花費的交易輸出(UTXO)。這可使用BitPay中的相同API找到:

getAddressUtxo(address) {
    return Request("https://insight.bitpay.com/api/addr/" + address + "/utxo").then(utxo => {
        return new Promise((resolve, reject) => {
            if(JSON.parse(utxo).length == 0) {
                reject({ "message": "There are no unspent transactions available." });
            }
            resolve(JSON.parse(utxo));
        });
    });
}
複製代碼

若是沒有未使用的交易輸出,則意味着咱們沒法傳輸任何內容,而是應該拋出錯誤。足夠的發送表明的是一個不一樣的意思。

例如,咱們能夠這樣作:

getMasterAddressWithMinimum(addresses, amount) {
    var promises = [];
    for(var i = 0; i < addresses.length; i++) {
        promises.push(Request("https://insight.bitpay.com/api/addr/" + addresses[i]));
    }
    return Promise.all(promises).then(result => {
        for(var i = 0; i < result.length; i++) {
            if(result[i].balanceSat >= amount) {
                return resolve({ "address": result[i].addrStr });
            }
        }
        reject({ "message": "Not enough funds in exchange" });
    });
}
複製代碼

在上面的函數中,咱們將獲取一個地址列表並檢查哪一個地址的數量大於咱們提供的閾值。若是他們都沒有足夠的餘額,咱們應該發送這個消息。

最終的實用程序相關功能,咱們已經看到了一些:

getMasterKeyPairs() {
    var keypairs = [];
    var key;
    var account = this.master.deriveChild(0);
    for(var i = 1; i <= 10; i++) {
        key = account.deriveChild(i);
        keypairs.push({ "secret": key.privateKey.toWIF().toString(), "address": key.privateKey.toAddress().toString() });
    }
    return keypairs;
}
複製代碼

上面的函數將爲咱們提供全部主密鑰,這對於簽名和檢查值很是有用。

重申一下,我使用有限值來生成多少個鍵。你可能會也可能不想這樣作,這取決於你。

如今讓咱們深刻研究一些用於存儲應用程序數據的NoSQL邏輯。

截至目前,咱們的數據庫中沒有數據。第一個邏輯步驟多是建立一些數據。雖然獨立並非特別困難,但咱們能夠建立這樣的函數:

insert(data, id = UUID.v4()) {
    return new Promise((resolve, reject) => {
        this.bucket.insert(id, data, (error, result) => {
            if(error) {
                reject({ "code": error.code, "message": error.message });
            }
            data.id = id;
            resolve(data);
        });
    });
}
複製代碼

基本上,咱們接受一個對象和一個id用做文檔密鑰。若是未提供文檔密鑰,咱們將自動生成它。完成全部操做後,咱們將返回建立的內容,包括響應中的id

因此咱們假設咱們要建立一個用戶賬戶。咱們能夠作到如下幾點:

createAccount(data) {
    return new Promise((resolve, reject) => {
        this.bucket.counter("accounts::total", 1, { "initial": 1 }, (error, result) => {
            if(error) {
                reject({ "code": error.code, "message": error.message });
            }
            data.account = result.value;
            this.insert(data).then(result => {
                resolve(result);
            }, error => {
                reject(error);
            });
        });
    });
}
複製代碼

請記住,賬戶由此示例的自動遞增數值驅動。咱們可使用Couchbase中的counter建立遞增值。若是計數器不存在,咱們將其初始化爲1並在每次下一次調用時遞增。請記住,0是爲應用程序密鑰保留的。

在咱們獲得計數器值以後,咱們將它添加到傳遞的對象並調用咱們的insert函數,在這種狀況下爲咱們生成一個惟一的id

咱們尚未看到它,由於咱們沒有任何端點,但咱們假設在建立賬戶時,它沒有地址信息,只有賬戶標識符。咱們可能想爲用戶添加地址:

addAddress(account) {
    return new Promise((resolve, reject) => {
        this.bucket.get(account, (error, result) => {
            if(error) {
                reject({ "code": error.code, "message": error.message });
            }
            var keypair = this.createKeyPair(result.value.account);
            this.bucket.mutateIn(account).arrayAppend("addresses", keypair, true).execute((error, result) => {
                if(error) {
                    reject({ "code": error.code, "message": error.message });
                }
                resolve({ "address": keypair.address });
            });
        });
    });
}
複製代碼

添加地址時,咱們首先按文檔ID獲取用戶。檢索文檔後,咱們獲取數字賬戶值並建立10,000個選項的新密鑰對。使用子文檔操做,咱們能夠將密鑰對添加到用戶文檔,而無需下載文檔或對其進行操做。

關於咱們剛剛作了什麼很是嚴肅的事情。

我將未加密的私鑰和公共地址存儲在用戶文檔中。這對生產來講是一個很大的禁忌。還記得你讀過的全部關於人們鑰匙被盜的地方的故事嗎?實際上,咱們但願在插入數據以前加密數據。咱們能夠經過使用Node.js加密庫來實現,或者若是咱們使用Couchbase Server 5.5,Couchbase的Node.js SDK會提供加密。咱們不會在這裏探討它。

好的,咱們如今已經在數據庫中得到了賬戶數據和地址。讓咱們查詢該數據:

getAddresses(account) {
    var statement, params;
    if(account) {
        statement = "SELECT VALUE addresses.address FROM " + this.bucket._name + " AS account USE KEYS $id UNNEST account.addresses as addresses";
        params = { "id": account };
    } else {
        statement = "SELECT VALUE addresses.address FROM " + this.bucket._name + " AS account UNNEST account.addresses as addresses WHERE account.type = 'account'";
    }
    var query = Couchbase.N1qlQuery.fromString(statement);
    return new Promise((resolve, reject) => {
        this.bucket.query(query, params, (error, result) => {
            if(error) {
                reject({ "code": error.code, "message": error.message });
            }
            resolve(result);
        });
    });
}
複製代碼

上面的getAddresses函數能夠作兩件事之一。若是提供了賬戶,咱們將使用N1QL查詢來獲取該特定賬戶的全部地址。若是未提供賬戶,咱們將獲取數據庫中每一個賬戶的全部地址。在這兩種狀況下,咱們只獲取公共地址,沒有任何敏感信息。使用參數化的N1QL查詢,咱們能夠將數據庫結果返回給客戶端。

咱們的查詢中須要注意的事項。

咱們將地址存儲在用戶文檔中的數組中。使用UNNEST運算符,咱們能夠展平這些地址並使響應更具吸引力。

如今假設咱們有一個地址,咱們想得到相應的私鑰。 咱們可能會作如下事情:

getPrivateKeyFromAddress(account, address) {
    var statement = "SELECT VALUE keypairs.secret FROM " + this.bucket._name + " AS account USE KEYS $account UNNEST account.addresses AS keypairs WHERE keypairs.address = $address";
    var query = Couchbase.N1qlQuery.fromString(statement);
    return new Promise((resolve, reject) => {
        this.bucket.query(query, { "account": account, "address": address }, (error, result) => {
            if(error) {
                reject({ "code": error.code, "message": error.message });
            }
            resolve({ "secret": result[0] });
        });
    });
}
複製代碼

給定一個特定的賬戶,咱們建立一個相似於咱們以前看到的查詢。此次,在咱們UNNEST,咱們執行WHERE條件,僅爲匹配地址提供結果。若是咱們想要咱們能夠作一個數組操做。使用Couchbase和N1QL,有不少方法能夠解決問題。

======================================================================

分享一些以太坊、EOS、比特幣等區塊鏈相關的交互式在線編程實戰教程:

  • java以太坊開發教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
  • python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
  • php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和交易等內容。
  • 以太坊入門教程,主要介紹智能合約與dapp應用開發,適合入門。
  • 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
  • C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和交易等。
  • EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、帳戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
  • java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
  • php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
  • tendermint區塊鏈開發詳解,本課程適合但願使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操代碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。

匯智網原創翻譯,轉載請標明出處。這裏是原文Node.js和NoSQL開發比特幣加密貨幣應用程序

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息