Como construir uma aplicação BlockChain — Lista de tarefas com Ethereum-Ubuntu 18.04
O terminal deve mostrar que o projecto foi criado com sucesso. Pode-se abrir o editor de texto e verificar que foram criados novos ficheiros e directorias. Agora vamos criar o ficheiro package.json para instalar algumas dependências de desenvolvimento que vamos precisar para o projecto. Podemos fazer isso através deste comando:
Podemos usar bootstrap para todas as dependências do projecto simplesmente copiando e colando o código em baixo para o nosso ficheiro package.json :
Agora podemos instalar todas as dependências através da linha de comandos da seguinte forma:
Agora que todas as dependências foram instaladas, vamos examinar a estrutura da directoria do projecto que acabamos de criar:
contracts: aqui é onde vamos colocar os smart contracts. Nós já temos uma migração do contracto que vai lidar com as nossas migrações para o blockchain.
migrations: aqui é onde se encontram todos os ficheiros de migração. Estas migrações são semelhantes às frameworks de desenvolvimento web que necessitam de migrações para mudar o estado da base de dados. Sempre que implementamos um smart contract para o blockchain, vamos actualizar o estado do blockchain, para isso precisamos de uma migração.
node_modules: aqui é onde as dependências Node estão instaladas.
test: aqui vamos colocar os nossos testes do nosso smart contract.
truffle-config.js: este é o ficheiro principal de configuração do nosso projecto Truffle, aqui vamos colocar coisas como a configuração à network, por exemplo.
Agora vamos começar a desenvolver o smart contract que vai gerir a nossa lista de tarefas. Podemos fazer isto criando um ficheiro novo no directório dos contracts da seguinte forma:
touch ./contracts/TodoList.sol
Dentro desta directoria, vamos desenvolver o smart contract da nossa lista de tarefas. Primeiro temos que especificar a versão, assim:
pragma solidity ^0.5.0;
Podemos declarar o smart contract assim:
pragma solidity ^0.5.0;
contract TodoList
Para sabermos o numero de tasks dentro da lista de tarefas vamos criar uma variável simples chamada taskCount.
pragma solidity ^0.5.0;
contract TodoList
O taskCount é uma variável especial chamada “variável de estado”. Qualquer que seja o dado que guardamos dentro desta variável de estado, esta será armazenada no blockchain. Muda o estado do smart contract e tem alcance dentro de todo o smart contract, diferentemente das variáveis locais que apenas têm alcance dentro das funções. Podemos colocar a variável a 0 da seguinte forma:
pragma solidity ^0.5.0;contract TodoList
Agora podemos criar uma maneira para aceder ao valor desta variável de estado fora do contracto. Podemos fazer isto com um modificador especial chamado public no Solidity. Quando fazemos isto, o Solidity cria automáticamente a função taskCount() para que assim possamos aceder ao valor desta variável fora do smart contract. Isto vai ser útil quando interagirmos com o smart contract na consola, na aplicação client side e dentro dos ficheiros de teste.
Agora vamos compilar o smart contract para termos a certeza que não há erros:
truffle compile
Se chegou até aqui conseguiu instalar o seu primeiro smart contract do Ethereum. Deve ter reparado que um novo ficheiro é gerado sempre que compile o smart contrat no seguinte caminho: “./build/contracts/TodoList.json”. Este ficheiro é o ficheiro ABI do smart contract, “Abstract Binary Interface”. Este ficheiro tem várias responsabilidades, mas as duas mais importantes são:
- Conter a versão compilada em bytecode do smart Contract do Solidity que pode ser executado no EVM (Ethereum Virtual Machine).
- Conter uma representação JSON das funções do smart contract que pode ser exposto para clientes externos, como o client side de aplicações JavaScript.
O próximo passo é aceder ao smart contract dentro da consola Truffle. No entanto, não podemos executar a consola Truffle porque a nossa aplicação ainda não está conectada à network pessoal do blockchain do Ganache que configurámos na secção das dependências. Para interagir com o smart contract na network pessoal do blockchain dentro da consola do Truffle, temos de fazer algumas coisas:
- Actualizar a configuração do ficheiro do projeto para especificar à network do blockchain pessoal a que nós nos queremos conectar (Ganache).
- Criar o script da migração que diz ao Truffle como implementar o smart contract para a network do blockchain.
- Executar a nova migração criada, implementando o smart contract à network do blockchain.
Primeiro vamos atualizar o ficheiro da configuração do projeto para especificar a network do blockchain pessoal que nós queremos configurar na primeira secção. Procure o ficheiro truffle-config.js e coloque o seguinte código:
module.exports =
},
solc:
}
}
Nota: este código deve corresponder às definições padrão fornecidas por a network do blockchain pessoal do Ganache. Se mudou alguma definição dentro das definições dentro da página do Ganache, como por exemplo o porto, essas alterações devem ser refletidas aqui.
A seguir vamor criar a migração do script dentro da diretoria das migrações para implementar o smart contract para a network do blockchain pessoal. Na raiz do projeto, vamos criar um novo ficheiro utilizando o seguinte comando:
touch migrations/2_deploy_contracts.js
Sempre que criamos um novo smart contract, estamos a atualizar o estado do blockchain. Lembre-se que um blockchain é fundamentalmente uma base de dados. Por isso, sempre que mudamos permanentemente o seu estado, temos que fazer a migração. Isto é muito semelhante com outras aplicações web que provavelmente já desenvolveu.
Repare que todos os ficheiros dentro das migrações estão numerados para que o Truffle saiba em que ordem deve executá-los. Dentro desta novo ficheiro da migração criada, podemos usar o seguinte código para implementar o smart contract:
var TodoList = artifacts.require(“./TodoList.sol”); module.exports = function(deployer) ;
Precisamos de declarar uma variável no contracto que acabámos de criar chamada TodoList. A seguir, vamos adicioná-la ao manifest dos contratos implementados para garantir que é implementada quando executarmos a migração. Agora vamos correr o script desta migração através da linha de comandos, assim:
truffle migrate
Agora vamos abrir a consola para interagir com o smart contract. Podemos abrir a consola do truffle através de linha de comandos, assim:
truffle console
Agora que estamos na consola, vamos obter a instância para implementar o smart contract e ver se podemos ler o taskCount do contracto através do seguinte código:
todoList = await TodoList.deployed()
O TodoList é o nome da variável que criámos no ficheiro da migração. Recuperamos a instância implementada do contracto com a função deployed(), e atribuímos ao todoList. Note que usámos um await, isto porque é necessário interagir com o blockchain de maneira assíncrona. Por isso é que o JavaScript é uma excelente escolha para interações client-side com os smart contracts do blockchain. Uma das formas mais populares é async/await, que foi a que escolhi.
Primeiro, podemos obter o endereço do smart contract que foi implementado no blockchain da seguinte maneira:
todoList.address
// => ‘0xABC123…’
Agora podemos ler o valor do taskCount da storage assim:
taskCount = await app.taskCount()
// => 0
Se chegou até aqui conseguiu complementar a primeira secção deste tutorial com sucesso! Até agora fizemos:
- Configuração da nossa máquina para o desenvolvimento de um blockchain.
- Criamos um projeto truffle
- Criamos o nosso primeiro smart contract
- Interagimos com o smart contract que criámos no nosso blockchain
Se ficou preso em alguma parte, volte atrás e reveja onde se enganou.
Agora vamos começar a listar as tarefas no lista de tarefas. Aqui estão todos os passos que vamos completar nesta secção:
- Escrever código para listar tarefas no smart contract
- Listar as tarefas do smart contract
- Listar as tarefas na aplicação client-side
- Escrever o teste para listar as tarefas
Para listar tarefas dentro do smart contract, vamos precisar de uma maneira para modelar a tarefa no Solidity. O Solidity permite que possamos definir os nossos tipos de dados através de structs. Podemos modelar qualquer dados arbitrariamente através de structs da seguinte maneira:
pragma solidity ^0.5.0;
contract TodoList
}
Primeiro vamos modelar a tarefa com a palavra struct seguida por o nome da nova struct Task. Note que isto não representa nenhuma instância de uma Task, mas sim, simplesmente a definição da estrutura da Task. Dentro desta struct temos os seus atributos:
- uint id-este é o identificador único para a struct. Terá um id, tal como uma base de dados tradicional. Note que nós declaramos o tipo de dados para este identificador como um uint, o que significa “unsigned integer”. Isto simplesmente significa que é um inteiro não negativo. Não tem sinal (sign).
- string content–este é o texto da tarefa na lista de tarefas contido numa string.
- bool completed-este é o checkbox do estado da lista de tarefas, que é true/false. Será true a tarefa quando for concluída ou verificada da lista de tarefas.
Agora que modelámos a tarefa, precisamos de um local para colocar todas as tarefas na lista! Nós queremos armazená-las no blockchain para que o estado do blockchain seja persistente. Podemos aceder o armazenamento do blockchain com uma variável de estado, como fizemos com o taskCount. Vamos criar uma variável de estado chamada tasks. Esta variável vai usar um tipo especial de dados da estrutura de dados do Solidity chamada mapping assim:
pragma solidity ^0.5.0;
contract TodoList
mapping(uint => Task) public tasks;
}
O mapping no Solidity é muito parecido com um array associativo ou um hash usado em outras linguagens de programação. Cria pares valores-chave que ficam armazenados no blockchain. Vamos usar um id único como chave. O valor será a própria tarefa.
Agora vamos criar a função para criar as tarefas. Isto vai permitir-nos que possamos adicionar novas tarefas à nossa lista como padrão para assim as podermos listar na consola.
pragma solidity ^0.5.0;
contract TodoList
mapping(uint => Task) public tasks;function createTask(string memory _content) public
}
Vou explicar esta função:
- Primeiro, vamos criar a função com a palavra function e damos-lhe o nome createTask().
- Vamos permitir que a função aceite um argumento chamado _content, que vai ser o texto da tarefa. Vamos especificar que este argumento vai ser do tipo string, que vai persistir na memória.
- Deixamos a função pública, para que possamos chamá-la fora do smart contract, como na consola, ou através do client-side, por exemplo.
- Dentro da função, vamos criar um id para a nova tarefa. Nós simplesmente pegamos na taskcount e incrementamos por 1.
- Agora chamamos uma nova estrutura para a tarefa chamando a Task(taskCount, _content, false) e passamos os valores da nova tarefa.
- A seguir, vamos guardar a nova tarefa no blockchain adicionando ao mapeando das tasks assim: task[taskCount] = … .
Agora vamos adicionar uma tarefa à lista quando o smart contract for implementado no blockchain para que tenha uma tarefa padrão que nós possamos inspecionar através da consola. Podemos fazer isto chamando a função createTask() dentro do construtor do smart contract assim:
contract TodoList
// ….
}
O construtor acima é apenas corrido uma vez, sempre que o contracto é inicializado. Dentro desta função vamos ter que criar uma tarefa padrão a qual chamei “Teste Inicial”.
Agora vamos implementar o smart contract no blockchain. Para fazermos isto, primeiro precisamos de implementar uma nova copia do nosso código. Lembre-se, o código do smart contract é imutável. Não pode mudar. Então, precisamos de criar um novo smart contract sempre que fizermos alterações. Felizmente o Truffle fornece um shortcut para ajudar nisto. Podemos voltar a executar as migrações assim:
truffle migrate — reset
Agora vamos copiar o smart contract no blockchain. Agora vamos listar todas as tarefas na consola.
truffle console
Dentro do consola, vamos implementar uma cópia do novo smart contract.
todoList = await TodoList.deployed()
Agora podemos ir buscar a tarefa da lista chamando a função tasks(). Isto vai permitir que possamos aceder a valores das tasks mapeadas por id. Vamos simplesmente passar o id da primeira task da lista, para isso podemos chamar esta função:
task = await todoList.tasks(1)
Agora vamos criar o código do client side para interagir com a lista de tarefas do smart contract. Vamos ter que criar os seguintes ficheiros no projeto:
- bs-config.json
- src/index.html
- src/app.js
Vamos usar lite-server para todos os ficheiros do client side. Vamos precisar de dizer ao lite-server onde todos estes ficheiros estão. Podemos fazer isto atualizando a configuração do browsersync para o lite-server dentro do ficheiro bs-config.json. Copie esta configuração para o seu ficheiro:
}
}
Esta configuração diz ao lite-server para exportar os ficheiros que estão no src e build/contracts para a raiz do servidor. Também adiciona um alias para todos os ficheiros no node_modules para aparecer na route vendor. Isto vai nos permitir extrair as dependências do projeto, como o bootstrap, no lado do cliente com a rota vendor.
Agora vamos preencher o código HTML para a nossa lista de tarefas. Este tutorial é focado para o blockchain por isso não vamos perder tempo no HTML e CSS. Basta copiar o código seguinte:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Dapp University | Todo List</title> <!-- Bootstrap -->
<link href="vendor/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]--> <style>
main
#content
form
ul
#completedTaskList .content
</style>
</head>
<body>
<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://www.dappuniversity.com/free-download" target="_blank">Dapp University | Todo List</a>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap d-none d-sm-none d-sm-block">
<small><a class="nav-link" href="#"><span id="account"></span></a></small>
</li>
</ul>
</nav>
<div class="container-fluid">
<div class="row">
<main role="main" class="col-lg-12 d-flex justify-content-center">
<div id="loader" class="text-center">
<p class="text-center">Loading...</p>
</div>
<div id="content">
<!-- <form onSubmit="App.createTask(); return false;">
<input id="newTask" type="text" class="form-control" placeholder="Add task..." required>
<input type="submit" hidden="">
</form> -->
<ul id="taskList" class="list-unstyled">
<div class="taskTemplate" class="checkbox" style="display: none">
<label>
<input type="checkbox" />
<span class="content">Task content goes here...</span>
</label>
</div>
</ul>
<ul id="completedTaskList" class="list-unstyled"> </ul>
</div>
</main>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="vendor/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="vendor/truffle-contract/dist/truffle-contract.js"></script>
<script src="app.js"></script>
</body>
</html>
Agora vamos preencher o código de JavaScript para este secção. Vamos adicionar o código para o ficheiro app.js que criámos recentemente, assim:
App = , load: async () => , // https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8
loadWeb3: async () => else
// Modern dapp browsers...
if (window.ethereum) )
} catch (error)
}
// Legacy dapp browsers...
else if (window.web3) )
}
// Non-dapp browsers...
else
}, loadAccount: async () => , loadContract: async () => , render: async () => // Update app loading state
App.setLoading(true) // Render Account
$('#account').html(App.account) // Render Tasks
await App.renderTasks() // Update loading state
App.setLoading(false)
}, renderTasks: async () => else // Show the task
$newTaskTemplate.show()
}
}, setLoading: (boolean) => else
}
} $(() => )
})
Vou explicar este código. Foi criado um novo objeto App que contem todas as funções que precisamos para correr a aplicação JavaScript.
- loadWeb3() web3.js é uma biblioteca JavaScript para permitir que a aplicação client-side possa interagir com o blockchain. Nós configuramos o web3 aqui. Este é a configuração web3 padrão específica para o Metamask. Não se preocupe se não perceber o que está a acontecer aqui. Este é uma cópia de um ficheiro sugerido por o Metamask.
- loadContract() Aqui onde carregamos a data do smart contract do blockchain. Nós criamos uma representação JavaScript do smart contract com a biblioteca do contrato Truffle. Depois carregamos a data do smart contract com o web3. Isto vai permitir-nos listar tarefas na lista.
- renderTasks() Aqui é onde listamos as tarefas na lista de tarefas. Repare que nós criamos um loop for para aceder a cada tarefa individualmente. Isto porque não podemos ir buscar o mapeamento total das tarefas do smart contract. Primeiro temos que determinar o taskCount e ir buscar cada uma delas uma por uma.
Agora vamos começar o servidor web para confirmar que o projeto vai carregar no browser.
npm run dev
Conseguimos carregar a aplicação client side com sucesso! Repare que a nossa aplicação diz “Loading…”. Isto é porque ainda não estamos registados no blockchain. Para nos conectarmos ao blockchain, precisamos de importar uma das contas do Ganache para o Metamask.
Quando se conectar com o Metamask, deve ver todos os contratos e dados da conta carregada.
E pronto, a lista de tarefas está feita.
Agora vamos escrever um teste básico para confirmar que a lista do smart contract está a funcionar corretamente. Primeiro vou apenas explicar o porque é que testar o smart contract é tão importante. Primeiro queremos confirmar que não temos quaisquer erros por algumas razões:
- Todo o código no blockchain Ethereum é imutável, não pode ser mudado. Se o contracto contem algum erro, precisamos de o desabilitar e implementar uma nova cópia. Esta nova cópia não vai ter o mesmo estado do contracto antigo, e terá um endereço diferente.
- Implementar contractos custa gas, porque cria a transação e escreve a data para o blockchain. Isto custa Ether, e nós queremos minimizar o Ether que temos que pagar.
- Se nenhuma função no contracto contem erros, a conta que está a chamar esta função pode potencialmente gastar Ether e pode não se comportar como o esperado.
Vamos criar um ficheiro de teste assim:
test/TodoList.test.js
Vamos escrever todos os nossos testes em JavaScript dentro deste ficheiro com o Mocha testing framework e com Chai assertion library. Estes vêm em conjunto com o framework Truffle. Vamos escrever todos estes testes em JavaScript para simular a interação client-side com o nosso smart contract, será muito idêntico ao que fizemos na consola. Aqui está todo o código para os testes:
const TodoList = artifacts.require('./TodoList.sol') contract('TodoList', (accounts) => )
it('deploys successfully', async () => )
it('lists tasks', async () => )
})
Vou passar a explicar este código. Primeiro, fazemos o require do contracto e atribuímos-lhe uma variável, como fizemos na migração. A seguir, chamamos a função contract e escrevemos todos os testes dentro desta função callback. Esta função callback fornece uma variável chamada “accounts” que representa todas as contas no nosso blockchain, fornecido por o Ganache.
Este primeiro teste verifica que o smart contract lista as tarefas propriamente verificando a tarefa padrão que nós criámos quando inicializamos a função.
Agora vamos correr os testes através da linha de comandos assim:
truffle test
Passámos os testes com sucesso! Se não funcionou volta atrás e verifique o que tem errado.
Nós já criámos a função para criar as tarefas, mas ainda não está completa. Isto porque nós queremos desencadear um evento sempre que uma nova tarefa for criada. O Solidity permite-nos desencadear eventos arbitrariamente o que permite que consumidores externos se possam subscrever. Isto vai permitir-nos que possamos “escutar” estes eventos dentro da aplicação client side, etc… Vamos criar a TaskCreated() e desencadeá-la sempre que a nova tarefa for criada na createTask(), assim:
pragma solidity ^0.5.0;contract TodoList
}
Agora vamos criar um teste para confirmar que este evento é desencadeado sempre que uma nova tarefa é criada. Vamos inspecionar o recibo da transação quando a nova tarefa for criada. Isto irá conter toda a informação do log que irá conter o evento da data. Podemos inspecionar a data dentro do nosso teste desta forma:
it('creates tasks', async () => )
Agora corremos o teste:
truffle test
Boa, passámos! Agora vamos implementar uma nova cópia do smart contract para o blockchain porque alterámos o código:
truffle migrate --reset
Agora vamos atualizar o código client side. Primeiro vamos tirar de comentário o código form no index.html:
<form onSubmit="App.createTask(); return false;">
<input id="newTask" type="text" class="form-control" placeholder="Add task..." required>
<input type="submit" hidden="">
</form>
Agora vamos adicionar a função createTask() no ficheiro app.js assim:
createTask: async () => ,
Já devemos ser capazes de adicionar uma nova tarefa! Repare que ainda não temos um botão “submit” no formulário. Foi deixado de fora para simplificar a interface. Tem que carregar na tecla “enter” para submeter a tarefa. Assim que o fizer terá uma notificação da confirmação. Tem que assinar esta transação para criar a tarefa.
A última coisa a fazer é o “check off” para completar a tarefa. Depois de o fazermos, é necessário que a tarefa apareça na lista “completed”. Primeiro vamos atualizar o smart contract. Vamos adicionar um evento TaskCompleted(), e vamos desencadeá-lo dentro da nova função toggleCompleted(), assim:
pragma solidity ^0.5.0;contract TodoList
}
Agora vamos escrever o teste assim:
it('toggles task completion', async () => )
Agora vamos correr o teste:
truffle test
E passou! Agora vamos implementar uma nova cópia do smart contract no blockchain porque o código foi alterado:
truffle migrate --reset
Agora vamos atualizar o código client side. Primeiro vamos tirar de comentário o evento dentro da função renderTasks():
$newTaskTemplate.find('input')
.prop('name', taskId)
.prop('checked', taskCompleted)
.on('click', App.toggleCompleted)
Agora vamos adicionar a função toggleCompleted() no ficheiro app.js assim:
toggleCompleted: async (e) => ,
Agora experimente completar uma tarefa na aplicação. Depois de assinar a transação, a tarefa vai ser completada da lista de tarefas!
A aplicação blockchain distribuída por smart contracts Ethereum foi costruida com sucesso.
Referência: https://www.dappuniversity.com/articles/blockchain-app-tutorial
https://medium.com/@junior_lopes_97/como-construir-uma-aplica%C3%A7%C3%A3o-blockchain-lista-de-tarefas-com-ethereum-ubuntu-18–04–584ec84e53a9
Published at Tue, 04 Feb 2020 23:36:47 +0000
{flickr|100|campaign}
