Sau đây là một số rủi ro liên quan đến chaincode được đề cập đến trong bài báo “Potential Risks of Hyperledger Fabric Smart Contracts”1:
Non-determinism Arising From Language Instructions
Sự tất định (determinism) là sự đồng nhất về kết quả thực thi của chương trình với cùng một hoặc nhiều giá trị đầu vào giống nhau cho những lần thực thi khác nhau. Do chaincode được thực thi ở trong các nút mạng phân tán và độc lập nên việc đảm bảo sự tất định của các logic nghiệp vụ là cần thiết nhằm đạt được sự đồng thuận ở trong mạng chuỗi khối. Nếu chaincode là bất định thì nó sẽ làm cho các kết quả chứng thực từ các endorsing peer không đồng nhất.
Một số ngôn ngữ miền chuyên biệt (Domain Specific Languages - DSLs) chẳng hạn như Solidity không chứa các chỉ thị (instruction) liên quan đến các số ngẫu nhiên nhằm đảm bảo tính tất định. Tuy nhiên, các ngôn ngữ đa mục đích (General-Purpose Languages) lại có những chỉ thị đó. Một giải pháp đã được đề ra nhằm lọc các thao tác bất định trong blockchain nhưng chi phí áp dụng trong thực tế là khá cao2.
Sau đây là một số vấn đề về sự bất định xuất phát từ bản chất cố hữu của các ngôn ngữ đa mục đích:
Global Variable
Giá trị của các biến toàn cục luôn có thể bị thay đổi và điều này có thể dẫn đến sự không đồng nhất giữa các kết quả thực thi.
Trong đoạn code dưới, giả sử cấu hình và hành vi của chaincode phụ thuộc vào biến toàn cục globalConfigValue
. Ta thiết lập giá trị cho globalConfigValue
bằng cách gọi hàm setConfigValue()
. Tuy nhiên, không có gì đảm bảo rằng giá trị truyền vào là cố định khi chaincode được thực thi bởi các peer khác nhau. Điều này có thể dẫn đến kết quả đầu ra của các peer là khác nhau.
// Non-deterministic chaincode example using global configuration
var globalConfigValue int
// This function sets a configuration value
func setConfigValue(value int) {
globalConfigValue = value
}
// Chaincode function that relies on the configuration
func getConfigValue() int {
return globalConfigValue
}
Random Number Generation
Việc sinh số ngẫu nhiên chính là ví dụ điển hình của sự bất định trong code. Trong giai đoạn chứng thực, các endorsing peer sẽ thực hiện giả lập thực thi chaincode một cách độc lập và do đó kết quả thực thi có thể không đồng nhất. Ví dụ bên dưới sinh số ngẫu nhiên để chọn ra duy nhất một người thắng cuộc trong trò chơi sổ xố:
// Something like a lottery application
// Users predict a number
// User’s prediction
pred := arg[0]
// Answer
rand.Seed(seed)
sel := rand.Intn(10)
if pred == sel {
PayPrizeToUser(user, prize)
}
Có thể thấy, giá trị của sel
sẽ là khác nhau giữa các peer khác nhau. Dẫn đến, chaincode sẽ không đảm bảo được rằng chỉ có duy nhất một người chơi thắng cuộc. Điều này làm sai lệch logic nghiệp vụ của chaincode.
System Timestamp
Tương tự với trường hợp sinh số ngẫu nhiên, không có gì bảo đảm rằng timestamp được sử dụng trong chaincode mang giá trị giống nhau khi được thực thi bởi các peer khác nhau.
Map Structure Iteration
Theo đặc điểm kỹ thuật của Go, thứ tự của các key của map ở mỗi lần lặp là không giống nhau. Do đó, nếu chaincode phụ thuộc vào thứ tự của các key này thì có thể dẫn đến sự bất định.
Reified Object Addresses
Nếu developer sử dụng các giá trị vùng nhớ gán cứng ở trong chaincode thì cũng có thể dẫn đến sự bất định bởi vì các giá trị này phụ thuộc vào hệ thống của từng peer.
Concurrency of Program
Các ngôn ngữ như Go hoặc Java hỗ trợ lập trình đồng thời (concurrency programming). Nếu không xử lý tốt, race condition có thể xảy ra.
Non-determinism Arising From Accessing Outside of Blockchain
Các rủi ro sau cũng gây ra sự bất định cho chaincode nhưng đến từ các tác nhân bên ngoài blockchain.
- Web service: khi logic nghiệp vụ cần thông tin ở bên ngoài blockchain, chaincode sẽ sử dụng các oracle để gọi đến API của các bên thứ 3. Nếu API trả về kết quả cho các peer là khác nhau thì sẽ dẫn đến sự bất định.
- Sử dụng các thư viện bên ngoài: tương tự với việc gọi đến các web service. Developer cần phải hiểu rõ hành vi và cách sử dụng của các thư viện bên ngoài để tránh việc có nhiều kết quả thực thi khác nhau giữa các peer.
- Thực thi các câu lệnh hệ thống: một số ngôn ngữ lập trình chẳng hạn như Go cho phép gọi thực thi các câu lệnh của hệ thống. Không có gì bảo đảm rằng kết quả của các câu lệnh này là giống nhau khi được thực thi trên các peer khác nhau.
- Truy cập đến các file của hệ thống: tương tự như khi thực thi các câu lệnh của hệ thống.
State Database Specification
Các rủi ro liên quan đến đặc điểm kỹ thuật của state database:
- Hyperledger Fabric cung cấp các phương thức truy vấn theo khoảng (range query methods) chẳng hạn như
GetQueryResult()
hoặcGetPrivateDataQueryResult()
. Các phương thức này sẽ không được tái thực thi trong lúc peer thực hiện validation. Điều này đồng nghĩa với việc các phantom read3 có thể không được phát hiện. Các phantom read sẽ đọc cả dữ liệu mà các transaction khác thêm hoặc xóa và sẽ thay đổi kết quả của cả quá trình.
Fabric Specification
Các rủi ro đến từ đặc điểm kỹ thuật của chính Hyperledger Fabric. Các rủi ro này sẽ được sửa trong tương lai:
Field Declarations
Rủi ro này đến từ các chaincode được viết bằng Go. Cụ thể, nhà phát triển cần phải cài đặt phương thức Init()
và Invoke()
khi viết chaincode bằng Go. Khi các phương thức này được cài đặt cho một structure có chứa biến toàn cục, chương trình sẽ chứa rủi ro.
Ví dụ minh họa:
type BadChaincode struct {
globalValue string // this is a risk
}
func (t *BadChaincode) Invoke (stub shim.ChaincodeStubInterface) peer.Response {
t.globalValue = args[0]
return shim.Success([]byte("success"))
}
Read Your Write
Trong lĩnh vực hệ thống phân tán, tính nhất quán Read-Your-Write định nghĩa rằng giá trị được ghi bởi một tiến trình P lên dữ liệu X sẽ luôn khả dụng đối với thao tác đọc sau đó bởi chính tiến trình P.
Trong Hyperledger Fabric, tính chất này không được hỗ trợ. Cụ thể, nếu một transaction cập nhật giá trị của một key rồi đọc giá trị của key này, kết quả nhận lại sẽ luôn là giá trị của trạng thái đã được cam kết (committed state) trước đó thay vì là giá trị mới được cập nhật.
// At the initial point: {key: "key", value: 0}
val := 1
// Update the value from 0 to 1
err := stub.PutState("key", val)
if err != nil {
fmt.Printf("Error is happened. %s", err)
}
// The method returns 0, not 1
ret, err := stub.GetState("key")
if err != nil {
fmt.Printf("Error is happened. %s", err)
}
Summary
Đa số các rủi ro trong smart contract của Hyperledger Fabric là trực quan và tường minh, không như lỗ hổng reentrancy của Solidity. Tuy nhiên, những tính năng gây ra các rủi ro trên thường được sử dụng trong phát triển phần mềm và các lập trình viên nên ý thức được các rủi ro này trong khi phát triển smart contract.
Related
list
from [[Potential Risks of Hyperledger Fabric Smart Contracts]]
sort file.ctime asc
Resources
Footnotes
-
các rủi ro dưới được thu thập chủ yếu từ các vấn đề thảo luận ở trên GitHub cùng với tài liệu chính thức của Hyperledger Fabric và các bài blog. ↩
-
tham khảo https://doi.org/10.48550/arXiv.1603.07351 ↩
-
phantom read là vấn đề phát sinh khi hai lần truy vấn liên tiếp của transaction cho ra hai kết quả khác nhau bởi vì có một transaction khác đã thực hiện thay đổi dữ liệu. Tại sao một transaction lại truy vấn hai lần? Có thể là transaction thực hiện logic nghiệp vụ trên kết quả của lần đọc thứ hai dựa trên kết quả của lần đọc đầu tiên. ↩