5

Transaction, ContractMethods & Provider In Dapp

 2 years ago
source link: https://medium.com/taipei-ethereum-meetup/transaction-contractmethods-provider-in-dapp-cba7c8f8d599
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Transaction, ContractMethods & Provider In Dapp

這是一篇關於 Transaction、Smart Contract Function 和 Provider 三角愛恨情仇的故事。

🚀 Table of Contents

  • 🤩 Intro.
  • 🔎 Cast an eye over the Underlying Arch.
  • 🍷 “Tasting” Time — Deploy a Contract in Client
  • 🍩 Final

Link Tree

Aricle Quick Look

  1. 何謂交易(Transaction, Tx),以及交易的型態和性質
  2. Web3.js 中關於交易的 High / Low Level Function
  3. 透過各種寫法在 Dapp 中以交易完成與區塊鏈互動的行為(呼叫合約函式、部署合約)
  4. 剖析為何在節點商的限制下,程式碼會沒辦法達到傳送交易等目的
  5. 以錢包呼叫 JSON-RPC 的方式還原真實情況

🤩 Intro.

Motivation

在我前陣子開發 Dapp 的時候,使用 Infura 還有 Alchemy 作為 Provider 出現了不少錯誤,耗費不少時間才搞懂了一些很基本關於 Ethereum Transaction 的內容,因此想要和大家分享我在部署 Dapp 之後才發現的關於「交易」這件事的趣聞跟整理!

Subject

以「製造一筆交易(transaction object)的方式」反向模仿我們過往欲送出的任何高階 API 函式(包裝過的交易),再來送出這筆「人造交易」來達成原先目的。

這句話乍聽之下好像是在繞口令,但其實我是在於 Dapp 中呼叫合約函式和從客戶端的角度佈署合約時想到的。雖然廣義來說,就藉由「交易」的方式和智能合約互動而言,我們一直以來都是如此與 Dapp 互動的。

當與合約的函式(Writing Function)互動時會造成一筆交易並且更改區塊鏈上的「狀態(state)」,佈署合約也是同樣的道理:將 bytecode 和相關 constructor 的參數一起包裝成交易送上區塊鏈。

也許大家會很好奇這不是已經有 web3.eth.contract.methodsmyContract.deploy 之類的函式可以直接使用了嗎。但其實在節點商有相關的限制時,以及開發者並「沒有客戶的私鑰( Mnemonic)或 Passphrase」的情況下,有一些事情是做不到的!

Passphrase 可以控制所有 Wallet 中的 Private Key,兩者不完全相同但都是管理 / 復原一個 Wallet 的重要字串組。

在自己的 Command Line 或開發工具(Remix, Truffle, Hardhat)上單純連到 Ganache 或 MetaMask 使用 JSON-RPC 時一切是那麼的理所當然,但如果要把這些事情放在 Client 端執行時稍不注意就會出現問題。

🔎 Cast an eye over the Underlying Arch.

在開始實作之前我想再多分享一點關於交易(Transaction, Tx)和呼叫函式的內容,以方便後面我不用解釋太多程式碼的實作部分😂

Transaction

交易是由一個帳戶(EOA)傳遞資訊(Ether, data 或 payload)給另外一個帳戶(EOA, Contract, NULL)的動作。有三種型態:

  • 部署合約(不具有 to: 以及在 data: 中包含 Contract Code)
  • 執行合約函式:(to: <contractAddress>data: <inputData>

當交易的目標帳戶(to: <contractAddress>)包含了程式碼(部分合約),那我們就可以把 payload(附帶在交易裡面的 data)當作函式 input 的內容傳入。

當交易的目標帳戶(to: null)為空時,那這個交易將會被視為是創建合約的部署行為。也就是說當要判斷一個交易是否為創建合約時,可以藉由 getTransactionReceipt(<txHash>) 來判斷其 return 中的參數 contractAddress 是否是 null

此外,web3.eth.getcode() 能調用這個地址裡面的 EVM bytecode,如果其不是一個合約地址也會回傳 0x0

需要注意的是不是部署完成就會把 code 放入,需要等 contract 的 constructor 實際被執行(參數完整的情況)才可以真正呼叫到他的程式碼。有機率會因為節點商跟瀏覽器(Etherscan)的反應速度,而導致這個函式回傳 null

我們與合約互動之後會對程式碼裡面的「變數」做更改,換個角度來說,當我們呼叫合約函式以後會生成的 output data 也會被接續著記在這個 contract 之中,成為它的一部分,永遠被儲存在區塊鏈之上。

Call vs. Transaction

在 Dapp 中使用 web3.js 呼叫合約中的函式有幾種方法,他們都有不同的作用和執行結果,這邊我想特別比較一下 web3.eth.callweb3.eth.sendTransaction

此處的 web3.eth.call 和 Smart Contract 中 Solidity 語法<address>.call() 是不同的!

請記住這裡的 Underlying JSON-RPC eth_sendTransaction,我們之後會用到他!

eth.Contract vs. eth.sendTransaction

如同前文所提到的,實際上 .Contract.methods.Contract.deploy 都是創建一個 transaction object 並且送出(sign + send),也都是 wrapper of .sendTransaction

合約函式互動的比較:

創建合約的比較:

比較重要的是當想要使用 .sendTransaction 來作為:

  • 創建合約的交易時,data field 需要填上 initialisation code
    其中 initialisation code 便是合約編譯完之後得到的 bytecode。
  • 與合約函式互動的交易時,data field 需要填上 ABI byte string
    其中 ABI byte string 可以以以下方法得到:

🍷 “Tasting” Time — Deploy a Contract in Client

Initialization & Preparation

接下來到了實際測試的部分,目標是在客戶端讓用戶 Deploy 合約!

首先我們先對以下智能合約編譯取得 BytecodeABI,如何在客戶端進行編譯也是一個課題,未來有機會我再跟大家分享!

接下來簡單的建置一個 React 前端框架,僅僅是在 App.js 裡面運作而已!

其中 web3 = new Web3(infuraapi_url); 將會使用參數中的 API Key 來做為 web3 Provider,下文中我們將會在程式碼中省略的 handleDeploy_x() 部分呼叫它!

初始化後運作畫面如下:

Probelms

為了模擬某些現實開發情況,這邊一開始不是直接呼叫 web3.js 的初始化 Provider,所以已經熟悉 Dapp 建置的前輩們可能會覺得刻意用導入 API Key 的方式來呈現錯誤有點奇怪😥

先嘗試用最直觀的方法來 Deploy 一個合約:

當我們將 onClick 的函式 handleDeploy_x() 設為 handleDeploy1() 時,不同的節點商測試下會回傳以下錯誤:

  • alchemyapi_url:

😥 — Something wrong: Returned error: Unsupported method: eth_sendTransaction. Alchemy does not hold users’ private keys. See available methods at https://docs.alchemy.com/alchemy/documentation/apis

  • infuraapi_url:

😥 — Something wrong: Returned error: The method eth_sendTransaction does not exist/is not available

handleDeploy2() 取代 handleDeploy_x() 試試看更 low-level 的呼叫方式來創建合約:

也可以看見和以上一模一樣的錯誤。

Unlock

過去練習使用 Geth 在私有鏈上發起交易的時候,會發現在送出一筆交易之前需要將 account unlock 才能 sign 最後 send,然而這個動作需要我們的密碼(Passphrase)!

web3.js 中的 Unlock

注意在任何地方 unlockAccount 都是很危險的事情,所以請三百思而後行!

對應其底層的原始碼

unlock 的作用在於輸入欲解鎖帳戶的密碼(或獲得 keystore 的 JSON File),並將其解密成私鑰儲存在 memory 中,然後這段時間所有交易都會被這個私鑰 sign

由於節點商「不該」獲得客戶的私鑰,因此在缺乏錢包(節點媒介)的情況下,我們沒辦法讓客戶安全地去執行 unlock 這個動作,更不應該在 Dapp 中要求客戶使用 web3.personal.unlockAccount

Solution

於是乎我們必須要把整個 unlock 到發起交易並執行後的整個過程包裝起來變成一筆交易,這樣只要在 Dapp 設計一個按鈕,在 onClick 之後讓 Client 在 Metamask Sign 這筆交易就可以跨過這些阻礙了!

這件事情單純在前端透過 signTransaction() + sendSignedTransaction() 是做不到的,因為 signTransaction() 依然有一個參數是 privateKey。所以需要使用第三方的 Metamask(或其他節點媒介、錢包,或說私鑰管理器) 來確保整個流程是在一個安全的 HTTP RPC connection 情況下 unlock Account。

Implementation

在 Metamask(以及其他有提供類似功能的瀏覽器擴充插件錢包、私鑰管理器) 於以下 EIP 的架構中提供了 Ethereum JSON-RPC API 的呼叫方法,因此開發者可以讓使用者以 MetaMask 登入 Dapp 並進行交易簽核。

Image From Free Trade: Composable Smart Contracts

這是一個以類似 Web2 世界中使用 FB、APPLE、GOOGLE 登入其他網站的方法。欲登入的網站並不會直接得到我們的社群媒體密碼,只會得到第三方網站提供「確認」登入者是正確的人的驗證回覆,還有相關個人訊息(頭貼、帳號、公開資訊、好友名單等)。

在 Chrome 已經安裝 MetaMask 時(或具有其他擴充應用程式作為 Provider)便可以使用全域變數 window.ethereum.request({ method: "eth_requestAccounts" })

和使用以下 JSON-RPC API 作為 method field 的指令:

  • eth_accounts
  • eth_call
  • eth_getBalance
  • eth_sendTransaction
  • eth_sign

我們來用這個方法進行最後一搏,實際上這裡已經用不到節點商的 API Key 了,因為現在是靠 Metamask 這個媒介來完成所有行為!

成功部署了!

在 Etherscan 也可以看見 Input Data: 中有當初連帶著交易上傳的 Bytecode:

Ordinary Situation

繞了一大圈還是回到原點,把情況拉回最日常的模樣:為何我們平常可以使用 web3.js 而不會出現錯誤?

原因其實是來自:在預設中 web3.js 的 Provider 可以被設定成是瀏覽器中 web3 的 currentProvider,也就是看當前有誰能就是誰,而這個人即為 window.ethereum,換個方式呈現 web3 = new Web3(window.web3.currentProvider);,通常是當前瀏覽器中的錢包!

使用 window.ethereum 作為 Provider 以後,我們就可以正常的使用交易了!

Others

而市面上的桶裝 Wallet Login System,例如 Web3Modal。可能會有一個窮舉支援錢包的部分,以下程式碼取自 Ethereum StackExchange

當使用其他錢包的時候可能會因為 window.ethereum 不支援或有更好用的東西而選擇實作其他方法,例如使用 CoinbaseWalletSdk,或導入錢包背後的 RPC 以及 infuraId 來直接連動。

在其他鏈上進行相同操作的某些時候也可以同理應用,舉例來說可以使用 window.BinanceChain 來取得 Binance 錢包的 JSON-RPC 簽核途徑,以此來完成類似上文的目的,例如 Web3Modal - providers/connectors/binancechainwallet.ts:。

🍩 Final

Conclusion

這篇文章的誕生真的要感謝幫忙校稿和給予建議的所有前輩們!尤其是非常感謝智程老師、昶吾老師、陳品老師、雅文老師,超級無敵宇宙霹靂多的幫助!

Reference

Web3.js Documentation

Metamask Docs

最後歡迎大家拍打餵食窮苦大學生0x2b83c71A59b926137D3E1f37EF20394d0495d72d 😥


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK