blockchain - 由于状态根哈希不匹配,TP 在区块被拒绝后未接收交易 Hyperledger Sawtooth
问题描述
我已经设置了一个Hyperlder Sawtooth Network
,Sawtooth Docs
你可以docker-compose.yaml
在这里找到我用来设置网络的地方:
https://sawtooth.hyperledger.org/docs/core/releases/1.0/app_developers_guide/sawtooth-default.yaml
事务处理器代码:
const { TransactionHandler } = require('sawtooth-sdk/processor/handler');
const { InvalidTransaction } = require('sawtooth-sdk/processor/exceptions');
const { TextEncoder, TextDecoder } = require('text-encoding/lib/encoding');
const crypto = require('crypto');
const _hash = (x) => {
return crypto.createHash('sha512').update(x).digest('hex').toLowerCase();
}
const encoder = new TextEncoder('utf8');
const decoder = new TextDecoder('utf8');
const TP_FAMILY = 'grocery';
const TP_NAMESPACE = _hash(TP_FAMILY).substring(0, 6);
class GroceryHandler extends TransactionHandler {
constructor() {
super(TP_FAMILY, ['1.0.0'], [TP_NAMESPACE]);
this.timeout = 500;
}
apply(request, context) {
console.log('Transaction Processor Called!');
this._context = context;
this._request = request;
const actions = ['createOrder'];
try {
let payload = JSON.parse(decoder.decode(request.payload));
let action = payload.action
if(!action || !actions.includes(action)) {
throw new InvalidTransaction(`Upsupported action "${action}"!`);
}
try {
return this[action](payload.data);
} catch(e) {
console.log(e);
}
} catch(e) {
throw new InvalidTransaction('Pass a valid json string.');
}
}
createOrder(payload) {
console.log('Creating order!');
let data = {
id: payload.id,
status: payload.status,
created_at: Math.floor((new Date()).getTime() / 1000)
};
return this._setEntry(this._makeAddress(payload.id), data);
}
_setEntry(address, payload) {
let dataBytes = encoder.encode(JSON.stringify(payload));
let entries = {
[address]: dataBytes
}
return this._context.setState(entries);
}
_makeAddress(id) {
return TP_NAMESPACE + _hash(id).substr(0,64);
}
}
const transactionProcessor = new TransactionProcessor('tcp://validator:4004');
transactionProcessor.addHandler(new GroceryHandler());
transactionProcessor.start();
客户端代码:
const { createContext, CryptoFactory } = require('sawtooth-sdk/signing');
const { protobuf } = require('sawtooth-sdk');
const { TextEncoder } = require('text-encoding/lib/encoding');
const request = require('request');
const crypto = require('crypto');
const encoder = new TextEncoder('utf8');
const _hash = (x) => {
return crypto.createHash('sha512').update(x).digest('hex').toLowerCase();
}
const TP_FAMILY = 'grocery';
const TP_NAMESPACE = _hash(TP_FAMILY).substr(0, 6);
const context = createContext('secp256k1');
const privateKey = context.newRandomPrivateKey();
const signer = new CryptoFactory(context).newSigner(privateKey);
let payload = {
action: 'create_order',
data: {
id: '1'
}
};
const address = TP_NAMESPACE + _hash(payload.id).substr(0, 64);
const payloadBytes = encoder.encode(JSON.stringify(payload));
const transactionHeaderBytes = protobuf.TransactionHeader.encode({
familyName: TP_FAMILY,
familyVersion: '1.0.0',
inputs: [address],
outputs: [address],
signerPublicKey: signer.getPublicKey().asHex(),
batcherPublicKey: signer.getPublicKey().asHex(),
dependencies: [],
payloadSha512: _hash(payloadBytes)
}).finish();
const transactionHeaderSignature = signer.sign(transactionHeaderBytes);
const transaction = protobuf.Transaction.create({
header: transactionHeaderBytes,
headerSignature: transactionHeaderSignature,
payload: payloadBytes
});
const transactions = [transaction]
const batchHeaderBytes = protobuf.BatchHeader.encode({
signerPublicKey: signer.getPublicKey().asHex(),
transactionIds: transactions.map((txn) => txn.headerSignature),
}).finish();
const batchHeaderSignature = signer.sign(batchHeaderBytes)
const batch = protobuf.Batch.create({
header: batchHeaderBytes,
headerSignature: batchHeaderSignature,
transactions: transactions
});
const batchListBytes = protobuf.BatchList.encode({
batches: [batch]
}).finish();
request.post({
url: 'http://localhost:8008/batches',
body: batchListBytes,
headers: { 'Content-Type': 'application/octet-stream' }
}, (err, response) => {
if (err) {
return console.log(err);
}
console.log(response.body);
});
验证者日志:https ://justpaste.it/74y5g
事务处理器日志:https ://justpaste.it/5ayn6
> grocery-tp@1.0.0 start /processor
> node index.js tcp://validator:4004
Connected to tcp://validator:4004
Registration of [grocery 1.0.0] succeeded
Transaction Processor Called!
Creating order!
Transaction Processor Called!
Creating order!
Transaction Processor Called!
Creating order!
Transaction Processor Called!
Creating order!
Transaction Processor Called!
Creating order!
Transaction Processor Called!
Creating order!
Transaction Processor Called!
Creating order!
在验证器日志中输入以下条目后,我没有收到任何发送给处理器的交易。
[2018-07-04 10:39:18.026 DEBUG block_validator] Block(c9636780f4babea6b8103665bc1fb19a59ce0ba66289494fc61972e97423a3273dd1d41e93ddf90c933809dab5350a0a83b282aaf25ebdcc6619735e25d8b337 (block_num:75, state:00704f66a517e79dc064e63586b12d677a3b60ce25363a4654fa819a59e4132c, previous_block_id:32b07cd79093aee0b7833b8924c8fef01fce798f3d58560c83c9891b2c05c02f2a4b894de43503fdcb0f129e9f365cfbdc415b798877393f7e75598195ad3c94)) rejected due to state root hash mismatch: 00704f66a517e79dc064e63586b12d677a3b60ce25363a4654fa819a59e4132c != e52737049078b9e0f149bb58fc4938473a5e889fa427536b0e862c4728df5004
解决方案
当锯齿处理一个事务时,它会多次将其发送到您的 TP ,然后比较多个调用之间的哈希值以确保返回相同的结果。如果在 TP 中生成不同的地址或存储在某个地址的数据变体,则事务将失败。
锯齿中的一句名言就是每个事务的TP必须是确定性的,也就是说它类似于函数编程中的规则:用相同的事务调用相同的TP应该产生相同的结果。
需要注意的事项:
- 注意不要构造包含时间戳元素、增量计数或其他随机信息位的地址
- 注意不要对存储在某个地址的数据做同样的事情
推荐阅读
- delphi - RAD Studio:启动 IDE 时出错。如何纠正这个?
- apache-spark-sql - Spark 创建的用于从 S3 读取输入表的任务
- azure-functions - IDurableOrchestrationContext.SetCustomStatus 不会立即更新状态
- ruby-on-rails - 通过控制台访问翻译表
- reactjs - 改变 a 的背景颜色与 useRef 反应
- nginx - Nginx Lua 扩展读取多部分形式的正文,使其保持完整(代理)
- rust - 使用 serde 将枚举变量的名称作为字符串获取
- jquery - 如何将父数据属性值应用于动态插入的按钮
- python - 寻找最小公倍数
- python-3.x - 使用 L1 的 KNN 预测(曼哈顿距离)