自定义事务处理器未收到请求
Custom Transaction Processor not receiving request
为什么我的交易处理器没有通过其余的 API 收到我 post 的请求?
我在Golang中构建了一个客户端和事务处理器(TP),这与XO示例没有太大区别。我已成功将 TP 运行 本地连接到 Sawtooth 组件,并从单独的 cli 工具发送批处理列表。目前 TP 中的 apply 方法没有被命中,也没有收到我的任何交易。
编辑:为了尽可能简化和阐明我的问题,我放弃了我原来的源代码,并构建了一个更简单的客户端,为 XO sdk 示例发送交易。*
当我 运行 我构建的工具时,其余的 api 成功接收请求、处理和 returns 202 响应但似乎省略了批处理的 ID来自批处理状态 url。检查日志,似乎验证器从未收到来自其余 api 的请求,如下面的日志所示。
sawtooth-rest-api-default | [2018-05-16 09:16:38.861 DEBUG route_handlers] Sending CLIENT_BATCH_SUBMIT_REQUEST request to validator
sawtooth-rest-api-default | [2018-05-16 09:16:38.863 DEBUG route_handlers] Received CLIENT_BATCH_SUBMIT_RESPONSE response from validator with status OK
sawtooth-rest-api-default | [2018-05-16 09:16:38.863 INFO helpers] POST /batches HTTP/1.1: 202 status, 213 size, in 0.002275 s
我将交易发送到本地实例的整个命令行工具如下。
package main
import (
"bytes"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"strings"
"time"
"github.com/hyperledger/sawtooth-sdk-go/protobuf/batch_pb2"
"github.com/hyperledger/sawtooth-sdk-go/protobuf/transaction_pb2"
"github.com/hyperledger/sawtooth-sdk-go/signing"
)
var restAPI string
func main() {
var hostname, port string
flag.StringVar(&hostname, "hostname", "localhost", "The hostname to host the application on (default: localhost).")
flag.StringVar(&port, "port", "8080", "The port to listen on for connection (default: 8080)")
flag.StringVar(&restAPI, "restAPI", "http://localhost:8008", "The address of the sawtooth REST API")
flag.Parse()
s := time.Now()
ctx := signing.CreateContext("secp256k1")
key := ctx.NewRandomPrivateKey()
snr := signing.NewCryptoFactory(ctx).NewSigner(key)
payload := "testing_new,create,"
encoded := base64.StdEncoding.EncodeToString([]byte(payload))
trn := BuildTransaction(
"testing_new",
encoded,
"xo",
"1.0",
snr)
trn.Payload = []byte(encoded)
batchList := &batch_pb2.BatchList{
Batches: []*batch_pb2.Batch{
BuildBatch(
[]*transaction_pb2.Transaction{trn},
snr),
},
}
serialised := batchList.String()
fmt.Println(serialised)
resp, err := http.Post(
restAPI+"/batches",
"application/octet-stream",
bytes.NewReader([]byte(serialised)),
)
if err != nil {
fmt.Println("Error")
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
elapsed := time.Since(s)
log.Printf("Creation took %s", elapsed)
resp.Close = true
}
// BuildTransaction will build a transaction based on the information provided
func BuildTransaction(ID, payload, familyName, familyVersion string, snr *signing.Signer) *transaction_pb2.Transaction {
publicKeyHex := snr.GetPublicKey().AsHex()
payloadHash := Hexdigest(string(payload))
addr := Hexdigest(familyName)[:6] + Hexdigest(ID)[:64]
transactionHeader := &transaction_pb2.TransactionHeader{
FamilyName: familyName,
FamilyVersion: familyVersion,
SignerPublicKey: publicKeyHex,
BatcherPublicKey: publicKeyHex,
Inputs: []string{addr},
Outputs: []string{addr},
Dependencies: []string{},
PayloadSha512: payloadHash,
Nonce: GenerateNonce(),
}
header := transactionHeader.String()
headerBytes := []byte(header)
headerSig := hex.EncodeToString(snr.Sign(headerBytes))
return &transaction_pb2.Transaction{
Header: headerBytes,
HeaderSignature: headerSig[:64],
Payload: []byte(payload),
}
}
// BuildBatch will build a batch using the provided transactions
func BuildBatch(trans []*transaction_pb2.Transaction, snr *signing.Signer) *batch_pb2.Batch {
ids := []string{}
for _, t := range trans {
ids = append(ids, t.HeaderSignature)
}
batchHeader := &batch_pb2.BatchHeader{
SignerPublicKey: snr.GetPublicKey().AsHex(),
TransactionIds: ids,
}
return &batch_pb2.Batch{
Header: []byte(batchHeader.String()),
HeaderSignature: hex.EncodeToString(snr.Sign([]byte(batchHeader.String())))[:64],
Transactions: trans,
}
}
// Hexdigest will hash the string and return the result as hex
func Hexdigest(str string) string {
hash := sha512.New()
hash.Write([]byte(str))
hashBytes := hash.Sum(nil)
return strings.ToLower(hex.EncodeToString(hashBytes))
}
// GenerateNonce will generate a random string to use
func GenerateNonce() string {
return randStringBytesMaskImprSrc(16)
}
const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
func randStringBytesMaskImprSrc(n int) string {
rand.Seed(time.Now().UnixNano())
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = rand.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
这有很多问题,希望我能单独解释每个问题,以帮助阐明这些交易失败的方式。
交易完成度
正如@Frank C. 在我的交易 headers 上面的评论一样,缺少几个值。这些是地址,也是随机数。
// Hexdigest will hash the string and return the result as hex
func Hexdigest(str string) string {
hash := sha512.New()
hash.Write([]byte(str))
hashBytes := hash.Sum(nil)
return strings.ToLower(hex.EncodeToString(hashBytes))
}
addr := Hexdigest(familyName)[:6] + Hexdigest(ID)[:64]
transactionHeader := &transaction_pb2.TransactionHeader{
FamilyName: familyName,
FamilyVersion: familyVersion,
SignerPublicKey: publicKeyHex,
BatcherPublicKey: publicKeyHex,
Inputs: []string{addr},
Outputs: []string{addr},
Dependencies: []string{},
PayloadSha512: payloadHash,
Nonce: uuid.NewV4(),
}
追踪
接下来是在批处理中启用跟踪。
return &batch_pb2.Batch{
Header: []byte(batchHeader.String()),
HeaderSignature: batchHeaderSignature,
Transactions: trans,
Trace: true, // Set this flag to true
}
通过以上设置,Rest API 将解码消息以打印额外的日志记录信息,并且验证器组件将输出更多有用的日志记录。
400 Bad Request
{
"error": {
"code": 35,
"message": "The protobuf BatchList you submitted was malformed and could not be read.",
"title": "Protobuf Not Decodable"
}
}
以上是 Rest API 打开跟踪后的输出。这证明接收到的数据有问题。
为什么会这样?
根据 Sawtooth 聊天室的一些宝贵建议,我尝试使用另一种语言的 SDK 反序列化我的批次。
反序列化
为了测试反序列化另一个 SDK 中的批次,我在 python 中构建了一个网络 api,我可以轻松地将批次发送到该网站,它可以尝试反序列化它们。
from flask import Flask, request
from protobuf import batch_pb2
app = Flask(__name__)
@app.route("/batches", methods = [ 'POST' ])
def deserialise():
received = request.data
print(received)
print("\n")
print(''.join('{:02x}'.format(x) for x in received))
batchlist = batch_pb2.BatchList()
batchlist.ParseFromString(received)
return ""
if __name__ == '__main__':
app.run(host="0.0.0.0", debug=True)
将我的批次发送到此后,我收到以下错误。
RuntimeWarning: Unexpected end-group tag: Not all data was converted
这显然是我的批处理出现的问题,但鉴于这一切都由 Hyperledger Sawtooth Go SDK 处理,我决定转移到 Python 并用它构建我的应用程序。
[编辑]
设法使用有关在 Go 中编写客户端的信息更新文档 https://sawtooth.hyperledger.org/docs/core/nightly/master/app_developers_guide/go_sdk.html
[原答案]
回答这个问题有点晚了,希望这能帮助其他面临类似 Go 客户端问题的人。
我最近正在试用 Sawtooth 的示例 Go 客户端。面对您在此处提出的类似问题,目前很难调试组合批处理列表中出现的问题。问题是缺少在客户端应用程序开发中使用 Go SDK 的示例代码和文档。
这里有一个 link 的示例 Go 代码:https://github.com/arsulegai/contentprotection/tree/master/ContentProtectionGoClient
请看文件src/client/client.go,它由单个Transaction和Batch组成,将其放入batch list并发送给validator。我调试的方法是用另一种语言编写预期的批处理列表(针对特定交易),并将每个步骤与 Go 代码中等效步骤的结果进行比较。
问题以及组合事务中缺少的信息 header 其他问题可能是 protobuf 消息的序列化方式。请使用 protobuf 库进行序列化。
示例:(扩展@danielcooperxyz 的答案)
transactionHeader := &transaction_pb2.TransactionHeader{
FamilyName: familyName,
FamilyVersion: familyVersion,
SignerPublicKey: publicKeyHex,
BatcherPublicKey: publicKeyHex,
Inputs: []string{addr},
Outputs: []string{addr},
Dependencies: []string{},
PayloadSha512: payloadHash,
Nonce: uuid.NewV4(),
}
transactionHeaderSerializedForm, _ := proto.Marshal(transactionHeader)
(protobuf 库方法可以在 github.com/golang/protobuf/proto 中找到)
为什么我的交易处理器没有通过其余的 API 收到我 post 的请求?
我在Golang中构建了一个客户端和事务处理器(TP),这与XO示例没有太大区别。我已成功将 TP 运行 本地连接到 Sawtooth 组件,并从单独的 cli 工具发送批处理列表。目前 TP 中的 apply 方法没有被命中,也没有收到我的任何交易。
编辑:为了尽可能简化和阐明我的问题,我放弃了我原来的源代码,并构建了一个更简单的客户端,为 XO sdk 示例发送交易。*
当我 运行 我构建的工具时,其余的 api 成功接收请求、处理和 returns 202 响应但似乎省略了批处理的 ID来自批处理状态 url。检查日志,似乎验证器从未收到来自其余 api 的请求,如下面的日志所示。
sawtooth-rest-api-default | [2018-05-16 09:16:38.861 DEBUG route_handlers] Sending CLIENT_BATCH_SUBMIT_REQUEST request to validator
sawtooth-rest-api-default | [2018-05-16 09:16:38.863 DEBUG route_handlers] Received CLIENT_BATCH_SUBMIT_RESPONSE response from validator with status OK
sawtooth-rest-api-default | [2018-05-16 09:16:38.863 INFO helpers] POST /batches HTTP/1.1: 202 status, 213 size, in 0.002275 s
我将交易发送到本地实例的整个命令行工具如下。
package main
import (
"bytes"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"strings"
"time"
"github.com/hyperledger/sawtooth-sdk-go/protobuf/batch_pb2"
"github.com/hyperledger/sawtooth-sdk-go/protobuf/transaction_pb2"
"github.com/hyperledger/sawtooth-sdk-go/signing"
)
var restAPI string
func main() {
var hostname, port string
flag.StringVar(&hostname, "hostname", "localhost", "The hostname to host the application on (default: localhost).")
flag.StringVar(&port, "port", "8080", "The port to listen on for connection (default: 8080)")
flag.StringVar(&restAPI, "restAPI", "http://localhost:8008", "The address of the sawtooth REST API")
flag.Parse()
s := time.Now()
ctx := signing.CreateContext("secp256k1")
key := ctx.NewRandomPrivateKey()
snr := signing.NewCryptoFactory(ctx).NewSigner(key)
payload := "testing_new,create,"
encoded := base64.StdEncoding.EncodeToString([]byte(payload))
trn := BuildTransaction(
"testing_new",
encoded,
"xo",
"1.0",
snr)
trn.Payload = []byte(encoded)
batchList := &batch_pb2.BatchList{
Batches: []*batch_pb2.Batch{
BuildBatch(
[]*transaction_pb2.Transaction{trn},
snr),
},
}
serialised := batchList.String()
fmt.Println(serialised)
resp, err := http.Post(
restAPI+"/batches",
"application/octet-stream",
bytes.NewReader([]byte(serialised)),
)
if err != nil {
fmt.Println("Error")
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
elapsed := time.Since(s)
log.Printf("Creation took %s", elapsed)
resp.Close = true
}
// BuildTransaction will build a transaction based on the information provided
func BuildTransaction(ID, payload, familyName, familyVersion string, snr *signing.Signer) *transaction_pb2.Transaction {
publicKeyHex := snr.GetPublicKey().AsHex()
payloadHash := Hexdigest(string(payload))
addr := Hexdigest(familyName)[:6] + Hexdigest(ID)[:64]
transactionHeader := &transaction_pb2.TransactionHeader{
FamilyName: familyName,
FamilyVersion: familyVersion,
SignerPublicKey: publicKeyHex,
BatcherPublicKey: publicKeyHex,
Inputs: []string{addr},
Outputs: []string{addr},
Dependencies: []string{},
PayloadSha512: payloadHash,
Nonce: GenerateNonce(),
}
header := transactionHeader.String()
headerBytes := []byte(header)
headerSig := hex.EncodeToString(snr.Sign(headerBytes))
return &transaction_pb2.Transaction{
Header: headerBytes,
HeaderSignature: headerSig[:64],
Payload: []byte(payload),
}
}
// BuildBatch will build a batch using the provided transactions
func BuildBatch(trans []*transaction_pb2.Transaction, snr *signing.Signer) *batch_pb2.Batch {
ids := []string{}
for _, t := range trans {
ids = append(ids, t.HeaderSignature)
}
batchHeader := &batch_pb2.BatchHeader{
SignerPublicKey: snr.GetPublicKey().AsHex(),
TransactionIds: ids,
}
return &batch_pb2.Batch{
Header: []byte(batchHeader.String()),
HeaderSignature: hex.EncodeToString(snr.Sign([]byte(batchHeader.String())))[:64],
Transactions: trans,
}
}
// Hexdigest will hash the string and return the result as hex
func Hexdigest(str string) string {
hash := sha512.New()
hash.Write([]byte(str))
hashBytes := hash.Sum(nil)
return strings.ToLower(hex.EncodeToString(hashBytes))
}
// GenerateNonce will generate a random string to use
func GenerateNonce() string {
return randStringBytesMaskImprSrc(16)
}
const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
func randStringBytesMaskImprSrc(n int) string {
rand.Seed(time.Now().UnixNano())
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = rand.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
这有很多问题,希望我能单独解释每个问题,以帮助阐明这些交易失败的方式。
交易完成度
正如@Frank C. 在我的交易 headers 上面的评论一样,缺少几个值。这些是地址,也是随机数。
// Hexdigest will hash the string and return the result as hex
func Hexdigest(str string) string {
hash := sha512.New()
hash.Write([]byte(str))
hashBytes := hash.Sum(nil)
return strings.ToLower(hex.EncodeToString(hashBytes))
}
addr := Hexdigest(familyName)[:6] + Hexdigest(ID)[:64]
transactionHeader := &transaction_pb2.TransactionHeader{
FamilyName: familyName,
FamilyVersion: familyVersion,
SignerPublicKey: publicKeyHex,
BatcherPublicKey: publicKeyHex,
Inputs: []string{addr},
Outputs: []string{addr},
Dependencies: []string{},
PayloadSha512: payloadHash,
Nonce: uuid.NewV4(),
}
追踪
接下来是在批处理中启用跟踪。
return &batch_pb2.Batch{
Header: []byte(batchHeader.String()),
HeaderSignature: batchHeaderSignature,
Transactions: trans,
Trace: true, // Set this flag to true
}
通过以上设置,Rest API 将解码消息以打印额外的日志记录信息,并且验证器组件将输出更多有用的日志记录。
400 Bad Request
{
"error": {
"code": 35,
"message": "The protobuf BatchList you submitted was malformed and could not be read.",
"title": "Protobuf Not Decodable"
}
}
以上是 Rest API 打开跟踪后的输出。这证明接收到的数据有问题。
为什么会这样?
根据 Sawtooth 聊天室的一些宝贵建议,我尝试使用另一种语言的 SDK 反序列化我的批次。
反序列化
为了测试反序列化另一个 SDK 中的批次,我在 python 中构建了一个网络 api,我可以轻松地将批次发送到该网站,它可以尝试反序列化它们。
from flask import Flask, request
from protobuf import batch_pb2
app = Flask(__name__)
@app.route("/batches", methods = [ 'POST' ])
def deserialise():
received = request.data
print(received)
print("\n")
print(''.join('{:02x}'.format(x) for x in received))
batchlist = batch_pb2.BatchList()
batchlist.ParseFromString(received)
return ""
if __name__ == '__main__':
app.run(host="0.0.0.0", debug=True)
将我的批次发送到此后,我收到以下错误。
RuntimeWarning: Unexpected end-group tag: Not all data was converted
这显然是我的批处理出现的问题,但鉴于这一切都由 Hyperledger Sawtooth Go SDK 处理,我决定转移到 Python 并用它构建我的应用程序。
[编辑]
设法使用有关在 Go 中编写客户端的信息更新文档 https://sawtooth.hyperledger.org/docs/core/nightly/master/app_developers_guide/go_sdk.html
[原答案]
回答这个问题有点晚了,希望这能帮助其他面临类似 Go 客户端问题的人。
我最近正在试用 Sawtooth 的示例 Go 客户端。面对您在此处提出的类似问题,目前很难调试组合批处理列表中出现的问题。问题是缺少在客户端应用程序开发中使用 Go SDK 的示例代码和文档。
这里有一个 link 的示例 Go 代码:https://github.com/arsulegai/contentprotection/tree/master/ContentProtectionGoClient
请看文件src/client/client.go,它由单个Transaction和Batch组成,将其放入batch list并发送给validator。我调试的方法是用另一种语言编写预期的批处理列表(针对特定交易),并将每个步骤与 Go 代码中等效步骤的结果进行比较。
问题以及组合事务中缺少的信息 header 其他问题可能是 protobuf 消息的序列化方式。请使用 protobuf 库进行序列化。
示例:(扩展@danielcooperxyz 的答案)
transactionHeader := &transaction_pb2.TransactionHeader{
FamilyName: familyName,
FamilyVersion: familyVersion,
SignerPublicKey: publicKeyHex,
BatcherPublicKey: publicKeyHex,
Inputs: []string{addr},
Outputs: []string{addr},
Dependencies: []string{},
PayloadSha512: payloadHash,
Nonce: uuid.NewV4(),
}
transactionHeaderSerializedForm, _ := proto.Marshal(transactionHeader)
(protobuf 库方法可以在 github.com/golang/protobuf/proto 中找到)