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)
ошибка при отправке от клиента. Вот варианты, которые я попробовал:
- впрыскивать
Access-Control-Allow-*
Заголовки в ответ с использованием расширения: Ответ по-прежнему выдает ту же ошибку Удалите пользовательский заголовок, чтобы обойти предварительный запрос: это заставит валидатор выдать ошибку, как показано:
... 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
Может кто-нибудь, пожалуйста, подскажите мне, как решить эту проблему?
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?
REST API Sawtooth не знает домен, с которого вы собираетесь запустить свою веб-страницу, поэтому он не может явно внести его в белый список. Можно внести в белый список все домены, но это, очевидно, разрушает любую защиту, которую CORS может дать вам. Вместо того, чтобы пытаться взвесить затраты и преимущества этого подхода для всех пользователей Sawtooth во всем мире, было принято решение сделать REST API как можно более легким и независимым от безопасности. Ожидается, что любой разработчик, использующий его, разместит его за прокси-сервером, и он может принимать любые решения по безопасности, которые ему нужны на этом уровне прокси.
Так как же это исправить?
Вам нужно настроить прокси-сервер, который поместит REST API и вашу веб-страницу в один домен. Для этого нет возможности быстрой настройки. Вам нужно будет настроить фактический сервер. Очевидно, есть много способов сделать это. Если вы уже знакомы с Node, вы можете обслуживать страницу из Node.js, а затем иметь прокси-сервер Node для вызовов API. Если вы уже используете все компоненты Sawtooth с docker-compose
хотя может быть проще использовать Docker и Apache.
Настройка Apache Proxy с помощью 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
соответствует действительному имени службы API REST Sawtooth в вашем файле компоновки Docker.
Модифицируйте свой файл докера
Теперь, чтобы docker составлял файл 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
(относительно файла создания docker), и запустите его с командой по умолчанию, которая запускает 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 быстрее. Надеюсь, это поможет.
Заголовок транзакции должен содержать такие данные, как адрес блока, в котором он будет сохранен. Вот пример, который я использовал и работает нормально для меня: 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();