Bài 4. Hoàn thiện việc ký giao dịch bằng API trong CosmosSDK
Bài đăng này đã không được cập nhật trong 4 năm
Ở bài trước mình đã hướng dẫn các bạn viết rest-server để người dùng có thể tại giao dịch bằng API, nhưng vẫn còn một vấn đề là hiện tại người dùng vẫn phải dùng command-line để ký và broadcast giao dịch. Hôm nay, ở bài cuối cùng này mình sẽ hướng dẫn các bạn cách để người dùng có thể ký và broadcast giao dịch bằng API, góp phần làm trong suốt luồng ứng dụng bên dưới đối với người dùng hơn.
1. Tìm hiểu về lệnh tx sign
và cách lấy thông tin của user từ hệ thống
Quay lại bài trước, khi người dùng tạo một giao dịch, thì giao dịch đó mới chỉ ở dạng proposal, người dùng phải ký và broadcast nó thì nó mới có hiệu lực.
Câu lệnh tx sign
có cấu trúc như sau:
nscli tx sign unsignedTx.json --from alice --offline --chain-id namechain --sequence 0 --account-number 4
xem thêm source code của câu lệnh tại đây. Chú ý vào các tham số quan trọng sau:
- Tham số ngay phía sau
sign
bắt buộc phải là một file, trong trường hợp này làusignedTx.json
- Tham số sau flag
--from
hoặc là tên hoặc là địa chỉ của user tạo giao dịch - Tham số sau flag
--sequence
tăng dần từ 0 sau mỗi giao dịch của người đó dù giao dịch có thành công hay không, ý nghĩa của tham số này là giúp hệ thống có thể sắp xếp các giao dịch đúng thứ tự, tránh bị double spending. - Tham số ngay sau flag
--accountn-number
là số thứ tự của user đó trong hệ thống
Như vậy muốn người dùng có thể ký giao dịch bằng API, thì trong request mà user gửi lên phải có: giao dịch chưa được ký, tên hoặc địa chỉ của user đó, sequence, account-number.
Các tham số địa chỉ, sequence, account-number là các tham số mà được hệ thống tính toán và cấp cho user, người dùng không thể biết trước nó được, thế nên đầu tiên mình sẽ viết một route để người dùng có thể query được địa chỉ của mình thông qua tên, từ đó lại dùng tiếp route mặc định http://localhost:1317/auth/accounts/$address
để query về tiếp các tham số sequence
, account-number
, cũng như số dư token của user đó.
Mở file x/nameservice/client/rest/query.go
và thêm các đoạn sau đây:
- Import package
os/exec
vào đầu file để có thể thực thi lệnh.
import (
...
"os/exec"
...
)
- Thêm hàm xử lý request dưới đây
func accAddressHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
cmd := exec.Command("nscli", "keys", "show", name, "-a") // chạy ngầm câu lệnh show key của một name
stdout, err := cmd.Output() // bắt giá trị trả về
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
address := string(stdout) // ép kiểu về string
result := strings.Trim(address, "\n") // cắt bỏ \n
rest.PostProcessResponse(w, cliCtx, result) // trả về kết quả cho ngươi dùng
}
}
Thêm một route bên file x/nameservice/client/rest/rest.go
để gọi đến hàm xử lý ở trên:
r.HandleFunc(fmt.Sprintf("/%s/names/{name}/address", storeName), accAddressHandler(cliCtx)).Methods("GET")
2. Tìm hiểu lệnh tx broadcast
Câu lệnh tx broadcast
có cấu trúc như sau:
nscli tx broadcast signedTx.json
Bạn có thể xem code của câu lệnh tại đây
- Tham só đi ngay sau
broadcast
phải là một file trong trường hợp này là ```signedTx.json
Kết hợp 2 câu lệnh lại ta có thể hình dung luồng của route mà ta sắp code như sau:
- User gửi một request gồm các tham số:
usignedTx
,sequence
,account number
- Server ghi
unsignedTx
vào fileunsignedTx.json
- Server thực hiện ngầm câu lệnh
tx sign
và ghi kết qủa vào filesignedTx.json
- Server lại thực hiện ngầm câu lệnh
tx broadcast
3. Code
Mở file x/nameserivce/client/rest/tx.go
và thêm đoạn code sau:
type signTxReq struct {
BaseReq rest.BaseReq `json:"base_req"`
Tx string `json:"tx"`
Sequence string `json:"sequence"`
AccountNumber string `json:"accountNumber"`
}
func signTxHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req signTxReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request")
return
}
baseReq := req.BaseReq.Sanitize()
if !baseReq.ValidateBasic(w) {
return
}
filePath := "unsignedTx.json"
data := []byte(req.Tx)
err := ioutil.WriteFile(filePath, data, 0644) // ghi tx mà user gửi lên vào file unsignedTx.json
if err != nil {
panic(err)
}
cmd := exec.Command("nscli", "tx", "sign", filePath, "--from", req.BaseReq.From, "--offline", "--chain-id", req.BaseReq.ChainID, "--sequence", req.Sequence, "--account-number", req.AccountNumber) // thực hiện ký
stdout, err := cmd.Output()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
filePath = "signedTx.json"
data = []byte(string(stdout))
err = ioutil.WriteFile(filePath, data, 0644) // ghi giao dịch đã ký vào filed signedTx.json
if err != nil {
panic(err)
}
cmd = exec.Command("nscli", "tx", "broadcast", filePath) // broadcase giao dịch đã được ký
stdout, err = cmd.Output()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
rest.PostProcessResponse(w, cliCtx, string(stdout))
}
}
Mở file x/nameservice/client/rest/rest.go
và thêm dòng sau vào:
r.HandleFunc(fmt.Sprintf("/%s/tx/sign", storeName), signTxHandler(cliCtx)).Methods("POST")
4. Test
Make lại tools, sau đó init và start chain:
make install
./init.sh
nsd start
Mở một terminal khác và start server:
nscli rest-server --chain-id namechain --trust-node
Mở Post Man lên và test
-
Query address theo của jack:
-
Query tất cả infor của jack:
-
jack tạo product:
-
jack ký giao dịch tạo product lưu ý coy giá trị trả về của route vừa rồi vào
tx
của request này -
Query để thấy product đã được tạo
-
Query address của alice
-
Query tất cả infor của alice:
-
alice tạo giao dịch buy product
-
alice ký giao dịch buy product
-
Query lại product để thấy nó đã được chuyển owner
-
Query thông tin của jack và alice để thấy đã có
10nametoken
đã được chuyển từ alice sang jack
Tổng kết
Như vậy là mình đã hướng dẫn các bạn hoàn thành series này, các bạn đã đủ hoàn toàn có thể clone repo mẫu cho series tại đây và customize theo ý của mình.
All rights reserved