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
No related merge requests found
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