O acesso a arquivos grandes e a vários dados dinâmicos externos geralmente é uma parte muito importante de um aplicativo descentralizado. Ao mesmo tempo, o próprio Ethereum não fornece um mecanismo para mudar de posição - contratos inteligentes só podem ser lidos e gravados dentro do próprio blockchain. Neste artigo, consideraremos o Oraclize, que possibilita a interação com o mundo exterior consultando quase todos os recursos da Internet. Um tópico relacionado é o IPFS e mencione-o brevemente.
IPFS
IPFS é um sistema de arquivos distribuído com endereçamento de conteúdo. Isso significa que, para o conteúdo de qualquer arquivo adicionado lá, é considerado um hash exclusivo. O mesmo hash é usado para pesquisar e recuperar esse conteúdo da rede.
As informações básicas já foram descritas
neste artigo e em vários outros, portanto não vemos motivo para repetir.
Por que usar o IPFS em conjunto com o Ethereum?
Salvar qualquer volume de conteúdo na blockchain é muito caro e prejudicial para a rede. Portanto, a melhor opção é salvar algum tipo de link para o arquivo que está no armazenamento fora da cadeia, não necessariamente IPFS. Mas o IPFS tem várias vantagens:
- Um link de arquivo é um hash exclusivo do conteúdo específico do arquivo; portanto, se colocarmos esse hash no blockchain, podemos ter certeza de que o arquivo recebido é o que foi originalmente adicionado, o arquivo não pode ser substituído
- O sistema distribuído garante a indisponibilidade de um servidor específico (devido a bloqueio ou outros motivos)
- O link para o arquivo e a confirmação de hash são combinados em uma linha, o que significa que você pode escrever menos no blockchain e economizar gás
Entre as deficiências, pode-se mencionar que, como não há servidor central, para a acessibilidade dos arquivos, é necessário que pelo menos um arquivo seja “distribuído”. Mas se você possui um arquivo específico, é fácil conectar-se aos distribuidores - inicie o daemon ipfs e adicione o arquivo via
ipfs add
.
A tecnologia é muito adequada para a ideologia da descentralização, portanto, considerando o Oraclize agora, frequentemente encontraremos o uso do IPFS em diferentes mecanismos de oráculo.
Oraclize
Para executar praticamente qualquer trabalho útil, um contrato inteligente precisa receber novos dados. No entanto, não há capacidade integrada de atender a uma solicitação da blockchain para o mundo exterior. Obviamente, você pode adicionar tudo o que é exigido pelas transações manualmente, mas é impossível verificar de onde esses dados vieram e sua confiabilidade. Além disso, pode ser necessário organizar uma infraestrutura adicional para atualizar rapidamente dados dinâmicos, como taxas de câmbio. E atualizações com um intervalo fixo levarão a excedentes de gás.
Portanto, o serviço fornecido pelo
Oraclize é útil: em um contrato inteligente, você pode enviar uma solicitação para quase qualquer API ou recurso na Internet, verifique se os dados recebidos do recurso especificado não são alterados e use o resultado no mesmo contrato inteligente.
O Oraclize não é apenas um serviço Ethereum, funcionalidade semelhante é fornecida a outras cadeias de blocos, mas descreveremos apenas o pacote com o Ethereum.
Introdução
Tudo que você precisa para começar - é adicionar-se a um projecto dos arquivos de oraclizeAPI
repositório . Você só precisa escolher aquele adequado para sua versão do compilador (solc): oraclizeAPI_0.5.sol para versões a partir de 0.4.18, oraclizeAPI_0.4.sol para versões a partir de 0.4.1, oraclizeAPI_pre0.4.sol para todas as versões antigas, suporte Esta versão já foi descontinuada. Se você usar trufa, não esqueça de renomear o arquivo para usingOraclize - isso exige que o nome do arquivo e o contrato correspondam.
Ao incluir o arquivo apropriado em seu projeto, você herda o contrato de
usingOraclize
. E você pode começar a usar o Oracle, que se resume a duas coisas principais: enviar uma solicitação usando o assistente
oraclize_query
e depois processar o resultado na função
__callback
. O contrato inteligente mais simples (para obter o preço atual do tempo de antena em dólares) pode ter a seguinte aparência:
pragma solidity 0.4.23; import "./usingOraclize.sol"; contract ExampleContract is usingOraclize { string public ETHUSD; event updatedPrice(string price); event newOraclizeQuery(string description); function ExampleContract() payable { updatePrice(); } function __callback(bytes32 myid, string result) { require (msg.sender == oraclize_cbAddress()); ETHUSD = result; updatedPrice(result); } function updatePrice() payable { if (oraclize_getPrice("URL") > this.balance) { newOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee"); } else { newOraclizeQuery("Oraclize query was sent, standing by for the answer.."); oraclize_query("URL", "json(https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=USD).0.price_usd"); } } }
A função que envia a solicitação é
updatePrice
. Você pode ver que primeiro há uma verificação de que
oraclize_getPrice(“URL”)
maior que o saldo atual do contrato. Isso ocorre porque a chamada
oraclize_query
deve ser paga, o preço é calculado como a soma da comissão fixa e do pagamento de gás para a chamada de retorno de chamada.
“URL”
é uma designação de um dos tipos de fontes de dados; nesse caso, é uma solicitação simples via https, e consideraremos outras opções. As respostas sob solicitação podem ser analisadas antecipadamente como json (como no exemplo) e de várias outras maneiras (consideraremos mais adiante). Uma linha de
__callback
é retornada em
__callback
. No início, verifica-se que a chamada passada no endereço confiável oraclize
Todas as opções para usar o oraclize são construídas de acordo com um esquema, apenas as fontes de dados e a capacidade de adicionar autenticação ao
__callback
. Portanto, em exemplos futuros, citaremos apenas diferenças significativas.
Usar preço
Como já mencionado, éter extra é pago por solicitações de oraclize e é removido do saldo do contrato, e não do endereço de chamada. Uma exceção é apenas a primeira solicitação de cada novo contrato, é fornecida gratuitamente. Também é interessante que a mesma mecânica seja preservada nas redes de teste, mas o pagamento é feito através da transmissão da rede correspondente, ou seja, nas solicitações de testnet são praticamente gratuitas.
Já foi mencionado que o preço solicitado consiste em dois valores: uma comissão fixa e pagamento por uma chamada de retorno de chamada de gás. Uma comissão fixa é definida em dólares e a quantidade de éter é calculada a partir da taxa atual. A comissão depende da fonte dos dados e de mecanismos de suporte adicionais, nos quais iremos nos concentrar. A tabela de preços atual é assim:
Como você pode ver, o preço por solicitação de URL é de vários centavos. É muito ou pouco? Para fazer isso, vamos considerar quanto custa a segunda parte - a cobrança do gás de retorno de chamada.
Isso funciona de acordo com o seguinte esquema: a quantidade de éter necessária para pagar uma quantidade fixa de gás a um preço fixo é transferida antecipadamente com a solicitação do contrato. Esse valor deve ser suficiente para efetuar uma chamada de retorno e o preço deve ser adequado ao mercado; caso contrário, a transação não será realizada ou travará por muito tempo. Ao mesmo tempo, fica claro que nem sempre é possível saber a quantidade de gás com antecedência; portanto, a diretoria também deve estar em reserva (a reserva não é devolvida). Os valores padrão são um limite de 200 mil gases a um preço de 20 gwei. Isso é suficiente para um retorno de chamada médio com várias entradas e algum tipo de lógica. E o preço de 20 gwei, embora possa parecer muito grande no momento (no momento em que escrevo, a média é de 4 gwei), mas no momento do influxo de transações, o preço de mercado pode subir repentinamente e ser ainda mais alto, então, em geral, esses valores estão próximos dos reais usados. Portanto, com esses valores e o preço do ar na região de US $ 500, os pagamentos de gás chegarão a US $ 2, para que possamos dizer que uma comissão fixa ocupa uma pequena parte.
Se você sabe o que está fazendo, existe a opção de alterar o limite e o preço do gás, economizando significativamente em solicitações.
O preço do gás pode ser definido por uma função separada -
oraclize_setCustomGasPrice(< wei>)
. Após a chamada, o preço é salvo e usado em todas as solicitações subsequentes.
O limite pode ser definido a pedido
oraclize_query
, indicando o seu último argumento, assim:
oraclize_query("URL", "<>", 50000);
Se você tiver uma lógica complexa em
__callback
e o gás for consumido mais de 200k, você definitivamente precisará definir um limite que cubra o pior caso de consumo de gás. Caso contrário, se o limite for excedido,
__callback
simplesmente reverterá.
A propósito, recentemente oraclize obteve informações que você pode pagar por solicitações fora da blockchain, o que permitirá que você não gaste todo o limite ou devolva o saldo (e o pagamento não vem do contrato). Ainda não tivemos que usá-lo, mas a oraclize se oferece para entrar em contato com info@oraclize.it, se essa opção for interessante. Portanto, lembre-se.
Como isso funciona
Por que, tendo herdado de um contrato inteligente regular, obtemos funcionalidades que não eram inicialmente suportadas por mecanismos de blockchain? De fato, o serviço oracle não consiste apenas em contratos com funções auxiliares. O principal trabalho de obtenção de dados é realizado por um serviço externo. Os contratos inteligentes formam aplicativos para acesso a dados externos e os colocam no blockchain. Serviço externo - monitora novos blocos do blockchain e, se detectar um aplicativo, o executa. Esquematicamente, isso pode ser representado da seguinte maneira:
Fontes de dados
Além do
URL
considerado, o oraclize fornece mais 4 opções (que você viu na seção sobre preços):
WolframAlpha
,
IPFS
,
random
e
computation
. Vamos considerar cada um deles.
1. URL
O exemplo já discutido usa essa fonte de dados. Essa é a fonte de solicitações HTTP para várias APIs. O exemplo foi o seguinte:
oraclize_query("URL", "json(https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=USD).0.price_usd");
Isso está recebendo o preço do éter e, como a api fornece uma string json com um conjunto de dados, a solicitação é agrupada em um analisador json e retorna apenas o campo necessário. Nesse caso, é GET, mas o URL de origem também suporta solicitações POST. O tipo de solicitação é determinado automaticamente por um argumento adicional. Se houver json válido, como neste exemplo:
oraclize_query("URL", "json(https://shapeshift.io/sendamount).success.deposit", '{"pair":"eth_btc","amount":"1","withdrawal":"1AAcCo21EUc1jbocjssSQDzLna9Vem2UN5"}')
a solicitação é processada como POST (a API usada é descrita
aqui , se estiver interessado)
2. WolframAlpha
Essa fonte de dados permite acessar o serviço
WolframAlpha , que pode fornecer respostas a várias solicitações de fatos ou cálculos, por exemplo
oraclize_query(“WolframAlpha”, “president of Russia”)
retornará
Vladimir Putin
e solicitará
oraclize_query(“WolframAlpha”, “solve x^2-4”)
retornará
x = 2
.
Como você pode ver, o resultado foi incompleto porque o símbolo ± foi perdido. Portanto, antes de usar essa fonte, é necessário verificar se o valor de uma solicitação específica pode ser usado em um contrato inteligente. Além disso, a autenticação não é suportada para respostas; portanto, eles próprios recomendam que essa fonte seja usada apenas para teste.
3. IPFS
Como você pode imaginar, ele permite recuperar o conteúdo de um arquivo no IPFS usando um multi-hash. O tempo limite para receber conteúdo é de 20 segundos.
oraclize_query(“IPFS”, “QmTL5xNq9PPmwvM1RhxuhiYqoTJcmnaztMz6PQpGxmALkP”)
retornará
Hello, Habr!
(se o arquivo com este conteúdo ainda estiver disponível)
4. aleatório
A geração aleatória de números funciona da mesma maneira que outras fontes, mas se você usar
oraclize_query
, será necessária uma preparação demorada dos argumentos. Para evitar isso, você pode usar a
oraclize_newRandomDSQuery(delay, nbytes, customGasLimit)
auxiliar
oraclize_newRandomDSQuery(delay, nbytes, customGasLimit)
, configurando apenas o atraso de execução (em segundos), o número de bytes gerados e o limite de gás para chamar
__callback
.
O uso
random
tem algumas coisas a serem lembradas:
- Para confirmar que o número é realmente aleatório, um tipo especial de verificação é usado - Ledger, que pode ser realizado na blockchain (diferente de todos os outros, mas mais sobre isso posteriormente). Isso significa que, no construtor do contrato inteligente, você precisa definir este método de verificação pela função:
oraclize_setProof(proofType_Ledger);
E no início do retorno de chamada, deve haver uma verificação em si:
function __callback(bytes32 _queryId, string _result, bytes _proof) { require (oraclize_randomDS_proofVerify__returnCode(_queryId, _result, _proof) == 0) ); <...>
Essa verificação requer uma rede real e não funciona com ganache, portanto, para testes locais, você pode remover temporariamente esta linha. A propósito, o terceiro argumento para __callback
aqui é o parâmetro _proof
opcional. É sempre necessário quando um dos tipos de confirmação é usado. - Se você usar um número aleatório para momentos críticos, por exemplo, para determinar o vencedor na loteria, capture a entrada do usuário antes de enviar newRandomDSQuery. Caso contrário, essa situação poderá ocorrer: oraclize chama _callback e a transação é visível para todos na lista pendente. Junto com isso, o próprio número aleatório é visível. Se os usuários puderem continuar a fazer apostas grosseiras, poderão especificar um preço de gás mais alto e aumentar sua taxa antes da execução do _callback, sabendo antecipadamente que serão vencedores.
5. computação
Esta é a mais flexível das fontes. Ele permite que você escreva seus próprios scripts e os use como fonte de dados. A computação ocorre na AWS. Para execução, é necessário descrever o Dockerfile e reuni-lo com arquivos adicionais arbitrários em um arquivo zip e fazer o download do arquivo no IPFS. A implementação deve atender às seguintes condições:
- Escreva a resposta que você deseja retornar com a última linha em stdout
- A resposta deve ter no máximo 2500 caracteres
- A inicialização e execução não devem demorar mais de 5 minutos no total
Para um exemplo de como isso é feito, consideraremos como executar a união mais simples das linhas transmitidas e retornar o resultado.
Dockerfile:
FROM ubuntu:16.04 MAINTAINER "info@rubyruby.ru" CMD echo "$ARG0 $ARG1 $ARG2 $ARG3"
Variáveis de ambiente
ARG0
,
ARG1
, etc. - Estes são os parâmetros passados junto com a solicitação.
Inclua o dockerfile no arquivo morto, inicie o servidor ipfs e adicione esse arquivo lá
$ zip concatenation.zip Dockerfile $ ipfs daemon & $ ipfs add concatenation.zip QmWbnw4BBFDsh7yTXhZaTGQnPVCNY9ZDuPBoSwB9A4JNJD
Usamos o hash resultante para enviar a solicitação via
oraclize_query
no contrato inteligente:
oraclize_query("computation", ["QmVAS9TNKGqV49WTEWv55aMCTNyfd4qcGFFfgyz7BYHLdD", "s1", "s2", "s3", "s4"]);
Uma matriz é usada como argumento, no qual o primeiro elemento é o multihash de archive e todo o restante são os parâmetros que se enquadram nas variáveis de ambiente.
Se você esperar a solicitação ser concluída,
__callback
resultado
s1 s2 s3 s4
.
Auxiliares e subconsultas do analisador
A partir da resposta retornada por qualquer fonte, é possível pré-selecionar apenas as informações necessárias usando vários auxiliares, como:
1. Analisador JSON
Você viu esse método no primeiro exemplo, em que apenas o preço foi retornado do resultado retornado pelo coinmarketcap:
json(https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=USD).0.price_usd
O caso de uso é bastante óbvio, retornando, por exemplo:
[ { "id": "ethereum", "name": "Ethereum", "symbol": "ETH", "rank": "2", "price_usd": "462.857", "price_btc": "0.0621573", "24h_volume_usd": "1993200000.0", "market_cap_usd": "46656433775.0", "available_supply": "100800968.0", "total_supply": "100800968.0", "max_supply": null, "percent_change_1h": "-0.5", "percent_change_24h": "-3.02", "percent_change_7d": "5.93", "last_updated": "1532064934" } ]
Como esse é um array, usamos o elemento
0
e, a partir dele, o campo
price_usd
2. XML
O uso é semelhante ao JSON, por exemplo:
xml(https://informer.kovalut.ru/webmaster/getxml.php?kod=7701).Exchange_Rates.Central_Bank_RF.USD.New.Exch_Rate
3. HTML
Você pode analisar XHTML usando XPath. Por exemplo, obtenha um valor de mercado com o etherscan:
html(https://etherscan.io/).xpath(string(//*[contains(@href, '/stat/supply')]/font))
MARKET CAP OF $46.148 BillionB
4. Auxiliar binário
Permite cortar pedaços de dados brutos usando a função de fatia (deslocamento, comprimento). Ou seja, por exemplo, temos um arquivo com o conteúdo de “abc”:
echo "abc" > example.bin
Coloque no IPFS:
$ ipfs add example.bin added Qme4u9HfFqYUhH4i34ZFBKi1ZsW7z4MYHtLxScQGndhgKE
Agora corte 1 caractere do meio:
binary(Qme4u9HfFqYUhH4i34ZFBKi1ZsW7z4MYHtLxScQGndhgKE).slice(1, 1)
Na resposta, obtemos
b
Como você deve ter notado, no caso do auxiliar binário, não foi usada a fonte IP, mas o IPFS. De fato, os analisadores podem ser aplicados a qualquer fonte, digamos que não seja necessário aplicar JSON ao que retorna a URL, você pode adicionar esse conteúdo ao arquivo:
{ "one":"1", "two":"2" }
Adicione-o ao IPFS:
$ ipfs add test.json added QmZinLwAq5fy4imz8ZNgupWeNFTneUqHjPiTPX9tuR7Vxp
E então desmonte assim:
json(QmZinLwAq5fy4imz8ZNgupWeNFTneUqHjPiTPX9tuR7Vxp).one
get
1
E um caso de uso particularmente interessante é combinar todas as fontes de dados e analisadores em uma solicitação. Isso é possível usando uma fonte de dados
nested
separada. Usamos o arquivo que acabamos de criar em uma solicitação mais complexa (adicionando valores em dois campos):
[WolframAlpha] add ${[IPFS] json(QmZinLwAq5fy4imz8ZNgupWeNFTneUqHjPiTPX9tuR7Vxp).one} to ${[IPFS] json(QmZinLwAq5fy4imz8ZNgupWeNFTneUqHjPiTPX9tuR7Vxp).two}
Nós temos
3
A solicitação é formada da seguinte maneira: especifique a fonte de dados
nested
e, para cada solicitação, adicione o nome da fonte à sua frente entre colchetes e, além disso, enquadre todas as subconsultas em
${..}
.
Teste
O Oraclize fornece um
serviço útil de validação de consulta sem a necessidade de contratos inteligentes. Basta entrar, escolher uma fonte de dados, um método de verificação e você pode ver que ele retornará para __callback se você enviar as solicitações correspondentes
Para verificação local em conjunto com um contrato inteligente, você pode usar uma
versão especial do Remix IDE que suporta solicitações oraclize.
E para verificar localmente com o ganache, você precisará do
ethereum bridge , que implementará ou permitirá contratos inteligentes na sua rede de teste. Para teste, primeiro adicione a seguinte linha ao construtor do seu contrato:
OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475);
correr
ganache-cli
Então
node bridge --dev
Aguarde até que os contratos terminem e você possa testar. Na saída da
node bridge
do
node bridge
é possível ver os pedidos enviados e as respostas recebidas.
Outra ajuda não é apenas para testes, mas também no uso real - possibilidade de monitorar os pedidos
aqui . Se você estiver solicitando em uma rede pública, poderá usar o hash da transação na qual a solicitação é executada. Se você usar autenticação, lembre-se de que eles garantem que sejam enviados apenas para a rede principal; para outras redes, ele pode retornar 0. Se a solicitação estava na rede local, você pode usar o ID da solicitação, que retorna
oraclize_query
. A propósito, é recomendável manter sempre esse ID, por exemplo, em um mapeamento semelhante:
mapping(bytes32=>bool) validIds;
No momento da solicitação, marque o ID enviado como
true
:
bytes32 queryId = oraclize_query(<...>); validIds[queryId] = true;
E, em
__callback
verifique se a solicitação com esse ID ainda não foi processada:
function __callback(bytes32 myid, string result) { require(validIds[myid] != bytes32(0)); require(msg.sender == oraclize_cbAddress()); validIds[myid] = bytes32(0); <...>
Isso é necessário porque
__callback
em uma solicitação pode ser chamado mais de uma vez devido às peculiaridades dos mecanismos do Oraclize.
Autenticação
Na tabela com as fontes, você pode ver que diferentes fontes podem oferecer suporte a diferentes tipos de confirmações, e podem ser cobradas taxas diferentes. Essa é uma parte muito importante do oraclize, mas uma descrição detalhada desses mecanismos é um tópico separado.
O mecanismo mais comumente usado, pelo menos por nós, é o
TLSNotary com armazenamento no IPFS. O armazenamento no IPFS é mais eficiente porque o
__callback
não retorna as evidências em si (talvez na região de 4-5 kilobytes), mas com um multi-hash muito menor. Para especificar este tipo, adicione uma linha no construtor:
oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS);
Só podemos dizer que esse tipo, grosso modo, nos protege da imprecisão dos dados recebidos do Oraclize. Mas o Oraclize usa servidores da Amazon, que atuam como auditor, portanto, eles só precisam confiar.
Leia mais
aqui .
Conclusão
O Oraclize fornece ferramentas que aumentam significativamente o número de casos de uso de contratos inteligentes, bem como o IPFS, que pode ser visto em várias versões das consultas Oracle. O principal problema é que novamente usamos dados externos sujeitos às ameaças que a blockchain deveria ter protegido: centralização, recursos de bloqueio, alterações de código, falsificação. Mas, embora tudo isso seja inevitável e a opção de obter dados seja muito útil e viável, você só precisa estar ciente do motivo pelo qual o uso da blockchain foi introduzido no projeto e se o uso de fontes externas não confiáveis reduz o benefício a zero.
Se você estiver interessado em alguns tópicos de desenvolvimento do Ethereum que ainda não foram divulgados nesses artigos - escreva nos comentários, talvez os abordemos a seguir.
Imersão em desenvolvimento no Ethereum:
Parte 1: IntroduçãoParte 2: Web3.js e gásParte 3: aplicativo do usuárioParte 4: implantar e depurar trufa, ganache, infura