本章內容會針對比原官方提供的dapp-demo,分析裏面的前端源碼,分析清楚整個demo的流程,而後針對裏面開發過程遇到的坑,添加一下我的的看法還有解決的方案。前端
爲了方便理解,這裏簡單說說儲蓄分成合約的內容,具體能夠查看儲蓄分成合約詳細說明,儲蓄分成,顧名思義就是儲蓄以後,當達到必定的時間,按照比例返回本息這樣的意思,因此demo中拆分紅saving(儲蓄)與profit(提現)兩個頁面,本章內容是針對合約交易的提交,因此只針對儲蓄頁面說明。react
比原官方demo地址git
1)訪問的前提須要用chrome打開比原官方demo地址,同時安裝bycoin插件,在應用商店搜索就行;github
2)安裝完bycoin,須要初始化用戶信息,新建或者導入備份文件去恢復用戶;chrome
3)填寫指定資產數量,點擊肯定;數據庫
4)彈出合約交易專用頁面,填寫密碼,點擊確認;後端
5)查看交易流水api
源碼 : 儲蓄分成合約前端源代碼 (本章內容講解的是 2019年7月10號 最新版的代碼)瀏覽器
前端代碼是基於前端框架react去作的,很容易讀懂,結構如上,咱們來看看做爲儲蓄頁面(saving)Bytom-Dapp-Demo1\src\components\layout\save\index.jsx前端框架
//提交後的方法 FixedLimitDeposit(amount, address) //####### 1. .then(()=> { //####### 2. this.refs.btn.removeAttribute("disabled"); this.setState({ error:'', msg:`Submit success!!! you spent ${amount} deposite asset,and gain ${amount} billasset.` }) }).catch(err => { //####### 3. this.refs.btn.removeAttribute("disabled"); this.setState({ error:err, msg: '' }) })
1)接收了輸入框的金額,還有當前用戶的地址;
2)成功後提示內容;
3)失敗後提示內容;
接下來到FixedLimitDeposit方法
export function FixedLimitDeposit(amount, address) { const object = { address: address, amount: amount, parameter: [amount, address] } return submitContract(listDepositUTXO, createContractTransaction, updateDatatbaseBalance, object) //####### 1. }
進入Dapp-Demo1\src\components\layout\save\action.js 的 submitContract方法
return new Promise((resolve, reject) => { //list available utxo return listDepositUTXO().then(resp => { //####### 1. //create the Contract Transaction return createContractTransaction(resp, amount, address).then(object =>{ //####### 2. const input = object.input const output = object.output const args = object.args const utxo = object.utxo //Lock UTXO return updateUtxo({"hash": utxo}) //####### 3. .then(()=>{ //Transactions return window.bytom.send_advanced_transaction({input, output, gas: GetContractArgs().gas*100000000, args}) //####### 4. .then((resp) => { //Update Balance return updateDatatbaseBalance(resp, ...updateParameters).then(()=>{//####### 5. resolve() }).catch(err => { throw err }) }) .catch(err => { throw err.message }) }) .catch(err => { throw err }) }).catch(err => { throw err }) }).catch(err => { reject(err) }) })
2)調用 createContractTransaction 方法,組裝好合約的對應信息參數;
3)選取要使用的 UTXO後,調用updateUtxo 告訴bufferserver ,該UTXO已經被使用,更改狀態,防止其餘人調用了;
4)執行window.bytom.send_advanced_transaction方法,參考插件錢包API,是高級交易方法,這個是bycoin插件的原生方法,調起 提交交易的頁面,讓用戶輸入密碼;
5)交易確認後,調用 updateDatatbaseBalance 提交數據到後端;
再來看看api.js的listDepositUTXO 方法,全部與bufferserver交互的接口所有寫到這個文件裏面:
function listDepositUTXO() { return listDappUTXO({//****** 1. "program": GetContractArgs().depositProgram, "asset": GetContractArgs().assetBill, "sort": { "by":"amount", "order":"desc" } }) } //Api call from Buffer server export function listDappUTXO(params) { let url switch (window.bytom.net){ case "testnet": url = "/dapptestnet/list-utxos" break default: url = "/dapp/list-utxos" } return post(url, params).then(resp => resp.data) }
很明顯最終是調用bufferserver的/list-utxos方法,很是簡單,值得一提的是
1)裏面的結構體根據program(合約代碼)與asset(資產ID)去查找UTXO,這裏其實底層是調用官方的blockcenter接口的,後面會細說;
繼續看看Dapp-Demo1\src\components\layout\save\action.js 的createContractTransaction方法
function createContractTransaction(resp, amount, address){ return new Promise((resolve, reject) => { //utxo pre calculation const limit = GetContractArgs().radio * 100000000 //****** 1. if (resp.length === 0) { reject( 'Empty UTXO info, it might be that the utxo is locked. Please retry after 60s.') } else if (amount < limit) { reject( `Please enter an amount bigger or equal than ${limit}.`) } const result = matchesUTXO(resp, amount) //****** 2. const billAmount = result.amount const billAsset = result.asset const utxo = result.hash //contract calculation if (amount > billAmount) { reject('input amount must be smaller or equal to ' + billAmount + '.') } else { const input = [] const output = [] const args = contractArguments(amount, address) //****** 3. input.push(spendUTXOAction(utxo)) //****** 4. input.push(spendWalletAction(amount, GetContractArgs().assetDeposited)) //****** 5. if (amount < billAmount) { //****** 6. output.push(controlProgramAction(amount, GetContractArgs().assetDeposited, GetContractArgs().profitProgram)) output.push(controlAddressAction(amount, billAsset, address)) output.push(controlProgramAction((BigNumber(billAmount).minus(BigNumber(amount))).toNumber(), billAsset, GetContractArgs().depositProgram)) } else { output.push(controlProgramAction(amount, GetContractArgs().assetDeposited, GetContractArgs().profitProgram)) output.push(controlAddressAction(billAmount, billAsset, address)) } resolve({ //****** 7 input, output, args, utxo }) } }) }
這個方法比較複雜,咱們一步一步來
這裏先看看 7),最終返回的內容是 input、 output、 args、 utxo, 跟發送交易頁面裏面的input、 output、 args對應起來,如 圖K
(圖K)
上一章說過,全部比原鏈的交易都要存在質量守恆定理,input與output的總和要相對應,合約交易裏面執行方法必須須要參數,這裏的args就表明傳入的參數,utxo是表明 utxo的id
1)作一下限制,設置最少值
2)matchesUTXO ,根據上面的內容,剛剛已經經過listDepositUTXO 拿到全部可用的UTXO列表,這個時候要根據用戶輸入的數額amount,選擇一個起碼 大於或等於的 amount 的UTXO出來;
3)contractArguments ,構建args,就是合約裏面方法的參數;
4)一般合約交易會有本身資產的input,合約UTXO的input,這裏是要解鎖的utxo的input;
5)這個是錢包資產的input;
判斷邏輯同樣,這裏插件錢包跟上一章說的pc錢包接口的結構有所不一樣,可是原理同樣。
最後咱們看看src\components\layout\save\action.js 的updateDatatbaseBalance 方法
function updateDatatbaseBalance(resp, amount, address){ return updateBalances({ "tx_id": resp.transaction_hash, address, "asset": GetContractArgs().assetDeposited, "amount": -amount }).then(()=>{ return updateBalances({ "tx_id": resp.transaction_hash, address, "asset": GetContractArgs().assetBill, "amount": amount }) }).catch(err => { throw err }) } export function updateBalances(params) { let url switch (window.bytom.net) { case "testnet": url = "/dapptestnet/update-balance" break default: url = "/dapp/update-balance" } return post(url, params) }
一樣是調用bufferserver,這裏是調用update-balance方法,把成功後的交易提交到後端。
上面介紹了dapp-demo前端代碼的內容,介紹了裏面的方法,除了插件api的調用比較複雜外,其餘都是普通的應用邏輯調用,主要理解了質量守恆定理,剩下的都是對數據審覈數據的問題,很是簡單。
有應用開發的讀者應該一會兒就能理解到問題核心吧,我如今在說說裏面的坑;
1) UTXO鎖定接口容易被刷; 假如我一個開發人員知道這個接口,狂刷你這個接口狂鎖應用的UTXO,這樣應用長期都會癱瘓狀態;
解決方案:這個應該從應用方面去考慮,譬如接口加一些一次性的驗證碼,加refer的監控,加受權之類的方式,後端加上交易監控,去維持着各類狀況UTXO的狀態,比較抽象,並且比較複雜,不過這是必須的;
2)併發問題;跟1)同樣,就算我是一個正經常使用戶,選擇了一個UTXO解鎖後,竟然經過http接口告訴後端去鎖定,調起 輸入密碼頁面 (圖K),這個時候若是用戶不輸入密碼不提交,在比原鏈上該UTXO是沒有被解鎖,可是bufferserver會卻鎖住了。
解決方案: 後端源碼是鎖定一段時間,若是沒有使用,則按期解鎖出來,這種狀況也是須要應用的監控判斷,維護全部utxo的狀態,我的建議在發合約的時候,發多筆UTXO鎖定合約,可用的UTXO就會變多,這個時候有些同窗問,TPS豈不是也同樣不高,若是用過火幣的同窗就知道了,區塊鏈交易原本就不太注重TPS,並且火幣的交易必需要超過60-100個區塊,才肯定一筆交易,這個看應用開發者如何去判斷,取捨。
3)用戶交易信息接口容易被刷;跟1)同樣,交易完成後,直接經過http接口去提交數據,我狂刷,豈不是億萬富翁....;
解決方案:想要用戶的交易信息,生成交易帳單,能夠直接用插件的接口,不過要經過合約編碼去篩選出來,筆者是經過監控區塊鏈瀏覽器全部交易,進入數據庫交易表的方式,這樣能夠時時刻刻監控因此交易。
4)容易產生鏈式錯誤; 這裏dapp-demo發的是一個合約的UTXO,假如用戶提交交易以後會產生新的UTXO,可是這個UTXO尚未確認的,bufferserver的list-utxo接口會把尚未確認的UTXO,從而解決併發問題,可是我一個開發人員,知道合約的編碼,隨便寫個交易提交了,雖然確定會失敗,可是須要時間,這個時候bufferserver也把這個確定失敗的UTXO返回過來前端,一直鏈式產生一堆交易,很容易產生鏈式失敗。
解決方案:1)這裏我跟比原官方的老大深深討論過,最優方案固然是合約自己設置一個密碼,輸入參數必需要根據這個密碼去加incode密過,傳入合約交易參數,合約自己在解釋的時候,再decode解密驗證,保證出入的參數是官方的,這樣就不會有人攻擊.....不過結論是,暫時比原鏈的合約引擎不支持。
2)必定要隱藏好合約邏輯,其餘人就沒辦法去惡意調用惡意佔用,例如前端代碼混淆,或者args參數是後端生成,另外建議比原的blockcenter的build-transaction接口參數能夠加密這樣,去掩蓋合約邏輯。
PS:這是筆者對於以上問題的思考,有更好的解決方案,歡迎一塊兒討論。
這種內容主要說了前端代碼的源碼分析,還有設計上的邏輯坑,具體的解決方案應該跟官方的開發人員溝通還有討論,區塊鏈的交易原本不追求大併發,可是也須要必定的併發性,筆者在第四章才根據bufferserver內容,在針對上面的問題,作出一些我的看法還有建議。 譯