Transaction, ContractMethods & Provider In Dapp

ChiHaoLu
Taipei Ethereum Meetup
14 min readApr 14, 2022

這是一篇關於 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

Article 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() 部分呼叫它!

初始化後運作畫面如下:

Problems

為了模擬某些現實開發情況,這邊一開始不是直接呼叫 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

Demo

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

--

--

ChiHaoLu
Taipei Ethereum Meetup

Multitasking Master & Mr.MurMur | Blockchain Dev. @ imToken Labs | chihaolu.me