如果你想知道在EOS上部署了什么版本的智能合約,你需要查看代碼哈希。我們將看到如何計算代碼和ABI哈希,并編寫一個函數,通過比較它們的哈希來查看本地WASM文件是否與正在運行的協議相匹配。
EOS代碼的哈希
當通過eosio
setcode
操作設置或更新合約時,檢查合約代碼是否已經在運行。因此,通過查看setcode
實現,我們可以從WASM文件看到如何計算哈希值。
void apply_eosio_setcode(apply_context& context) {
// some setup code
fc::sha256 code_id; /// default ID == 0
if( act.code.size() > 0 ) {
code_id = fc::sha256::hash( act.code.data(), (uint32_t)act.code.size() );
wasm_interface::validate(context.control, act.code);
}
const auto& account = db.get<account_object,by_name>(act.account);
int64_t code_size = (int64_t)act.code.size();
int64_t old_size = (int64_t)account.code.size() * config::setcode_ram_bytes_multiplier;
int64_t new_size = code_size * config::setcode_ram_bytes_multiplier;
EOS_ASSERT( account.code_version != code_id, set_exact_code, "contract is already running this version of code" );
// ...
}
這只是一個簡單的WASM字節表示的sha256哈希值。(第二個參數只是字節數組的長度,哈希函數需要它來知道要對多少字節進行哈希處理。)
在node.js中,我們可以通過哈希一個WASM文件并將其與區塊鏈上代碼的哈希值進行比較來輕松實現這一點。
const fs = require(`fs`)
const crypto = require(`crypto`)
const loadFileContents = file => {
if (!fs.existsSync(file)) {
throw new Error(`Code file "${file}" does not exist.`)
}
// no encoding => read as Buffer
return fs.readFileSync(file)
}
const createHash = contents => {
const hash = crypto.createHash(`sha256`)
hash.update(contents)
const digest = hash.digest(`hex`)
return digest
}
// fetch code contract from blockchain
const { code_hash: onChainCodeHash, abi_hash } = await api.rpc.fetch(`/v1/chain/get_raw_abi`, {
account_name: `hello`,
})
const contents = loadFileContents(`contracts/hello.wasm`)
const codeHash = createHash(contents)
if (codeHash === onChainCodeHash) {
console.log(`Code is up-to-date.`)
}
get-raw-abi
函數是一個很好的API端點,可以通過一個查詢同時獲取帳戶的代碼和abi哈希。
注意,WASM文件和代碼哈希依賴于編譯期間使用的eosio cpp
版本和-o
優化參數。來自同一個C++代碼,代碼哈希可能是不同的。
EOS ABI 的哈希
我們可以嘗試同樣的方法來計算ABI哈希,但是,由于某種原因,eosio setabi
操作不檢查ABI哈希,因此允許使用相同的哈希進行更新。
但是get-raw-abi-api
端點返回一個abi哈希,因此必須從某個地方獲取它。通過檢查nodeos-chain
插件,我們可以看到它是為每個請求動態計算的:
read_only::get_raw_abi_results read_only::get_raw_abi( const get_raw_abi_params& params )const {
get_raw_abi_results result;
result.account_name = params.account_name;
const auto& d = db.db();
const auto& accnt = d.get<account_object,by_name>(params.account_name);
result.abi_hash = fc::sha256::hash( accnt.abi.data(), accnt.abi.size() );
result.code_hash = accnt.code_version;
if( !params.abi_hash || *params.abi_hash != result.abi_hash )
result.abi = blob{{accnt.abi.begin(), accnt.abi.end()}};
return result;
}
計算結果與ABI字節表示的代碼哈希 - SHA256 完全相同。然而,實際存儲ABI的方式有一個很大的區別。它不是作為熟悉的JSON文件存儲的,而是作為EOS稱之為原始ABI的打包方式存儲的。
從raw abi轉換為json很容易使用eosjs
,但是從json轉換為raw abi需要一些nb的操作:
const {Serialize, Api} = require(`eosjs`)
const {TextEncoder, TextDecoder} = require(`util`) // node only; native TextEncoder/Decoder
const jsonToRawAbi = json => {
const tmpApi = new Api({
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(),
})
const buffer = new Serialize.SerialBuffer({
textEncoder: tmpApi.textEncoder,
textDecoder: tmpApi.textDecoder,
})
const abiDefinition = tmpApi.abiTypes.get(`abi_def`)
// need to make sure abi has every field in abiDefinition.fields
// otherwise serialize throws
const jsonExtended = abiDefinition.fields.reduce(
(acc, {name: fieldName}) => Object.assign(acc, {[fieldName]: acc[fieldName] || []}),
json,
)
abiDefinition.serialize(buffer, jsonExtended)
if (!Serialize.supportedAbiVersion(buffer.getString())) {
throw new Error(`Unsupported abi version`)
}
buffer.restartRead()
// convert to node buffer
return Buffer.from(buffer.asUint8Array())
}
每個ABI必須包含一組特定的字段,如version
、types
、structs
、actions
、tables
,即版本、類型、結構、操作、表等,然后將這些字段序列化為更大的有效表示形式。
計算ABI哈希并用鏈上的值檢查它是很簡單的:
const contents = loadFileContents(`contracts/hello.abi`)
const abi = JSON.parse(contents.toString(`utf8`))
const serializedAbi = jsonToRawAbi(abi)
const abiHash = createHash(serializedAbi)
// fetch abi hash from blockchain
const { code_hash, abi_hash: onChainAbiHash } = await api.rpc.fetch(`/v1/chain/get_raw_abi`, {
account_name: `hello`,
})
if (abiHash === onChainAbiHash) {
console.log(`ABI is up-to-date.`)
return null
}
======================================================================
分享一些比特幣、以太坊、EOS等區塊鏈相關的交互式在線編程實戰教程:
- EOS入門教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最后綜合運用各知識點完成一個便簽DApp的開發。
- 深入淺出玩轉EOS錢包開發,本課程以手機EOS錢包的完整開發過程為主線,深入學習EOS區塊鏈應用開發,課程內容即涵蓋賬戶、計算資源、智能合約、動作與交易等EOS區塊鏈的核心概念,同時也講解如何使用eosjs和eosjs-ecc開發包訪問EOS區塊鏈,以及如何在React前端應用中集成對EOS區塊鏈的支持。課程內容深入淺出,非常適合前端工程師深入學習EOS區塊鏈應用開發。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能,例如創建地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如創建地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- c#比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在C#代碼中集成比特幣支持功能,例如創建地址、管理錢包、構造裸交易等,是C#工程師不可多得的比特幣開發學習課程。
- java以太坊開發教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智能合約開發交互,進行賬號創建、交易、轉賬、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智能合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- ERC721以太坊通證實戰,課程以一個數字藝術品創作與分享DApp的實戰開發為主線,深入講解以太坊非同質化通證的概念、標準與開發方案。內容包含ERC-721標準的自主實現,講解OpenZeppelin合約代碼庫二次開發,實戰項目采用Truffle,IPFS,實現了通證以及去中心化的通證交易所。
- C#以太坊,主要講解如何使用C#開發基于.Net的以太坊應用,包括賬戶管理、狀態與交易、智能合約開發與交互、過濾器和交易等。
- Hyperledger Fabric 區塊鏈開發詳解,本課程面向初學者,內容即包含Hyperledger Fabric的身份證書與MSP服務、權限策略、通道配置與啟動、鏈碼通信接口等核心概念,也包含Fabric網絡設計、nodejs鏈碼與應用開發的操作實踐,是Nodejs工程師學習Fabric區塊鏈開發的最佳選擇。
- Hyperledger Fabric java 區塊鏈開發詳解,課程面向初學者,內容即包含Hyperledger Fabric的身份證書與MSP服務、權限策略、頻道配置與啟動、鏈碼通信接口等核心概念,也包含Fabric網絡設計、java鏈碼與應用開發的操作實踐,是java工程師學習Fabric區塊鏈開發的最佳選擇。
- tendermint區塊鏈開發詳解,本課程適合希望使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操代碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這里是如何計算EOS代碼和ABI的哈希