
No último artigo,
“Autonomous Agents”, ou se executamos o código na plataforma aberta de criptografia da Obyte, conversamos sobre o que são agentes autônomos e os comparamos aos contratos inteligentes da Ethereum. Vamos agora escrever nosso primeiro agente autônomo (AA) usando o exemplo de ataque de 51%. E no final do artigo, analisaremos maneiras de aprimorá-lo: como proteger os jogadores contra perda / perda de fundos e como melhorar o algoritmo para reduzir o efeito de "baleias" com grandes depósitos no resultado do jogo.
Os originais de ambos os agentes independentes estão sempre disponíveis no
editor de código Oscript on -
line na forma de modelos; basta selecioná-los no menu suspenso:
"51% attack game" e
"Fundraising proxy".Antes de começar a escrever AA no Oscript, recomendo a leitura do
Guia de Introdução (eng) na nossa documentação para familiarizar-se rapidamente com os princípios básicos da escrita de AA.
A essência do jogo
Várias equipes competem simultaneamente pelo direito de coletar todo o conjunto de fundos arrecadados. Os jogadores da equipe fazem depósitos, aumentando assim o valor total da piscina. A equipe que fez depósitos de pelo menos 51% de todos os fundos levantados e mantidos como líder por pelo menos um dia vence. O pool é distribuído entre os jogadores da equipe vencedora na proporção da contribuição feita, assim cada participante pode potencialmente dobrar seu investimento.
Assim que qualquer uma das equipes coleta> = 51% de todos os fundos, é declarado anteriormente o vencedor e seus participantes não podem mais fazer depósitos. Mas todas as outras equipes têm 24 horas para ultrapassar a equipe vencedora. Assim que isso acontece, a equipe de ultrapassagem agora se torna o vencedor e o cronômetro inicia a contagem regressiva novamente.
Nossa implementação emitirá "compartilhamentos" para cada depositante em troca de bytes, na proporção de 1 para 1, que, se a equipe vencer, o participante poderá trocar por um compartilhamento no pool ganho. Os estoques são um ativo na plataforma Obyte, lançada especificamente para cada equipe. Eles, como qualquer ativo, podem ser transferidos / negociados, vendendo seus ganhos potenciais para outras pessoas. O preço da ação dependerá do mercado avaliar as chances de vitória da equipe.
Qualquer um pode criar uma equipe. O criador da equipe pode definir uma comissão sobre a vitória, que será cobrada de cada membro da equipe em caso de vitória.
Também aplicaremos o segundo AA em nosso jogo, em nome do criador da equipe, que “financiará” a quantia necessária e somente se for alcançado (em nosso exemplo, ao atingir> 51% do pool de jogos), enviará todos os fundos arrecadados para o endereço AA jogos, caso contrário, o dinheiro pode ser retirado livremente.
Escrevendo Código Oscript
Deixe-me lembrá-lo de que o código AA é chamado toda vez que qualquer transação chega ao endereço desse AA, o chamado transação de gatilho. Na verdade, AA é um código que, em resposta à entrada (dados em uma transação de gatilho e o estado atual de AA, armazenado em variáveis de estado) gera saída (outra transação de "resposta") ou altera seu estado. Nossa tarefa é programar as regras da resposta do AA para inserir dados. Todas as transações no Obyte são um conjunto de mensagens, na maioria das vezes são mensagens de "pagamento" ou mensagens de "dados" etc.
Inicialização
Primeiro, inicializamos nosso AA. O bloco init é chamado toda vez que AA é iniciado, no início. Nele, definiremos constantes locais para um acesso mais conveniente aos valores.
{ init: `{ $team_creation_fee = 5000; $challenging_period = 24*3600; $bFinished = var['finished']; }`, messages: { cases: [ ] } }
String
$ bFinished = var ['terminado']; lê a variável
finalizada do estado AA.
Uma matriz das mensagens resultantes será enquadrada pelos
casos: {} , que é semelhante ao habitual
switch / case / bloco
padrão , com base nas condições nos blocos filho if, apenas uma das mensagens será selecionada ou, se nenhum bloco retornar
verdadeiro , será última mensagem selecionada.
Criamos uma equipe
Portanto, o primeiro bloco processará transações para criar um novo comando:
Como vemos, a condição para a execução desse bloco é a primeira no bloco if e é uma verificação de que a transação acionadora contém uma mensagem com o tipo de dados (
trigger.data.create_team ), que possui uma chave chamada
create_team e que o jogo ainda não acabou (
! $ bFinished ). A constante local
$ bFinished pode ser acessada de qualquer lugar no código se tiver sido inicializada no bloco init. Se pelo menos uma dessas condições não fosse atendida, o bloco de casos pai continuaria simplesmente executando e verificando as condições para as seguintes mensagens, ignorando esta.
No próximo bloco
init , não inicializamos nada, mas verificamos as condições necessárias, sem as quais a transação de gatilho é considerada incorreta:
if (var['team_' || trigger.address || '_asset']) bounce('you already have a team'); if (trigger.output[[asset=base]] < $team_creation_fee) bounce('not enough to pay for team creation');
Aqui, concatenamos (usando ||) a string com a variável do acionador da transação e tentamos descobrir se existe uma variável chamada
'team_' || trigger.address || '_asset' em nossa história de AA.
A chamada de
devolução () reverte todas as alterações feitas no momento atual e retorna um erro ao chamador.
Observe também como a pesquisa dentro da transação do acionador é
realizada :
trigger.output [[asset = base]] está procurando saída com o
asset == base , que retornará a quantidade em bytes (ativo base = bytes) que foi especificada na transação do acionador. E se esse valor não for suficiente para criar uma nova equipe, chamamos bounce (), consumindo silenciosamente todos os bytes recebidos menos bounce_fee, que por padrão é de 10.000 bytes.
Em seguida, começa a maior parte do código de formação de equipe. Resumidamente, o algoritmo é o seguinte:
- A primeira mensagem libera um novo ativo ( aplicativo: 'ativo' )
- A segunda mensagem retorna tudo o que for maior que o número necessário de bytes para criar o comando ( aplicativo: 'pagamento' ). Confira o bloco if aqui. Se essa condição for falsa (o criador enviou exatamente o número necessário de bytes), essa mensagem não será incluída na transação resultante, mas simplesmente será descartada.
- A terceira mensagem altera o estado do nosso AA ( app: 'state' ): escreva o arquivo_da_exposição passado como argumento ou defina-o como 0 se não foi passado para a transação do acionador. A construção var1, caso contrário, var2 retornará var1 se for convertida para true, caso contrário, retornará var2. Aqui encontramos a variável response_unit, que sempre contém o hash da unidade resultante. Nesse caso, porque a unidade resultante criará um novo ativo chamado asset-a e será o hash da unidade de criação. A string response ['team_asset'] = response_unit simplesmente grava o mesmo hash (ou ativo para o comando fornecido) na matriz responseVars na unidade final. A matriz de resposta também pode ser lida para a pessoa que fez a transação do acionador, bem como nos ouvintes de eventos inscritos nos eventos com esse AA.
Aceitamos depósitos
Com a criação da equipe concluída, vá para o próximo bloco - processando depósitos dos membros da equipe.
Do novo que encontramos aqui é a emissão de tokens do ativo da equipe selecionada para seu participante em troca de seu depósito em bytes:
asset: `{var['team_' || trigger.data.team || '_asset']}`, outputs: [{address: "{trigger.address}", amount: "{trigger.output[[asset=base]]}"}
Como lembramos, a variável de estado
'team_' || trigger.data.team || Salvamos o
'_asset' na fase de criação da equipe e ele armazena o hash da unidade em que criamos o ativo para essa equipe, ou seja, o nome desse ativo-a.
No mesmo bloco, a condição principal é verificada em 51%:
if (var['team_' || trigger.data.team || '_amount'] > balance[base]*0.51){ var['winner'] = trigger.data.team; var['challenging_period_start_ts'] = timestamp; }
Se após essa transação de gatilho o saldo da equipe especificada exceder 51%, a equipe será declarada vencedora e registraremos o carimbo de data / hora unix atual (inicie o cronômetro).
Esse registro de data e hora será verificado no terceiro bloco quando recebermos uma transação de gatilho com uma tentativa de terminar o jogo:
Pagamos um prêmio
E o bloco final, o mais agradável, é o pagamento de todo o depósito aos vencedores:
O bloco de inicialização é interessante aqui, no qual calculamos os valores necessários com antecedência:
$share = $asset_amount / var['team_' || $winner || '_amount']; $founder_tax = var['team_' || $winner || '_founder_tax']; $amount = round(( $share * (1-$founder_tax) + (trigger.address == $winner AND !var['founder_tax_paid'] ? $founder_tax : 0) ) * var['total']);
Todos os participantes da equipe, exceto seu criador, recebem um valor proporcional à sua contribuição inicial (1 por 1 bytes em troca de tokens de ativos enviados em uma transação de gatilho). O criador também recebe uma comissão (é verificado que o endereço da
pessoa que enviou a transação do acionador é igual ao endereço do criador da equipe vencedora
trigger.address == $ winner ). É importante não esquecer que a comissão deve ser paga apenas uma vez, e o criador pode enviar infinitamente muitas transações de gatilho; portanto, salvamos a bandeira no estado AA.
Comece o jogo
Então, o código está pronto. Aqui está uma lista completa:
código AA completo { init: `{ $team_creation_fee = 5000; $challenging_period = 24*3600; $bFinished = var['finished']; }`, messages: { cases: [ {
Vamos verificar a validade do código e tentar implantá-lo no testnet.
- Acesse o editor on-line: https://testnet.oscript.org
- Cole nosso código e clique em Validar. Se tudo estiver correto, veremos um cálculo da complexidade do código: AA validado, complexidade = 27, ops = 176 . Aqui ops é o número de operações em nosso código, complexidade é a complexidade do código. Os AAs Obyte não possuem ciclos, mas mesmo isso não permite 100% de proteção da rede contra AAs maliciosos com código incorreto. Portanto, todos os AAs têm um limite superior de complexidade, complexidade = 100. A complexidade do código é calculada no momento da implantação e todas as ramificações do código são levadas em consideração. Algumas operações são relativamente fáceis, como ± e outras, não acrescentando complexidade. Outros, como acesso ao banco de dados (modificação de estado) ou cálculos complexos (chamando algumas funções) adicionam complexidade. Para descobrir quais operações são fáceis e quais são complexas, consulte a referência de idioma .
- Clique em Implantar. Vemos algo semelhante a
Check in explorer: https://testnetexplorer.obyte.org/#DiuxsmIijzkfAVgabS9chJm5Mflr74lZkTGud4PM1vI= Agent address: 6R7SF6LTCNSPLYLIJWDF57BTQVZ7HT5N
Aconselho que você siga o link no explorer e verifique se a unidade com o nosso AA está postada na rede. O Explorer também mostra o código completo do nosso AA, como todos os AAs na rede Obyte são de código aberto. Você pode estudar o código de qualquer agente em seu endereço.
Crowdfunding
E agora para as otimizações prometidas. Na implementação atual, cada jogador envia dinheiro imediatamente para o endereço AA do jogo, indicando seu time. Ao mesmo tempo, a equipe pode nunca se tornar um líder, e o dinheiro já foi enviado. Podemos otimizar o processo de coleta de dinheiro e evitar a situação em que enviamos dinheiro para uma equipe que nunca será vencedora. Isso pode ser feito usando o segundo AA, organizar o chamado financiamento coletivo de fundos, estabelecendo uma meta dinâmica para a quantidade de fundos captados igual a 51% da quantidade no jogo.
Em Oscript, podemos ler o estado de qualquer outro AA, portanto temos a oportunidade de definir uma meta dinâmica em nosso AA de financiamento coletivo.
O algoritmo será o seguinte: o criador da equipe pede aos jogadores que enviem dinheiro não para o endereço do jogo, mas para o endereço de um agente que implementa a funcionalidade de financiamento coletivo. Este agente armazenará a quantidade de bytes coletados em casa e os enviará ao jogo apenas se ele coletar> = 51% da quantidade no jogo. E imediatamente esse time se torna um líder. Se a quantia necessária não for coletada, o dinheiro será simplesmente devolvido aos jogadores. Na fase de captação de recursos, os jogadores não receberão fichas de jogo em equipe, mas fichas de crowdfunding, que no futuro poderão ser reembolsadas ou trocadas por fichas de jogo, se bem-sucedidas.
No próximo artigo, implementamos essa funcionalidade.
Coletamos não bytes, mas certificações
Na forma mais simples do jogo "Attack 51%", estamos falando dos montantes de fundos arrecadados. Assim, "baleias" com carteiras grandes podem levar a maior parte dos ganhos.
Para tornar o jogo mais honesto para todos os participantes, tentaremos contar não a quantidade de bytes enviados, mas o número de participantes nas equipes. A equipe que conseguiu atrair o número máximo de participantes vence. Cada jogador investirá uma quantia fixa, digamos 1 GB, e contará como uma unidade no pool de equipes. Mas nada nos impede de criar um número infinito de novos endereços; portanto, a condição crítica será que apenas os endereços vinculados a outros IDs sejam permitidos no jogo, nos quais a regra "uma pessoa - um ID" é respeitada. Essa ligação é chamada de
certificação . Um exemplo de certificação é a aprovação do procedimento KYC, após o qual uma mensagem é enviada ao DAG sobre a conexão entre o endereço Obyte e o hash de dados pessoais (os dados pessoais são armazenados na carteira do usuário e ele pode divulgá-los a contratantes individuais, se ele quiser, mas para essa tarefa não é necessário, é importante apenas o fato de vincular). Outro exemplo de certificação é a certificação de email em domínios em que a regra “uma pessoa - um email” é respeitada, por exemplo, nos domínios de algumas universidades, empresas e países. Assim, uma pessoa real será contada apenas uma vez.
Os agentes autônomos podem solicitar o status de certificação de endereços na rede, para que haja um número mínimo de alterações no código.
Eu sugiro que os leitores nos comentários sugiram em quais lugares e como exatamente é necessário alterar as linhas de código da versão atual do jogo para implementar isso. Para ajudar, como sempre, a
Referência da Linguagem Oscript .
Nossa
discórdia e
Twitter