Hyperledger Sawtooth - 提交交易时出现预检错误
Hyperledger Sawtooth - Preflight error while submitting transaction
我正在尝试使用 javascript 将交易提交给 Hyperledger Sawtooth v1.0.1 到本地主机上的验证器 运行。 post请求的代码如下:
request.post({
url: constants.API_URL + '/batches',
body: batchListBytes,
headers: { 'Content-Type': 'application/octet-stream' }
}, (err, response) => {
if (err) {
console.log(err);
return cb(err)
}
console.log(response.body);
return cb(null, response.body);
});
事务在从后端 nodejs 应用程序提交时得到处理,但在从客户端提交时 returns 出现 OPTIONS http://localhost:8080/batches 405 (Method Not Allowed)
错误。这些是我尝试过的选项:
- 使用扩展将
Access-Control-Allow-*
headers 注入到响应中:响应仍然给出相同的错误
删除自定义 header 以绕过预检请求:这会使验证器抛出错误,如下所示:
...
sawtooth-rest-api-default | KeyError: "Key not found: 'Content-Type'"
sawtooth-rest-api-default | [2018-03-15 08:07:37.670 ERROR web_protocol] Error handling request
sawtooth-rest-api-default | Traceback (most recent call last):
...
来自浏览器的未经修改的 POST
请求从验证器得到以下响应 header:
HTTP/1.1 405 Method Not Allowed
Content-Type: text/plain; charset=utf-8
Allow: GET,HEAD,POST
Content-Length: 23
Date: Thu, 15 Mar 2018 08:42:01 GMT
Server: Python/3.5 aiohttp/2.3.2
所以,我猜 OPTIONS
方法没有在验证器中处理。添加 CORS header 后,对状态的 GET
请求可以正常进行。 Sawtooth v0.8 也没有遇到这个问题。
我正在使用 docker 来启动验证器,启动它的命令是对 LinuxFoundationX:LFS171x 课程中给出的命令稍作修改的版本。相关指令如下:
bash -c \"\
sawadm keygen && \
sawtooth keygen my_key && \
sawset genesis -k /root/.sawtooth/keys/my_key.priv && \
sawadm genesis config-genesis.batch && \
sawtooth-validator -vv \
--endpoint tcp://validator:8800 \
--bind component:tcp://eth0:4004 \
--bind network:tcp://eth0:8800
有人可以指导我如何解决这个问题吗?
CORS 问题总是最好的。
什么是 CORS?
您的浏览器试图保护用户不被引导至他们认为 是API 的前端的页面,但实际上是欺诈性的。任何时候网页试图访问不同域上的 API 时,API 都需要明确授予该网页权限,否则浏览器将阻止该请求。这就是为什么你可以从 Node.js(没有浏览器)查询 API,并且可以将 REST API 地址直接放入你的地址栏(相同域)。但是,尝试从 localhost:3000
到 localhost:8008
或从 file://path/to/your/index.html
到 localhost:8008
将被阻止。
为什么 Sawtooth REST API 不处理 OPTIONS 请求?
Sawtooth REST API 不知道您要运行 您的网页所在的域,因此它无法明确地将其列入白名单。可以将 all 域列入白名单,但这显然会破坏 CORS 可能为您提供的任何保护。与其尝试权衡这种方法对各地所有 Sawtooth 用户的成本和收益,不如决定让 REST API 尽可能轻量且与安全无关。任何使用它的开发人员都应该将它放在代理服务器后面,他们可以在该代理层上做出他们需要的任何安全决策。
那么如何解决呢?
您需要设置一个代理服务器,将 REST API 和您的网页放在同一个域中。没有为此的快速配置选项。您将必须设置一个实际的服务器。显然有很多方法可以做到这一点。如果您已经熟悉 Node,则可以从 Node.js 提供页面,然后让 Node 服务器代理 API 调用。如果您已经 运行 将所有 Sawtooth 组件与 docker-compose
连接起来,那么使用 Docker 和 Apache 可能更容易。
使用 Docker
设置 Apache 代理
创建您的Docker文件
在与您的网络应用程序相同的目录中,创建一个名为 "Dockerfile" 的文本文件(无扩展名)。然后让它看起来像这样:
FROM httpd:2.4
RUN echo "\
LoadModule proxy_module modules/mod_proxy.so\n\
LoadModule proxy_http_module modules/mod_proxy_http.so\n\
ProxyPass /api http://rest-api:8008\n\
ProxyPassReverse /api http://rest-api:8008\n\
RequestHeader set X-Forwarded-Path \"/api\"\n\
" >>/usr/local/apache2/conf/httpd.conf
这将做几件事。首先它会从 DockerHub 中拉下 httpd
模块,它只是一个简单的静态服务器。然后我们使用一些 bash 向 Apache 的配置文件添加五行。这五行导入代理模块,告诉 Apache 我们要代理 http://rest-api:8008
到 /api
路由,并设置 X-Forwarded-Path
header 所以 REST API可以正确构建响应 URL。确保 rest-api
与 docker 撰写文件中 Sawtooth REST API 服务的实际名称匹配。
修改您的docker撰写文件
现在,要 docker 编写 YAML 文件,你正在 运行ning Sawtooth,你想在 services
键下添加一个新的 属性:
services:
my-web-page:
build: ./path/to/web/dir/
image: my-web-page
container_name: my-web-page
volumes:
- ./path/to/web/dir/public/:/usr/local/apache2/htdocs/
expose:
- 80
ports:
- '8000:80'
depends_on:
- rest-api
这将构建位于 ./path/to/web/dir/Dockerfile
的 Docker 文件(相对于 docker 撰写文件),并使用默认命令 运行 它,即启动阿帕奇。 Apache 将提供位于 /usr/local/apache2/htdocs/
中的任何文件,因此我们将使用 volumes
到 link 主机上您的 Web 文件的路径(即 ./path/to/web/dir/public/
),以容器中的那个目录。这基本上是一个别名,所以如果您稍后更新您的网络应用程序,您不需要重新启动这个 docker 容器来查看更改。最后,ports
将获取位于容器内端口 80
的服务器,并将其转发到 localhost:8000
。
运行全部
现在您应该可以 运行:
docker-compose -f path/to/your/compose-file.yaml up
它将启动您的 Apache 服务器以及 Sawtooth REST API 和验证程序以及您定义的任何其他服务。如果你去 http://localhost:8000
,你应该看到你的网页,如果你去 http://localhost:8000/api/blocks
,你应该看到链上区块的 JSON 表示。更重要的是,您应该能够从您的网络应用发出请求:
request.post({
url: 'api/batches',
body: batchListBytes,
headers: { 'Content-Type': 'application/octet-stream' }
}, (err, response) => console.log(response) );
哎呀。很抱歉这么长的回复,但我不确定是否可以更快地解决 CORS。希望这会有所帮助。
交易Header应该有详细信息,比如它要保存的区块地址。这是我使用过的示例,对我来说效果很好:
字符串负载 = "create,0001,BLockchain CPU,Black,5000";
logger.info("Sending payload as - "+ payload);
String payloadBytes = Utils.hash512(payload.getBytes()); // --fix for invaluid payload seriqalization
ByteString payloadByteString = ByteString.copyFrom(payload.getBytes());
String address = getAddress(IDEM, ITEM_ID); // get unique address for input and output
logger.info("Sending address as - "+ address);
TransactionHeader txnHeader = TransactionHeader.newBuilder().clearBatcherPublicKey()
.setBatcherPublicKey(publicKeyHex)
.setFamilyName(IDEM) // Idem Family
.setFamilyVersion(VER)
.addInputs(address)
.setNonce("1")
.addOutputs(address)
.setPayloadSha512(payloadBytes)
.setSignerPublicKey(publicKeyHex)
.build();
ByteString txnHeaderBytes = txnHeader.toByteString();
byte[] txnHeaderSignature = privateKey.signMessage(txnHeaderBytes.toString()).getBytes();
String value = Signing.sign(privateKey, txnHeader.toByteArray());
Transaction txn = Transaction.newBuilder().setHeader(txnHeaderBytes).setPayload(payloadByteString)
.setHeaderSignature(value).build();
BatchHeader batchHeader = BatchHeader.newBuilder().clearSignerPublicKey().setSignerPublicKey(publicKeyHex)
.addTransactionIds(txn.getHeaderSignature()).build();
ByteString batchHeaderBytes = batchHeader.toByteString();
byte[] batchHeaderSignature = privateKey.signMessage(batchHeaderBytes.toString()).getBytes();
String value_batch = Signing.sign(privateKey, batchHeader.toByteArray());
Batch batch = Batch.newBuilder()
.setHeader(batchHeaderBytes)
.setHeaderSignature(value_batch)
.setTrace(true)
.addTransactions(txn)
.build();
BatchList batchList = BatchList.newBuilder()
.addBatches(batch)
.build();
ByteString batchBytes = batchList.toByteString();
String serverResponse = Unirest.post("http://localhost:8008/batches")
.header("Content-Type", "application/octet-stream")
.body(batchBytes.toByteArray())
.asString()
.getBody();
我正在尝试使用 javascript 将交易提交给 Hyperledger Sawtooth v1.0.1 到本地主机上的验证器 运行。 post请求的代码如下:
request.post({
url: constants.API_URL + '/batches',
body: batchListBytes,
headers: { 'Content-Type': 'application/octet-stream' }
}, (err, response) => {
if (err) {
console.log(err);
return cb(err)
}
console.log(response.body);
return cb(null, response.body);
});
事务在从后端 nodejs 应用程序提交时得到处理,但在从客户端提交时 returns 出现 OPTIONS http://localhost:8080/batches 405 (Method Not Allowed)
错误。这些是我尝试过的选项:
- 使用扩展将
Access-Control-Allow-*
headers 注入到响应中:响应仍然给出相同的错误 删除自定义 header 以绕过预检请求:这会使验证器抛出错误,如下所示:
... sawtooth-rest-api-default | KeyError: "Key not found: 'Content-Type'" sawtooth-rest-api-default | [2018-03-15 08:07:37.670 ERROR web_protocol] Error handling request sawtooth-rest-api-default | Traceback (most recent call last): ...
来自浏览器的未经修改的 POST
请求从验证器得到以下响应 header:
HTTP/1.1 405 Method Not Allowed
Content-Type: text/plain; charset=utf-8
Allow: GET,HEAD,POST
Content-Length: 23
Date: Thu, 15 Mar 2018 08:42:01 GMT
Server: Python/3.5 aiohttp/2.3.2
所以,我猜 OPTIONS
方法没有在验证器中处理。添加 CORS header 后,对状态的 GET
请求可以正常进行。 Sawtooth v0.8 也没有遇到这个问题。
我正在使用 docker 来启动验证器,启动它的命令是对 LinuxFoundationX:LFS171x 课程中给出的命令稍作修改的版本。相关指令如下:
bash -c \"\
sawadm keygen && \
sawtooth keygen my_key && \
sawset genesis -k /root/.sawtooth/keys/my_key.priv && \
sawadm genesis config-genesis.batch && \
sawtooth-validator -vv \
--endpoint tcp://validator:8800 \
--bind component:tcp://eth0:4004 \
--bind network:tcp://eth0:8800
有人可以指导我如何解决这个问题吗?
CORS 问题总是最好的。
什么是 CORS?
您的浏览器试图保护用户不被引导至他们认为 是API 的前端的页面,但实际上是欺诈性的。任何时候网页试图访问不同域上的 API 时,API 都需要明确授予该网页权限,否则浏览器将阻止该请求。这就是为什么你可以从 Node.js(没有浏览器)查询 API,并且可以将 REST API 地址直接放入你的地址栏(相同域)。但是,尝试从 localhost:3000
到 localhost:8008
或从 file://path/to/your/index.html
到 localhost:8008
将被阻止。
为什么 Sawtooth REST API 不处理 OPTIONS 请求?
Sawtooth REST API 不知道您要运行 您的网页所在的域,因此它无法明确地将其列入白名单。可以将 all 域列入白名单,但这显然会破坏 CORS 可能为您提供的任何保护。与其尝试权衡这种方法对各地所有 Sawtooth 用户的成本和收益,不如决定让 REST API 尽可能轻量且与安全无关。任何使用它的开发人员都应该将它放在代理服务器后面,他们可以在该代理层上做出他们需要的任何安全决策。
那么如何解决呢?
您需要设置一个代理服务器,将 REST API 和您的网页放在同一个域中。没有为此的快速配置选项。您将必须设置一个实际的服务器。显然有很多方法可以做到这一点。如果您已经熟悉 Node,则可以从 Node.js 提供页面,然后让 Node 服务器代理 API 调用。如果您已经 运行 将所有 Sawtooth 组件与 docker-compose
连接起来,那么使用 Docker 和 Apache 可能更容易。
使用 Docker
设置 Apache 代理创建您的Docker文件
在与您的网络应用程序相同的目录中,创建一个名为 "Dockerfile" 的文本文件(无扩展名)。然后让它看起来像这样:
FROM httpd:2.4
RUN echo "\
LoadModule proxy_module modules/mod_proxy.so\n\
LoadModule proxy_http_module modules/mod_proxy_http.so\n\
ProxyPass /api http://rest-api:8008\n\
ProxyPassReverse /api http://rest-api:8008\n\
RequestHeader set X-Forwarded-Path \"/api\"\n\
" >>/usr/local/apache2/conf/httpd.conf
这将做几件事。首先它会从 DockerHub 中拉下 httpd
模块,它只是一个简单的静态服务器。然后我们使用一些 bash 向 Apache 的配置文件添加五行。这五行导入代理模块,告诉 Apache 我们要代理 http://rest-api:8008
到 /api
路由,并设置 X-Forwarded-Path
header 所以 REST API可以正确构建响应 URL。确保 rest-api
与 docker 撰写文件中 Sawtooth REST API 服务的实际名称匹配。
修改您的docker撰写文件
现在,要 docker 编写 YAML 文件,你正在 运行ning Sawtooth,你想在 services
键下添加一个新的 属性:
services:
my-web-page:
build: ./path/to/web/dir/
image: my-web-page
container_name: my-web-page
volumes:
- ./path/to/web/dir/public/:/usr/local/apache2/htdocs/
expose:
- 80
ports:
- '8000:80'
depends_on:
- rest-api
这将构建位于 ./path/to/web/dir/Dockerfile
的 Docker 文件(相对于 docker 撰写文件),并使用默认命令 运行 它,即启动阿帕奇。 Apache 将提供位于 /usr/local/apache2/htdocs/
中的任何文件,因此我们将使用 volumes
到 link 主机上您的 Web 文件的路径(即 ./path/to/web/dir/public/
),以容器中的那个目录。这基本上是一个别名,所以如果您稍后更新您的网络应用程序,您不需要重新启动这个 docker 容器来查看更改。最后,ports
将获取位于容器内端口 80
的服务器,并将其转发到 localhost:8000
。
运行全部
现在您应该可以 运行:
docker-compose -f path/to/your/compose-file.yaml up
它将启动您的 Apache 服务器以及 Sawtooth REST API 和验证程序以及您定义的任何其他服务。如果你去 http://localhost:8000
,你应该看到你的网页,如果你去 http://localhost:8000/api/blocks
,你应该看到链上区块的 JSON 表示。更重要的是,您应该能够从您的网络应用发出请求:
request.post({
url: 'api/batches',
body: batchListBytes,
headers: { 'Content-Type': 'application/octet-stream' }
}, (err, response) => console.log(response) );
哎呀。很抱歉这么长的回复,但我不确定是否可以更快地解决 CORS。希望这会有所帮助。
交易Header应该有详细信息,比如它要保存的区块地址。这是我使用过的示例,对我来说效果很好: 字符串负载 = "create,0001,BLockchain CPU,Black,5000";
logger.info("Sending payload as - "+ payload);
String payloadBytes = Utils.hash512(payload.getBytes()); // --fix for invaluid payload seriqalization
ByteString payloadByteString = ByteString.copyFrom(payload.getBytes());
String address = getAddress(IDEM, ITEM_ID); // get unique address for input and output
logger.info("Sending address as - "+ address);
TransactionHeader txnHeader = TransactionHeader.newBuilder().clearBatcherPublicKey()
.setBatcherPublicKey(publicKeyHex)
.setFamilyName(IDEM) // Idem Family
.setFamilyVersion(VER)
.addInputs(address)
.setNonce("1")
.addOutputs(address)
.setPayloadSha512(payloadBytes)
.setSignerPublicKey(publicKeyHex)
.build();
ByteString txnHeaderBytes = txnHeader.toByteString();
byte[] txnHeaderSignature = privateKey.signMessage(txnHeaderBytes.toString()).getBytes();
String value = Signing.sign(privateKey, txnHeader.toByteArray());
Transaction txn = Transaction.newBuilder().setHeader(txnHeaderBytes).setPayload(payloadByteString)
.setHeaderSignature(value).build();
BatchHeader batchHeader = BatchHeader.newBuilder().clearSignerPublicKey().setSignerPublicKey(publicKeyHex)
.addTransactionIds(txn.getHeaderSignature()).build();
ByteString batchHeaderBytes = batchHeader.toByteString();
byte[] batchHeaderSignature = privateKey.signMessage(batchHeaderBytes.toString()).getBytes();
String value_batch = Signing.sign(privateKey, batchHeader.toByteArray());
Batch batch = Batch.newBuilder()
.setHeader(batchHeaderBytes)
.setHeaderSignature(value_batch)
.setTrace(true)
.addTransactions(txn)
.build();
BatchList batchList = BatchList.newBuilder()
.addBatches(batch)
.build();
ByteString batchBytes = batchList.toByteString();
String serverResponse = Unirest.post("http://localhost:8008/batches")
.header("Content-Type", "application/octet-stream")
.body(batchBytes.toByteArray())
.asString()
.getBody();