Triển khai Hợp đồng Thông minh Go trên ChainMaker với SmartIDE

Để bắt đầu xây dựng ứng dụng trên blockchain, người dùng cần truy cập vào giao diện ChainMaker SmartIDE và đăng nhập bằng thông tin tài khoản đã được cấp phát.

Tạo dự án hợp đồng mới

Sau khi xác thực thành công, chọn phiên bản mạng lưới phù hợp (khuyến nghị ưu tiên v2.3.0+ trở lên) để đảm bảo tính tương thích. Tiếp theo, khởi tạo một dự án hợp đồng mới đặt tên là user_profile_contract. Hệ thống sẽ tự động thiết lập các file cấu trúc cơ bản bao gồm main.go, go.modgo.sum.

Xây dựng cấu trúc hợp đồng

Logic của hợp đồng tập trung vào việc lưu trữ và tra cứu thông tin cá nhân. Chúng ta định nghĩa một cấu trúc chính và triển khai các phương thức vòng đời cần thiết như khởi tạo, nâng cấp và thực thi. Mã nguồn mẫu ban đầu có thể được chỉnh sửa như sau:

package main

import (
	"chainmaker/pb/protogo"
	"chainmaker/sandbox"
	"chainmaker/sdk"
	"log"
	"strconv"
)

// UserProfileManager quản lý dữ liệu hồ sơ người dùng
type UserProfileManager struct {
}

// Initialize thực hiện khi hợp đồng được triển khai lần đầu
func (upm *UserProfileManager) Initialize() protogo.Response {
	return sdk.Success([]byte("Chuẩn hóa hợp đồng thành công"))
}

// CallDispatch xử lý các yêu cầu gọi hợp đồng từ bên ngoài
func (upm *UserProfileManager) CallDispatch(methodName string) protogo.Response {
	return sdk.Error("Phương thức không tồn tại")
}

// SystemUpgrade dành cho quá trình nâng cấp phiên bản hợp đồng
func (upm *UserProfileManager) SystemUpgrade() protogo.Response {
	return sdk.Success([]byte("Nâng cấp hợp đồng thành công"))
}

func main() {
	err := sandbox.Start(new(UserProfileManager))
	if err != nil {
		log.Fatal(err)
	}
}

Hiện thực chức năng nghiệp vụ

Cần thêm hai hàm chính: một để ghi dữ liệu (updateProfile) và một để đọc dữ liệu (readProfile). Các hàm này sẽ tương tác với trạng thái chuỗi (State) thông qua SDK.

// updateProfile ghi thông tin tuổi cho người dùng
func (upm *UserProfileManager) updateProfile() protogo.Response {
	params := sdk.Instance.GetArgs()
	
	userID := string(params["userID"])
	ageValue := string(params["age"])
	
	// Kiểm tra tham số đầu vào
	if userID == "" || ageValue == "" {
		sdk.Instance.Infof("Thiếu tham số bắt buộc")
		return sdk.Error("Tham số không hợp lệ")
	}

	// Chuyển đổi kiểu dữ liệu kiểm tra
	if _, err := strconv.Atoi(ageValue); err != nil {
		sdk.Instance.Infof("Không thể chuyển đổi số tuổi: %v", err)
		return sdk.Error("Giá trị tuổi sai định dạng")
	}

	// Lưu vào chuỗi
	if err := sdk.Instance.PutStateFromKey(userID, ageValue); err != nil {
		sdk.Instance.Infof("Lỗi lưu trữ dữ liệu: %v", err)
		return sdk.Error("Ghi thất bại")
	}

	return sdk.Success([]byte("Lưu dữ liệu thành công"))
}

// readProfile lấy thông tin tuổi của người dùng
func (upm *UserProfileManager) readProfile() protogo.Response {
	params := sdk.Instance.GetArgs()
	userID := string(params["userID"])
	
	if userID == "" {
		sdk.Instance.Infof("Thiếu ID người dùng")
		return sdk.Error("-1")
	}

	data, err := sdk.Instance.GetStateFromKey(userID)
	if err != nil {
		sdk.Instance.Logf("Lỗi truy xuất dữ liệu: %v", err)
		return sdk.Error("-1")
	}

	if data == "" {
		sdk.Instance.Infof("Không tìm thấy thông tin")
		return sdk.Error("-1")
	}

	return sdk.Success([]byte(data))
}

Kết nối các phương thức điều phối

Bước quan trọng là kết nối các hàm nghiệp vụ với phương thức điều phối tổng (CallDispatch). Sử dụng câu lệnh switch để định tuyến tên phương thức đến đúng hàm thực thi tương ứng.

func (upm *UserProfileManager) CallDispatch(methodName string) protogo.Response {
	switch methodName {
	case "updateProfile":
		return upm.updateProfile()
	case "readProfile":
		return upm.readProfile()
	default:
		return sdk.Error("Tên phương thức chưa được hỗ trợ")
	}
}

Mã nguồn hoàn chỉnh tích hợp mở rộng

Dưới đây là phiên bản đầy đủ bao gồm cả các chức năng nâng cao như gọi chéo hợp đồng và lặp lại lịch sử dữ liệu:

package main

import (
	"fmt"
	"chainmaker/pb/protogo"
	"chainmaker/sandbox"
	"chainmaker/sdk"
	"log"
	"strconv"
)

type ProfileHandler struct{}

func (ph *ProfileHandler) Init() protogo.Response {
	return sdk.Success([]byte("Khởi tạo thành công"))
}

func (ph *ProfileHandler) Execute(methodName string) protogo.Response {
	switch methodName {
	case "saveRecord":
		return ph.saveRecord()
	case "findRecord":
		return ph.findRecord()
	case "callNeighbor":
		return ph.callNeighbor()
	case "iterateHistory":
		return ph.iterateHistory()
	default:
		return sdk.Error("Phát hiện lỗi phương thức")
	}
}

func (ph *ProfileHandler) Update() protogo.Response {
	return sdk.Success([]byte("Cập nhật hệ thống"))
}

func (ph *ProfileHandler) saveRecord() protogo.Response {
	args := sdk.Instance.GetArgs()
	key := string(args["key"])
	val := string(args["val"])
	
	if key == "" || val == "" {
		return sdk.Error("Dữ liệu thiếu")
	}

	if _, err := strconv.Atoi(val); err != nil {
		return sdk.Error("Định dạng số sai")
	}

	if err := sdk.Instance.PutStateFromKey(key, val); err != nil {
		sdk.Instance.Errorf("Lỗi put state: %s", err.Error())
		return sdk.Error("Lỗi ghi dữ liệu")
	}
	return sdk.Success([]byte("OK"))
}

func (ph *ProfileHandler) findRecord() protogo.Response {
	args := sdk.Instance.GetArgs()
	key := string(args["key"])
	if key == "" {
		return sdk.Error("Key rỗng")
	}

	res, err := sdk.Instance.GetStateFromKey(key)
	if err != nil || res == "" {
		return sdk.Error("-1")
	}
	return sdk.Success([]byte(res))
}

func (ph *ProfileHandler) callNeighbor() protogo.Response {
	targetContract := "profile_contract"
	method := "findRecord"
	callerArgs := map[string][]byte{
		"key": []byte("TestUser"),
	}
	
	resp := sdk.Instance.CallContract(targetContract, method, callerArgs)
	sdk.Instance.EmitEvent("giao dịch chéo", []string{"hoàn tất"})
	return resp
}

func (ph *ProfileHandler) iterateHistory() protogo.Response {
	searchKey := "TestUser"
	iter, err := sdk.Instance.NewHistoryKvIterForKey(searchKey, "")
	if err != nil {
		return sdk.Error("Tạo iterator thất bại")
	}

	for iter.HasNext() {
		nextVal, err := iter.Next()
		if err != nil {
			return sdk.Error("Lỗi duyệt dữ liệu")
		}
		sdk.Instance.Log(fmt.Sprintf("Giá trị lịch sử: %v", nextVal))
	}
	return sdk.Success([]byte("Hoàn tất duyệt lịch sử"))
}

func main() {
	if err := sandbox.Start(new(ProfileHandler)); err != nil {
		log.Panic(err)
	}
}

Hướng dẫn gỡ rối và kiểm thử

Sau khi lưu mã nguồn, chuyển sang chế độ gỡ rối (Debug) trong bảng điều khiển bên trái. Chọn dự án hợp đồng tương ứng và phương thức cần kiểm tra (ví dụ: saveRecord). Nhập các cặp khóa-giá trị (key-value) tương ứng với tham số đầu vào của hàm, sau đó nhấn nút构建 và chạy để thực thi trên môi trường mô phỏng. Nếu thành công, giao diện sẽ hiển thị thông báo trả về tương ứng. Khi gọi hàm findRecord, kết quả sẽ phản ánh dữ liệu vừa được lưu trước đó.

Thẻ: ChainMaker GoSmartContract SmartIDE BlockchainDevelopment HyperledgerFabric

Đăng vào ngày 7 tháng 6 lúc 20:00