Skip to content
Snippets Groups Projects
Commit 9e76e7ae authored by Andrés Blanco's avatar Andrés Blanco
Browse files

Inicio

parent d00e17da
No related branches found
No related tags found
1 merge request!1Merge Develop into Master
Showing
with 15391 additions and 1 deletion
(hay que escribir esto) # TSA2
\ No newline at end of file
Descargar el proyecto:
```shell
git clone https://gitlab.bfa.ar/blockchain/tsa2.git
```
## Dependencias
Todo fue probado con node 10.8 y npm 6.2.
También hay que tener instalado truffle de manera global:
```shell
npm install -g truffle
```
## Instalación de desarrollo
Para desarrollo se utilizó Ganache, una "one click blockchain". Se puede
descargar desde acá: https://truffleframework.com/ganache
El proyecto está compuesto por tres componentes, los contratos, una api rest
y una interfaz gráfica.
### Deployar los contratos
Para manejar los contratos se utilizó truffle, para hacer el deploy de los
contratos hay que correr dentro del directorio ```contract``` el siguiente comando
```shell
truffle migrate
```
Si se hacen modificaciones sobre el contrato se debe ejecutar lo siguiente:
```shell
truffle migrate --reset
```
El contrato utilizado es el de Robert con unas pequeñas modificaciones, se puede ver
en ```contract/contracts/Stamper.sol```.
## Correr API
La primera vez hay que ejecutar lo siguiente dentro del directorio ```api```
```shell
npm install
```
Para que la API quede corriendo hay que ejecutar:
```shell
npm run serve
```
Si todo fue bien, se debería ver algo así en la consola:
```shell
➜ api git:(master) ✗ npm run serve
> api@1.0.0 serve /home/ablanco/proyectos/bfa/tsa/api
> nodemon --exec babel-node src/index.js
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `babel-node src/index.js`
Conectando a http://localhost:7545
El servidor es PUBLICO
TSA Api corriendo en 3000!
Conectado exitosamente:
> host: http://localhost:7545
> netId: 5777
> account: 0x80C6C180d044d476437F9F5bCc824dF134A2c9B2
```
La api tiene dos endpoints:
* **/stamp**: Es un POST, recibe en el body un objeto json con sólo una clave
llamada ```hashes``` que es un array de strings que representan los hashes a
stampear.
Devuelve 200 si la operación tuve exito, sino devuelve un error code > 400
* **/verify/:hash**: Es un GET, se le pasa en vez de :hash el hash a verificar.
Devuelve un objecto json con la siguiente estructura:
```json
{stamped: bool, stamps: [ {stamper: address, block: nroDeBlock} ]}
```
```stamped``` es true si tiene algun stamp el hash ese. En ```stamps``` viene la
lista de stampers de ese objecto junto al nro de bloque en el que lo hizo.
## UI
La primera vez hay que ejecutar lo siguiente dentro del directorio ```ui```
```shell
npm install
```
La aplicación está escrita con Vue.js. Para correr el servicio hay que ejecutar:
```shell
npm run serve
```
## Discusión
Cosas que habría que discutir:
* algoritmo de hasheo de archivos (ahora es sha256 se prefiere keccak o sha3?)
* https://emn178.github.io/online-tools/keccak_256_checksum.html
* vale la pena ponerse a ahorrar gas?
\ No newline at end of file
{
"presets": [
["@babel/preset-env", {
"targets": { "node": "10.8" }
}]
]
}
node_modules
.env
*.swp
"use strict";
require("dotenv/config");
var _web = _interopRequireDefault(require("web3"));
var _express = _interopRequireDefault(require("express"));
var _cors = _interopRequireDefault(require("cors"));
var _bodyParser = _interopRequireDefault(require("body-parser"));
var _expressBasicAuth = _interopRequireDefault(require("express-basic-auth"));
var _simpleStamperWrapper = _interopRequireDefault(require("./simpleStamperWrapper"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
async function setupWeb3Ganache() {
try {
let netIsListening = await web3.eth.net.isListening();
let netId = await web3.eth.net.getId();
web3.eth.defaultAccount = (await web3.eth.getAccounts())[0];
console.log(`Conectado exitosamente a Ganache:
> netId: ${netId}
> account: ${web3.eth.defaultAccount}
`);
} catch (e) {
console.error('Error de conexión');
console.error(e);
process.exit(1);
}
}
var web3 = web3;
if (typeof web3 !== 'undefined') {
console.log('Cargando web3 provider desde el entorno'); // Use Mist/MetaMask's provider
//conector.setWeb3Instance(new Web3(web3.currentProvider))
} else {
//console.warn(web3.eth.defaultAccount)
console.log('Conectando a Ganache');
web3 = new _web.default(new _web.default.providers.HttpProvider('http://localhost:7545'));
web3.eth.defaultAccount = web3.eth.accounts[0];
setupWeb3Ganache();
}
const app = (0, _express.default)();
app.use((0, _cors.default)());
app.use(_bodyParser.default.json());
if (process.env.AUTH_TYPE === 'basic') {
if (process.env.API_USER && process.env.API_PASS) {
var users = {};
users[process.env.API_USER] = process.env.API_PASS;
app.use((0, _expressBasicAuth.default)({
users: users,
challenge: true
}));
} else {
console.error("El servidor es PUBLICO");
}
}
app.post('/stamp', async (req, res) => {
let netId = await web3.eth.net.getId();
let ss = new _simpleStamperWrapper.default(web3, netId);
ss.setSender(web3.eth.defaultAccount);
if (!("hash" in req.body)) {
res.status(422);
res.send('No se incluyó la clave hash en el cuerpo del POST');
return;
}
var hash = req.body.hash;
if (!hash.startsWith('0x')) {
hash = '0x' + hash;
}
try {
let txHash = await ss.stamp(hash);
let fullUrl = req.protocol + '://' + req.get('host');
return res.json({
verifyUri: `${fullUrl}/verify/${hash}`,
hash: txHash
});
} catch (e) {
res.status(500);
res.send('Error interno. Chequee el log de la aplicación para más detalles');
}
});
app.get('/verify/:hash', async (req, res) => {
let netId = await web3.eth.net.getId();
let ss = new _simpleStamperWrapper.default(web3, netId);
ss.setSender(web3.eth.defaultAccount);
var value = req.params.hash;
if (!value.startsWith('0x')) {
value = '0x' + value;
}
try {
let info = await ss.verify(value);
return res.json(info);
} catch (e) {
console.error(e);
res.status(404);
res.send("No existe el hash en la base de datos");
}
}); // app.get('/status/:txHash', async (req, res) => {
// try {
// let info = await web3.eth.getTransaction(req.params.txHash, (e, f) => {
// console.log(e)
// console.log(f)
// })
// console.log(info)
// res.send(info)
// } catch (e) {
// res.send(e)
// }
// })
let port = process.env.PORT ? process.env.PORT : 3000;
app.listen(port, () => console.log(`TSA Api corriendo en ${port}!`));
\ No newline at end of file
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
const simpleStamperInterface = require('../../contract/build/contracts/SimpleStamper.json');
class SimpleStamper {
constructor(web3, netId) {
this.web3 = web3;
let address = simpleStamperInterface.networks[netId].address;
this.contract = new web3.eth.Contract(simpleStamperInterface.abi, address);
}
setSender(fromAddress) {
this.from = fromAddress;
}
async stamp(hash) {
console.log(`${hash}: stamping`);
let txPromise = this.contract.methods.stamp(hash).send({
from: this.from
});
txPromise.then(receipt => {
console.log(`${hash}: stamped (bloque: ${receipt.blockNumber})`);
}).catch(error => {
console.error(error);
});
return new Promise((resolve, reject) => {
txPromise.on('transactionHash', txHash => {
resolve(txHash);
});
txPromise.catch(error => {
reject(error);
});
});
}
async verify(hash) {
try {
let blockNumber = await this.contract.methods.getBlockNumber(hash).call();
console.log(`${hash}: blockNmb (${blockNumber})`);
return {
verified: blockNumber > 0,
blockNumber: blockNumber
};
} catch (e) {
console.error(e);
throw e;
}
}
}
var _default = SimpleStamper;
exports.default = _default;
\ No newline at end of file
This diff is collapsed.
{
"name": "api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"serve": "nodemon --exec babel-node src/index.js",
"build": "babel src -d dist ",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.4.3",
"@babel/core": "^7.4.3",
"@babel/node": "^7.2.2",
"@babel/preset-env": "^7.4.3",
"nodemon": "^1.18.11"
},
"dependencies": {
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"dotenv": "^7.0.0",
"express": "^4.16.4",
"express-basic-auth": "^1.2.0",
"web3": "^1.0.0-beta.54"
}
}
const stamperInterface = require('../../contract/build/contracts/Stamper.json')
class Stamper {
constructor(web3, netId) {
this.web3 = web3
console.log(stamperInterface)
let address = stamperInterface.networks[netId].address
this.contract = new web3.eth.Contract(stamperInterface.abi, address)
}
setSender(fromAddress) {
this.from = fromAddress
}
async stamp(hashes) {
console.log(`stamping ${hashes}`)
let hashesToStamp = []
for (let i=0; i < hashes.length; i++) {
let blockNo = await this.contract.methods.getBlockNo(hashes[i], this.from).call()
if (blockNo == 0) hashesToStamp.push(hashes[i])
}
if (hashesToStamp.length == 0) return new Promise( (resolve) => {
console.log(`Los hashes enviados ya están stampeados`)
resolve()
})
let txPromise = this.contract.methods.put(hashesToStamp).send({
from: this.from,
gasLimit: 500000
})
txPromise.then((receipt) => {
console.log(`${hash}: stamped (bloque: ${receipt.blockNumber})`)
}).catch((error) => {
console.error(error)
})
return new Promise((resolve, reject) => {
txPromise.on('transactionHash', (txHash) => {
resolve(txHash)
})
txPromise.catch((error) => {
reject(error)
})
})
}
async verify(hash) {
try {
let count = await this.contract.methods.getObjectCount(hash).call()
if (count == 0) {
return { stamped: false, stamps: [] }
}
var stamps = []
for (var i = 0; i < count; i++) {
let stampPos = await this.contract.methods.getObjectPos(hash, i).call()
let stamp = await this.contract.methods.getStamplistPos(stampPos).call()
stamps.push({ stamper: stamp[1], block: stamp[2].toString() })
}
return { stamped: true, stamps: stamps }
} catch (e) {
console.error(e)
throw e
}
}
}
export default Stamper
\ No newline at end of file
import 'dotenv/config'
import Web3 from 'web3'
import express from 'express'
import cors from 'cors'
import bodyParser from 'body-parser'
import basicAuth from 'express-basic-auth'
//import SimpleStamper from './simpleStamperWrapper'
import Stamper from './StamperWrapper'
/***************************************************/
// Conexión al provider
/***************************************************/
const providerHost = process.env.GETH_HOST || 'http://localhost:7545'
const accountIsSet = process.env.GETH_ACCOUNT || false
// si no se seteó una account se usa esta const como el indice de accounts de ganache
const account = (accountIsSet) ? process.env.GETH_ACCOUNT : 1
var web3 = web3
if (typeof web3 !== 'undefined') {
console.log('Cargando web3 provider desde el entorno')
// Use Mist/MetaMask's provider
//conector.setWeb3Instance(new Web3(web3.currentProvider))
} else {
console.log(`Conectando a ${providerHost}`)
web3 = new Web3(new Web3.providers.HttpProvider(providerHost))
setupWeb3()
}
async function setupWeb3() {
try {
let netIsListening = await web3.eth.net.isListening()
let netId = await web3.eth.net.getId()
web3.eth.defaultAccount = (accountIsSet) ? account : (await web3.eth.getAccounts())[account]
console.log(`Conectado exitosamente:
> host: ${providerHost}
> netId: ${netId}
> account: ${web3.eth.defaultAccount}
`)
} catch (e) {
console.error('Error de conexión')
console.error(e)
process.exit(1)
}
}
/***************************************************/
// Setup API
/***************************************************/
const app = express()
// USE_CORS=0 para disablear
const useCors = process.env.USE_CORS || 1
if (useCors) app.use(cors())
app.use(bodyParser.json())
if (process.env.API_USER && process.env.API_PASS) {
var users = {}
users[process.env.API_USER] = process.env.API_PASS
app.use(basicAuth({
users: users,
challenge: true
}))
} else {
console.log("El servidor es PUBLICO")
}
app.post('/stamp', async (req, res) => {
let netId = await web3.eth.net.getId()
let ss = new Stamper(web3, netId)
ss.setSender(web3.eth.defaultAccount)
if (!("hashes" in req.body)) {
res.status(422)
res.send('No se incluyó la clave hashes en el cuerpo del POST')
return
}
let hashes = req.body.hashes
if (! Array.isArray(hashes)) {
res.status(422)
res.send('La clave hashes debe ser un array')
return
}
for (let i=0; i < hashes.length; i++) {
let hash = hashes[i]
if (! hash.startsWith('0x')) {
hash = '0x' + hash
}
hashes[i] = hash
}
try {
let txHash = await ss.stamp(hashes)
//let fullUrl = req.protocol + '://' + req.get('host')
res.status(200).send('success')
} catch (e) {
console.error(e)
res.status(500)
res.send('Error interno. Chequee el log de la aplicación para más detalles')
}
})
app.get('/verify/:hash', async (req, res) => {
let netId = await web3.eth.net.getId()
let ss = new Stamper(web3, netId)
ss.setSender(web3.eth.defaultAccount)
var value = req.params.hash
if (! value.startsWith('0x')) {
value = '0x' + value
}
try {
let info = await ss.verify(value)
return res.json(info)
} catch (e) {
console.error(e)
res.status(404)
res.send("No existe el hash en la base de datos")
}
})
// app.get('/status/:txHash', async (req, res) => {
// try {
// let info = await web3.eth.getTransaction(req.params.txHash, (e, f) => {
// console.log(e)
// console.log(f)
// })
// console.log(info)
// res.send(info)
// } catch (e) {
// res.send(e)
// }
// })
let port = (process.env.PORT) ? process.env.PORT : 3000
app.listen(port, () =>
console.log(`TSA Api corriendo en ${port}!`),
)
const simpleStamperInterface = require('../../contract/build/contracts/SimpleStamper.json')
class SimpleStamper {
constructor(web3, netId) {
this.web3 = web3
let address = simpleStamperInterface.networks[netId].address
this.contract = new web3.eth.Contract(simpleStamperInterface.abi, address)
}
setSender(fromAddress) {
this.from = fromAddress
}
async stamp(hash) {
console.log(`${hash}: stamping`)
let txPromise = this.contract.methods.stamp(hash).send({
from: this.from
})
txPromise.then((receipt) => {
console.log(`${hash}: stamped (bloque: ${receipt.blockNumber})`)
}).catch((error) => {
console.error(error)
})
return new Promise((resolve, reject) => {
txPromise.on('transactionHash', (txHash) => {
resolve(txHash)
})
txPromise.catch((error) => {
reject(error)
})
})
}
async verify(hash) {
try {
let blockNumber = await this.contract.methods.getBlockNumber(hash).call()
console.log(`${hash}: blockNmb (${blockNumber})`)
return {
verified: blockNumber > 0,
blockNumber: blockNumber
}
} catch (e) {
console.error(e)
throw e
}
}
}
export default SimpleStamper
\ No newline at end of file
export TX_HASH=$(curl -s -u pepo:pepo http://localhost:3000/stamp | jq -r .hash) && echo $TX_HASH
curl -s -u pepo:pepo http://localhost:3000/status/$TX_HASH | jq
.DS_Store
This diff is collapsed.
This diff is collapsed.
pragma solidity >=0.4.21 <0.6.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
// 20190401 Robert Martin-Legene <robert@nic.ar>
// Stamper
// vim:filetype=javascript
pragma solidity ^0.5.2;
contract Stamper {
struct Stamp {
bytes32 object;
address stamper;
uint256 blockNo;
}
Stamp[] stampList;
// Mapping de objects stampeados a la stampList
mapping ( bytes32 => uint256[] ) hashObjects;
// Mapping de cuentas que stampean (stampers) a la stampList
mapping ( address => uint256[] ) hashStampers;
// Evento que se dispara al agregar un stamp
event Stamped(
address indexed from,
bytes32 indexed object,
uint256 blockNo
);
address owner;
constructor() public {
owner = msg.sender;
// No queremos que haya stamps asociados a la posicion 0 (== false)
// entonces guardamos ahi informacion de quien creo el SC y en que bloque
stampList.push(Stamp(0, msg.sender, block.number));
}
// Stampear una lista de objects (hashes)
function put( bytes32[] memory objectList ) public {
uint256 i = 0;
uint256 max = objectList.length;
while ( i<max )
{
bytes32 h = objectList[i];
// stampList.push devuelve la longitud, restamos 1 para usar como indice
uint256 idx = stampList.push(Stamp(h, msg.sender, block.number)) - 1;
hashObjects[h].push(idx);
hashStampers[msg.sender].push(idx);
emit Stamped(msg.sender, h, block.number);
i++;
}
}
// devuelve un stamp completo (object, stamper, blockno) de la lista
function getStamplistPos( uint256 pos ) public view returns ( bytes32, address, uint256 )
{
return (stampList[pos].object, stampList[pos].stamper, stampList[pos].blockNo );
}
// devuelve la cantidad de stamps que hay de este object
function getObjectCount( bytes32 object ) public view returns (uint256)
{
return hashObjects[object].length;
}
// devuelve la ubicacion en la stampList de un stamp especifico de este object
function getObjectPos( bytes32 object, uint256 pos ) public view returns (uint256)
{
return hashObjects[object][pos];
}
// devuelve el nro de bloque en el que stamper registro object. Si no fue stampeado devuelve 0
function getBlockNo( bytes32 object, address stamper ) public view returns (uint256)
{
uint length = hashObjects[object].length;
for (uint i = 0; i < length; i++) {
Stamp memory current = stampList[hashObjects[object][i]];
if (current.stamper == stamper) {
return current.blockNo;
}
}
return 0;
}
// devuelve la cantidad de stamps que realizo este stamper
function getStamperCount( address stamper ) public view returns (uint256)
{
return hashStampers[stamper].length;
}
// devuelve la ubicacion en la sstamplist de un Stamp especifico de este stamper
function getStamperPos( address stamper, uint256 pos ) public view returns (uint256)
{
return hashStampers[stamper][pos];
}
}
var contract = artifacts.require("SimpleStamper");
//var contract_address = '0xD32F2B055B1D9a45C99C4FdCc158Bd477999D93A';
let value = web3.utils.asciiToHex('sdfjsdd')
module.exports = function() {
async function insert() {
let instance = await contract.deployed()
let res = await instance.stamp(value);
console.log(res)
console.log('---------')
}
async function check() {
let instance = await contract.deployed()
let res = await instance.stamped(value);
console.log(res)
}
insert()
check()
}
\ No newline at end of file
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};
const Stamper = artifacts.require("Stamper");
module.exports = function(deployer) {
deployer.deploy(Stamper);
};
/**
* Use this file to configure your truffle project. It's seeded with some
* common settings for different networks and features like migrations,
* compilation and testing. Uncomment the ones you need or modify
* them to suit your project as necessary.
*
* More information about configuration can be found at:
*
* truffleframework.com/docs/advanced/configuration
*
* To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider)
* to sign your transactions before they're sent to a remote public node. Infura API
* keys are available for free at: infura.io/register
*
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
* phrase from a file you've .gitignored so it doesn't accidentally become public.
*
*/
// const HDWalletProvider = require('truffle-hdwallet-provider');
// const infuraKey = "fj4jll3k.....";
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();
module.exports = {
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/
networks: {
// Useful for testing. The `development` name is special - truffle uses it by default
// if it's defined here and no other network is specified at the command line.
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
// tab if you use this network and you must also set the `host`, `port` and `network_id`
// options below to some value.
//
// development: {
// host: "127.0.0.1", // Localhost (default: none)
// port: 8545, // Standard Ethereum port (default: none)
// network_id: "*", // Any network (default: none)
// },
// Another network with more advanced options...
// advanced: {
// port: 8777, // Custom port
// network_id: 1342, // Custom network
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
// from: <address>, // Account to send txs from (default: accounts[0])
// websockets: true // Enable EventEmitter interface for web3 (default: false)
// },
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
// ropsten: {
// provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/${infuraKey}`),
// network_id: 3, // Ropsten's id
// gas: 5500000, // Ropsten has a lower block limit than mainnet
// confirmations: 2, // # of confs to wait between deployments. (default: 0)
// timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
// skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
// },
// Useful for private networks
// private: {
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
// network_id: 2111, // This network is yours, in the cloud.
// production: true // Treats this network as if it was a public net. (default: false)
// }
},
// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},
// Configure your compilers
compilers: {
solc: {
version: "0.5.2", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
// enabled: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment