USDT是由Tether公司發行的基於比特幣區塊鏈的一種去中心化數字貨幣,做爲當前數字貨幣市場的主流錨訂貨幣之一,其官方承諾將嚴格遵照與美圓1:1的比例準備保證金。在技術層面,USDT是基於Omni協議發行的代幣,在Omni共識網絡上令牌id爲31。Omni是一個能夠自由發行數字貨幣的平臺,它徹底基於比特幣協議,並在原有的比特幣核心上增長了新的共識網絡,相似與HTTP協議基於TCP協議。html
OmniCore是Omni協議的C++實現,徹底採用與bitcoin的區塊數據,因此若是須要同時集成USDT與BTC,實際上只須要使用OmniCore一個核心錢包便可。java
本文的主要內容是介紹如何在服務端集成OmniCore實現USDT錢包的基本功能c++
示例代碼倉庫 github.com/initsysctrl…git
參數說明:github
Step1:安裝git和pkg-config,已經安裝過的能夠跳過shell
sudo apt-get install git
sudo apt-get install pkg-config
複製代碼
Step2:Clone OmniCorejson
git clone https://github.com/OmniLayer/omnicore.git
複製代碼
Step3:安裝依賴項api
首先安裝必須的構建工具數組
sudo apt-get install build-essential libtool autotools-dev automake pkg-config libssl-dev libevent-dev bsdmainutils
複製代碼
而後安裝boost,爲了兼容各個系統版本,建議安裝全部的boost開發包瀏覽器
sudo apt-get install libboost-all-dev
複製代碼
最後安裝BerkeleyDB。儘管Ubuntu自帶libdb-dev,但一樣爲了錢包的兼容性,建議使用下面的版本
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:bitcoin/bitcoin
sudo apt-get update
sudo apt-get install libdb4.8-dev libdb4.8++-dev
複製代碼
本文中並不須要構建Bitcoin-Qt,因此沒有依賴ZMQ和GUI。
Step4:開始構建OmniCore
上一步依賴安裝完畢後,進入OmnIcore安裝目錄
cd omnicore/
複製代碼
執行構建腳本
./autogen.sh
./configure
make
複製代碼
啓動以前,須要配置位於工程目錄之下的.bitcoin文件夾中的OmniCore配置文件bitcoin.conf
server=1
txindex=1
rpcuser=你的rpc用戶名
rpcpassword=你的rpc密碼
rpcallowip=127.0.0.1
rpcport=8332
paytxfee=0.00001
minrelaytxfee=0.00001
datacarriersize=80
logtimestamps=1
omnidebug=tally
omnidebug=packets
omnidebug=pending
複製代碼
server=1
表明開啓RPC訪問
txindex=1
表明事務初始索引
recuser
和rpcpassword
表明rpc訪問的身份驗證,
rpcallowip
和rpcport
表明容許訪問錢包的ip地址及端口。
paytxfee
和minrelattxfee
控制bitcoin交易的手續費,Omni交易也屬於一種特殊的比特幣交易,打包與廣播也須要向礦工支付費用。手續費設置太低會形成交易確認慢甚至交易失敗,手續費太高會形成資源的浪費(以2018.09.13的BTC價格換算,每多消耗0.0001btc須要浪費4rmb),因此設置動態配置交易手續費十分必要。預估比特幣交易手續費可使用下面的網址bitcoinfees.earn,buybitcoinworldwide。假設當前預估的比特幣交易費率爲0.0000001BTC/Byte,那麼須要設置paytxfee=0.00001BTC/kByte
。
上述構建完成後,進入omnicore/src
目錄,開始啓動錢包,啓動時能夠配置啓動項以選擇不一樣的網絡。
./omnicored -testnet
鏈接test3測試網絡,會同步test3網絡的區塊數據(約20G)./omnicored -regtest
單機運行,不須要鏈接其餘網絡,區塊數據在本地運行。./omnicored
鏈接比特幣主網網絡,會同步真實區塊數據(約180G)。錢包初始化完成後,將自動開始同步區塊。啓動主網或測試網後須要同步一段較長的時間,在這段時間內不要進行任何交易。能夠新開一個終端鏈接錢包所在服務器,經過getinfo
和omni_getinfo
能夠查看底層bitcoin信息和上層omni信息。區塊瀏覽器(Test3區塊瀏覽器)能夠做爲區塊同步的參考。同步將做爲守護進程在後臺執行,若是須要中止,使用指令 ./omnicore-cli stop
。前期開發建議在test3測試網絡上進行。
$ ./omnicore-cli getinfo
{
"version": 130200,
"protocolversion": 70015,
"walletversion": 130000,
"balance": 4.85909131, //錢包比特幣總餘額
"blocks": 1413349,
"timeoffset": 0,
"connections": 0,
"proxy": "",
"difficulty": 56234572.68927951,
"testnet": true, //是不是測試網
"keypoololdest": 1535371434,
"keypoolsize": 100,
"paytxfee": 0.00010000,
"relayfee": 0.00001000,
"errors": ""
}
複製代碼
$ ./omnicore-cli omni_getinfo
{
"omnicoreversion_int": 30001000,
"omnicoreversion": "0.3.1", //omni core 版本
"mastercoreversion": "0.3.1",
"bitcoincoreversion": "0.13.2", //基於比特幣版本
"block": 1413349,//區塊要舉高高
"blocktime": 1536841368,
"blocktransactions": 0,
"totaltrades": 15601,
"totaltransactions": 43731,
"alerts": [
]
}
複製代碼
json-rpc是一種輕量級傳輸協議,定義一個完整網絡請求中請求對象的格式和響應對象的格式。與rest api相比,僅僅只是數據格式的差別而已,網絡請求的自己並無什麼差異。
請求對象:
{
"jsonrpc": "2.0",//rpc版本號
"method": "your_method",//方法名
"params": [//參數數組
"var1",
"var2"
],
"id": 9527//請求
}
複製代碼
響應對象(正確):
{"jsonrpc": "2.0", "result": "this is result", "id": 9527}
複製代碼
響應對象(錯誤):
{
"result": null,
"error": {
"code": -32601,//錯誤碼
"message": "Method not found"//錯誤緣由
},
"id": 9527
}
複製代碼
請求的協議是http,請求的地址是錢包主機地址。身份驗證信息將以Authorzation的形式添加到headers中,方法、參數、id信息將以raw的形式添加到hbody中:
返回的結果以下:
服務端,以java爲例:
String RPC_USER = "your_user_name";
String RPC_PASSWORD = "your_password";
RestTemplate client = new RestTemplateBuilder()
.basicAuthorization(RPC_USER, RPC_PASSWORD)
.rootUri(URL)
.build();
client.postForObject(URL, baseRpcReq, BaseRPCresponse.class);
複製代碼
或者採用jsonrpc4j
,這種方式能夠捕捉異常便於調試:
<!--JSON-PRP handler-->
<dependency>
<groupId>com.github.briandilley.jsonrpc4j</groupId>
<artifactId>jsonrpc4j</artifactId>
<version>1.5.3</version>
</dependency>
複製代碼
String RPC_USER = "your_user_name";
String RPC_PASSWORD = "your_password";
String cred = Base64.encodeBase64String((RPC_USER + ":" + RPC_PASSWORD).getBytes());
Map<String, String> headers = new HashMap<>(1);
headers.put("Authorization", "Basic " + cred);
try {
this.mClient = new JsonRpcHttpClient(new URL(URL), headers);
} catch (MalformedURLException e) {
e.printStackTrace();
}
複製代碼
rpc僅僅只是一種數據請求的固定格式,username和passwrod並不能保證訪問的安全性。錢包須要配置rpcallowip字段來限定運行訪問錢包的ip地址,默認狀況下爲localhost
,在測試節點,可使用0.0.0.0/0
開啓無限制訪問。
Omnicore的指令集徹底兼容bitcoin,除了與omni令牌相關的指令集外,其他的指令集都來所有繼承自bitcoin-cli。下面爲錢包建立的核心指令集,更詳細的內容能夠從OmniCore JSON-RPC和BitCoin JSON-RPC進行查詢。
由於Omnicore底層基於Bitcoin,因此USDT地址實際上就是BTC地址,當前的比特幣錢包採用的是deterministic wallet錢包模式,使用如樹狀層級推導 (hierarchical deterministic) 的推導方式,從一個隨機數生成源推導全部地址密鑰。因此一個USDT錢包中,全部的地址實際上來自同一個種子源。若是是測試網絡,地址通常以"m","n"獲取新地址能夠指定account名稱,若是不指定,那麼會分配到默認帳戶。
$ ./omnicore-cli getnewaddress
mkRj6TFspkyso96LvDTJq77DwoqoFMBEcJ
複製代碼
$ ./omnicore-cli getnewaddress feeaccount
n1WuiWX5zjmz7MVymUtsdSnC325xQ1v4SR
複製代碼
一個帳戶名能夠對應多個地址
$ ./omnicore-cli getaddressesbyaccount feeaccount
[
"mk8cMZBX7v7zzzc9FHBMQbRNVPwRtq9CZ2",
"n1WuiWX5zjmz7MVymUtsdSnC325xQ1v4SR"
]
複製代碼
若是是正式環境,那麼必須使用其餘地址轉帳或提現到新地址才能獲取BTC和USDT。若是使用regtest本地網絡,那麼須要經過挖礦得到比特幣。若是使用test3測試網絡,那麼TBTC能夠從coinfaucet或者faucet獲取。但在測試網絡是沒有測試usdt的,全部只能用test omni代替usdt進行測試。發送TBTCmoneyqMan7uh8FqdCA2BV5yZ8qVrc9ikLP
便可得到少許TOMNI,匯率爲100 TOMNI/1 TBTC,令牌id分別爲1和2。
比特幣實際上沒有「餘額」這個概念,只有UTXO(Unspent Transaction Outputs)。在傳統的交易系統中,從A地址轉給B地址100個單位的資產的過程是把A地址下的餘額減100,B地址下的餘額加100,兩步必須知足原子性。但在比特幣中A地址下並無餘額,只有一張張零碎的「支票」,記錄着每一筆轉入資金,轉帳的過程其實是把一張或者多張「支票」湊起來花費掉,沒有花掉的部分做爲「找零」返回給找零地址。因此通常須要把找零地址設置爲發送地址,若是沒有的話,系統將在錢包中隨機挑選一個地址做爲「找零地址」。
列出比特幣UTXO:
方法: listunspent
參數:
返回:UTXO列表
$ ./omnicore-cli listunspent 0 999999 '["mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC"]'
[
{
"txid": "ef6e77063dff8988b82044286b3d3f022df5a14aae260179a95c2e80c0e47ec4",//來源
"vout": 2,
"address": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//地址
"account": "account0",//所屬帳戶
"scriptPubKey": "76a914293d87f697ca96ffb00f049b60645e5c8979498488ac",
"amount": 0.00054600,//支票金額
"confirmations": 799,
"spendable": true,//可花費?
"solvable": true
}
]
複製代碼
USDT的轉帳其實是代號爲31的OmniCore令牌轉帳。Omnicore提供了多套api實現令牌轉帳功能,v0.3.1版本以前,可使用omni_send
和omni_sendall
。這種方式必須保證發送地址上不只須要有令牌餘額,還須要有必定數量的比特幣用於支付手續費。從v0.3.1版本開始,Omnicore提供了兩個新的api omni_funded_send
和omni_funded_sendall
,這種方式的好處在於能夠指定手續費的支付方,全部的令牌交易均可以使用統一的地址進行支付比特幣手續費,而不須要發送者自身擁有比特幣。但這裏並未設定手續費的具體數量,系統將根據在配置文件中的關於手續費的配置文件進行動態設定。
方法 omni_funded_send
參數
fromaddress (string,必選) 令牌發送者
toaddress (string,必選) 令牌接收者
propertyid (number,必選) 令牌id
amount (string,必選) 發送金額
feeaddress (string,可選)用於支付手續費的地址,若是設置此地址,那麼此地址上必須擁有比特幣
返回:事務 hex
$ ./omnicore-cli omni_funded_send mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC mqrA5Ai8XdKe1ob1L2HwyYr3TXUf9nUeBf 1 5 mpaumxor659PhoJhXp1VCVHVwbFCZSRmuf
a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad
複製代碼
錯誤返回:
{
error code: -212
error message:Error choosing inputs for the send transaction
}
複製代碼
發送USDT或其餘令牌的過程屬於一種比較特殊的比特幣交易,交易的打包廣播一樣須要支付礦工費用,費用過低交易將沒法成功。發送令牌的過程可能會出現各類錯誤,能夠檢查發送者地址是不是本地錢包地址令牌餘額是否充足、feeaddress是不是本機錢包地址、比特幣餘額是否充足。
除了使用基本的api外,還可使用 Raw Transaction API 建立並廣播事務,但過程至關的繁瑣,須要通過七步構建。通常狀況下不建議這麼作,但若是須要將打包簽名的過程與發送的過程進行分離,那麼就必須使用這種方式。例如某些狀況下,須要在冷錢包中籤名,而後在熱錢包中廣播。
方法:omni_gettransaction
參數:hex(string_64位事務哈希),發送交易後的交易哈希txid
返回 :
$ ./omnicore-cli omni_gettransaction a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad
{
"txid": "a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad",//交易哈希
"fee": "0.00002765",//手續費金額
"sendingaddress": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//發送者
"referenceaddress": "mqrA5Ai8XdKe1ob1L2HwyYr3TXUf9nUeBf",//接受者
"ismine": true,//是否本機地址
"version": 0,
"type_int": 0,
"type": "Simple Send",
"propertyid": 1,
"divisible": true,
"amount": "5.00000000",//發送令牌金額
"confirmations": 0//確認數,,默認狀況下,>5通常才認爲交易有效
}
複製代碼
$ ./omnicore-cli omni_gettransaction a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad
{
"txid": "a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad",//交易哈希
"fee": "0.00002765",
"sendingaddress": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//發送address
"referenceaddress": "mqrA5Ai8XdKe1ob1L2HwyYr3TXUf9nUeBf",//接收address
"ismine": true,
"version": 0,
"type_int": 0,
"type": "Simple Send",//交易類型
"propertyid": 1,
"divisible": true,
"amount": "5.00000000",
"valid": true,//已經成功
"blockhash": "00000000000000460219a9fe9761cb92120eb7d67b640d2b643a0a05185fa2a0",
"blocktime": 1536324400,
"positioninblock": 1391,
"block": 1412595,
"confirmations": 1123//經過節點確認
}
複製代碼
方法:omni_listtransactions
參數:
addfilt | string | 可選 | 地址過濾 (default: "*" ) |
---|---|---|---|
count |
number | 可選 | 最大數量(default: 10 ) |
skip |
number | 可選 | 跳過第n個事務 (default: 0 ) |
startblock |
number | 可選 | 起始的區塊(default: 0 ) |
endblock |
number | 可選 | last block to include in the search (default: 999999 ) |
請求:
$ ./omnicore-cli omni_listtransactions
[
{
"txid": "a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad",//事務哈希
"fee": "0.00002765",//手續費
"sendingaddress": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//發送
"referenceaddress": "mqrA5Ai8XdKe1ob1L2HwyYr3TXUf9nUeBf",//接收
"ismine": true,
"version": 0,
"type_int": 0,
"type": "Simple Send",//類型
"propertyid": 1,//令牌id
"divisible": true,
"amount": "5.00000000",
"valid": true,//是否有效的交易事務
"blockhash": "00000000000000460219a9fe9761cb92120eb7d67b640d2b643a0a05185fa2a0",
"blocktime": 1536324400,
"positioninblock": 1391,
"block": 1412595,
"confirmations": 221//確認數,默認大於5猜有效
},
{
"txid": "2e527c3c85b2a9b21252b50efd6cda31022ee5ebcf9fee451255bea61211b799",
"fee": "0.00002570",
"sendingaddress": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",
"referenceaddress": "mq8fRoxRB9M4vstJ9BrEBaysZVUjxPxoK3",
"ismine": true,
"version": 0,
"type_int": 0,
"type": "Simple Send",
"propertyid": 2,
"divisible": true,
"amount": "3.00000000",
"valid": true,
"blockhash": "0000000000000007c957dc0642c39c26b9bb46327620e18e23244936f894c570",
"blocktime": 1536324082,
"positioninblock": 2115,
"block": 1412594,
"confirmations": 222
}
]
複製代碼
若是交易剛剛發送,即沒有被驗證是否合法,也沒有被節點確認,那麼該事務將處於pengding 狀態使用 omni_listtransactions
不能做爲轉帳的確認狀態。使用omni_listpendingtransactions
能夠在緩衝區找到這一類型的事務信息,但pengding狀態並不穩定,不能用於確認轉帳結果。
查詢USDT的餘額即查詢第31號令牌的餘額。
方法:omni_getbalance
參數:
$ ./omnicore-cli omni_getbalance mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC 1
{
"balance": "4.00000000",//餘額
"reserved": "0.00000000",
"frozen": "0.00000000"//被凍結,沒啥用
}
複製代碼
方法:omni_getwalletaddressbalances
將返回錢包內全部餘額不爲0的地址列表,每一個地址均可能有不一樣的令牌餘額。令牌id若是爲31,那麼這個令牌即USDT。
$ ./omnicore-cli omni_getwalletaddressbalances
[
{
"address": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//地址
"balances": [
{
"propertyid": 1,//令牌id,31=USDT
"name": "Omni",//令牌名稱
"balance": "4.00000000",//餘額
"reserved": "0.00000000",
"frozen": "0.00000000"
},
{
"propertyid": 2,
"name": "Test Omni",
"balance": "7.00000000",
"reserved": "0.00000000",
"frozen": "0.00000000"
}
]
}
]
複製代碼
方法:omni_getwalletbalances
返回:
$ ./omnicore-cli omni_getwalletbalances
[
{
"propertyid": 1,//令牌id
"name": "Omni",//令牌名稱
"balance": "11.00000000",//可用餘額
"reserved": "0.00000000",
"frozen": "0.00000000"
},
{
"propertyid": 2,
"name": "Test Omni",
"balance": "8.00000000",
"reserved": "0.00000000",
"frozen": "0.00000000"
}
]
複製代碼
其餘的相關指令集會在源代碼中示例出來。
中心化錢包的本質是代替用戶託管資產,錢包保存了全部地址的私鑰,對上面的令牌有徹底的使用權。對於用戶而言,對資產的流動有知情權,但並無實際控制權。一個完整的中心化錢包能夠分爲兩層,記帳層和區塊底層,至少須要集成四個基本的業務功能:
USDT地址即比特幣區塊鏈上的地址,藉助比特幣內核 getnewaddress
能夠從同一個種子推導出無數個地址,生成地址的過程相似與把一枚硬幣連續拋255次。服務端須要在本身的用戶系統中爲每一個用戶生成不一樣的地址,用戶的看到的資產實際上服務端的記帳狀態,並不是真實資產。
錢包一旦啓動,會開啓同步區塊的守護進程,服務端不須要進行手動的區塊同步操做。但服務端須要按期的掃描區塊以發現並確認充值事務。經過omni_listtransactions
能夠查詢當前錢包內的事務列表,根據業務須要,能夠定時每小時全量掃描一次,每次最多返回100條事務。遍歷每條事務,若是事務已經驗證且確認數大於等於6,那麼被認爲是一條有效的充值記錄。而後判斷記帳層是否已經記錄了該事務,若是沒有記錄則寫入充值記錄表,同時查詢綁定該地址的用戶,在餘額表中該用戶的可用餘額加上充值金額。若是已經寫入了那麼跳過本次事務。單次的事務處理流程以下:
這是最簡易的模式,根據業務情景能夠適當調整掃描週期和最大事務數。
用戶充值後USDT保留在用戶綁定的區塊地址中,須要及時的轉移到中央地址中去。中央地址即保存整個平臺資產的一個或者多個地址。可使用與普通用戶相同的「種子」,也能夠單獨使用一個錢包,或者直接使用冷錢包離線保存。在保證安全和效率的狀況下,越少的轉帳次數越好,能夠最大限度的節省手續費。獲取錢包地址USDT餘額列表有多種方式 ,從v0.3.1開始可使用omni_getwalletaddressbalances
直接返回全部每一個地址的全部令牌列表。一旦檢測id=31的令牌餘額不爲0,且大於最小額度(通常大於預估的手續費)則使用omni_funded_sendall
轉移全部的USDT到指定的中央錢包。
但須要注意的是,在Omnicore上從發送者轉帳轉移指定id的令牌到接受者,當交易被建立且被髮送成功後,交易驗證須要必定時間,發送者的令牌餘額不會當即變化。因此若是掃描餘額的時間週期過短,會形成一個地址上的餘額被屢次轉移,雖然只會有一次成功但會重複消耗手續費,因此建議2-6hour掃描一次本地錢包餘額列表。
提現是指用戶把實際資產從平臺錢包中轉移出去,只要判斷是本人操做並且提現金額小於可用額度就被認爲是有效的提現請求。根據提現地址的不一樣有兩種狀況:
當提現地址是錢包內的地址時(即平臺內的另一個用戶)屬於內部轉帳。這種方式並不需在從中央錢包發送USDT到指定地址,只須要在記帳層進行依次對兩個帳戶上的USDT餘額進行修改,幾乎沒有時間延遲。
當提現地址是否是錢包內的地址時(非平臺用戶)屬於外部轉帳。這種方式須要操做區塊鏈,不會立刻進行確認,根據手續費設定和當前比特幣主網擁堵情況可能須要幾小時到一天的確認時間。
對於外部轉帳,若是用戶綁定的區塊地址上還存在餘額,那麼優先使用該地址進行轉帳,其次選擇中央錢包進行轉帳。可使用omni_funded_send
來進行建立USDT交易並廣播,交易發送成功後會生成的事務哈希。根據事務哈希,經過omni_gettransaction
能夠進行提現進度的跟蹤。
參考: