Hyperledger Sawtooth - ошибка предварительной проверки при отправке транзакции

Я пытаюсь отправить транзакцию в Hyperledger Sawtooth v1.0.1, используя javascript, на валидатор, работающий на localhost. Код для почтового запроса следующий:

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.

Я использую докер для запуска валидатора, и команды для его запуска представляют собой слегка измененную версию тех, что приведены в курсе 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

Может кто-нибудь посоветовать мне, как решить эту проблему?


person KBhokray    schedule 15.03.2018    source источник


Ответы (2)


Проблемы CORS всегда лучше.

Что такое CORS?

Ваш браузер пытается защитить пользователей от перенаправления на страницу, которую они считают интерфейсом для API, но на самом деле это мошенничество. Каждый раз, когда веб-страница пытается получить доступ к API в другом домене, этот API должен явно предоставить разрешение веб-странице, иначе браузер заблокирует запрос. Вот почему вы можете запрашивать API из Node.js (без браузера) и помещать адрес REST API прямо в адресную строку (в том же домене). Однако попытка перейти с localhost:3000 на localhost:8008 или с file://path/to/your/index.html на localhost:8008 будет заблокирована.

Почему Sawtooth REST API не обрабатывает запросы OPTIONS?

API-интерфейс Sawtooth REST не знает, из какого домена вы собираетесь запускать веб-страницу, поэтому не может явно занести его в белый список. Можно внести в белый список все домены, но это, очевидно, разрушит любую защиту, которую CORS может вам предоставить. Вместо того, чтобы пытаться взвесить затраты и преимущества этого подхода для всех пользователей Sawtooth повсюду, было принято решение сделать REST API как можно более легким и независимым от безопасности. Ожидается, что любой разработчик, использующий его, разместит его за прокси-сервером, и он сможет принимать любые необходимые решения по безопасности на этом прокси-уровне.

Так как же это исправить?

Вам необходимо настроить прокси-сервер, который разместит REST API и вашу веб-страницу в одном домене. Для этого нет возможности быстрой настройки. Вам нужно будет настроить реальный сервер. Очевидно, есть много способов сделать это. Если вы уже знакомы с Node, вы можете обслуживать страницу из Node.js, а затем использовать прокси-сервер Node, который вызывает API. Если вы уже запускаете все компоненты Sawtooth с docker-compose, возможно, будет проще использовать Docker и Apache.

Настройка прокси-сервера Apache с помощью Docker

Создайте свой Dockerfile

В том же каталоге, что и ваше веб-приложение, создайте текстовый файл с именем «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 соответствует фактическому имени службы Sawtooth REST API в вашем файле компоновки докеров.

Измените файл создания докеров

Теперь, когда докер создаст файл YAML, через который вы запускаете 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

Это создаст ваш Dockerfile, расположенный в ./path/to/web/dir/Dockerfile (относительно файла компоновки докеров), и запустит его с командой по умолчанию, которая предназначена для запуска Apache. Apache будет обслуживать все файлы, расположенные в /usr/local/apache2/htdocs/, поэтому мы будем использовать volumes, чтобы связать путь к вашим веб-файлам на вашем хост-компьютере (т.е. ./path/to/web/dir/public/) с этим каталогом в контейнере. По сути, это псевдоним, поэтому, если вы обновите свое веб-приложение позже, вам не нужно перезапускать этот контейнер докеров, чтобы увидеть изменения. Наконец, 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 быстрее. Надеюсь, это поможет.

person Zac Delventhal    schedule 15.03.2018
comment
Привет, Зак. Спасибо за ответ! Я выполнил все шаги и смог развернуть прокси-сервер, и путь / api / blocks работал. Чтобы разрешить CORS, я добавил дополнительную строку в эхо: Header append Access-Control-Allow-Origin \ * \. Чтобы быть уверенным, я также добавил заголовок Allow: заголовок Set Allow OPTIONS, GET, HEAD, POST. Однако приложение теперь позволяет GET без каких-либо проблем, но по-прежнему выдает ошибку при предпечатной проверке. Это ответ: OPTIONS http: // ... net :: ERR_ABORTED, а это журналы прокси: OPTIONS / api / batches HTTP / 1.1 405 23. Можете ли вы сказать, как я могу решить эту проблему? - person KBhokray; 15.03.2018
comment
@KBhokray Я бы удалил заголовки CORS с вашего прокси. Они вам не понадобятся, если и ваш API, и веб-страница обслуживаются из одного домена, и они могут сбивать с толку. - person Zac Delventhal; 16.03.2018
comment
На самом деле в этом случае я пытался согнуть Sawtooth, чтобы сделать что-то, для чего он не был оптимизирован. Часть кода запроса поступает из браузера (здесь используется req вместо axios). Таким образом, этот случай больше похож на сценарий file: // ..., который вы упомянули, но исходит из SPA. В любом случае, для тех, кто пытается сделать то, что я сделал, вам нужно добавить их в дополнение к тем, что в моем предыдущем комментарии: LoadModule rewrite_module modules / mod_rewrite.so \ n \ RewriteEngine On \ n \ RewriteCond% {REQUEST_METHOD} OPTIONS \ n \ RewriteRule ^ - [R = 200]. Это заставляет прокси отвечать на все ОПЦИИ с 200 - person KBhokray; 21.03.2018

Заголовок транзакции должен содержать такие данные, как адрес блока, в котором он будет сохранен. Вот пример, который я использовал и отлично работает для меня: 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();
person Sandip Nirmal    schedule 20.03.2018