4

我正在尝试使用 javascript 将交易提交到 Hyperledger Sawtooth v1.0.1 到在 localhost 上运行的验证器。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 应用程序提交时得到处理,但从OPTIONS http://localhost:8080/batches 405 (Method Not Allowed)客户端提交时返回错误。这些是我尝试过的选项:

  1. Access-Control-Allow-*使用扩展将标头注入响应:响应仍然给出相同的错误
  2. 删除自定义标头以绕过预检请求:这会使验证器抛出错误,如下所示:

    ...
    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请求从验证器获取以下响应标头:

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方法没有在验证器中处理。GET添加 CORS 标头后,对状态的请求可以正常进行。在 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

有人可以指导我如何解决这个问题吗?

4

2 回答 2

7

CORS 问题总是最好的。

什么是 CORS?

您的浏览器试图保护用户不被定向到他们认为是 API 前端的页面,但实际上是欺诈性的。每当网页尝试访问不同域上的 API 时,该 API 都需要明确授予网页权限,否则浏览器将阻止该请求。这就是为什么您可以从 Node.js(无浏览器)查询 API,并且可以将 REST API 地址直接放入您的地址栏(同域)的原因。但是,尝试从localhost:3000tolocalhost:8008或 from file://path/to/your/index.htmltolocalhost:8008将被阻止。

为什么 Sawtooth REST API 不处理 OPTIONS 请求?

Sawtooth REST API 不知道您将从哪个域运行您的网页,因此它无法明确将其列入白名单。可以将所有域列入白名单,但这显然会破坏 CORS 可能为您提供的任何保护。与其试图为所有 Sawtooth 用户权衡这种方法的成本和收益,不如决定让 REST API 尽可能轻量级和安全无关。任何使用它的开发人员都应该将它放在代理服务器后面,并且他们可以在该代理层上做出他们需要的任何安全决策。

那么如何解决呢?

您需要设置一个代理服务器,将 REST API 和您的网页放在同一个域中。对此没有快速配置选项。您将必须设置一个实际的服务器。显然有很多方法可以做到这一点。如果您已经熟悉 Node,您可以从 Node.js 提供页面,然后让 Node 服务器代理 API 调用。但是,如果您已经在运行所有 Sawtooth 组件docker-compose,则使用 Docker 和 Apache 可能会更容易。

使用 Docker 设置 Apache 代理

创建你的 Dockerfile

在与您的 Web 应用程序相同的目录中,创建一个名为“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

这将做几件事。首先,它将httpd从 DockerHub 中拉下模块,这只是一个简单的静态服务器。然后我们使用一点 bash 将五行添加到 Apache 的配置文件中。这五行导入代理模块,告诉 Apache 我们要代理http://rest-api:8008路由/api,并设置X-Forwarded-Path标头以便 REST API 可以正确构建响应 URL。确保rest-api与 docker compose 文件中 Sawtooth REST API 服务的实际名称相匹配。

修改你的 docker compose 文件

现在,在运行 Sawtooth 的 docker compose YAML 文件中,您希望在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 compose 文件)的 Dockerfile,并使用其默认命令运行它,即启动 Apache。Apache 将提供位于 中的任何文件/usr/local/apache2/htdocs/,因此我们将使用volumes链接到您的主机(即./path/to/web/dir/public/)上的 Web 文件的路径,链接到容器中的该目录。这基本上是一个别名,因此如果您稍后更新您的 Web 应用程序,则无需重新启动此 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。希望这会有所帮助。

于 2018-03-15T15:26:24.630 回答
0

交易标头应包含详细信息,例如将要保存的块的地址。这是我使用过并且对我来说工作正常的示例: String payload = "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();
于 2018-03-20T08:51:06.197 回答