Commit 512152c2 authored by Zsolt from VM's avatar Zsolt from VM
Browse files

refactored version

parent 6df2193f
Pipeline #1605 canceled with stages
# Fabric Example GainSierra
Structure of this repo:
- folder `chaincode` contains chaincode (i.e. smart contract logic)
- folder `gainsierra` contains the client code to use the smart contract
- folder `gainsierra/typescript` contains the client code in typescript
- folder `gainsierra/app` contains the web app in flask to use the smart contract
- folder `first-network` contains the basic network configuration for our application (taken from the fabric-samples repo)
To start the application:
- run `./teardownAll.sh` (if you already used it in the past)
- run `./startFabric.sh` (this may take a few minutes and will compile the chaincode and start the network and also to compile the client code and bootstrap a local wallet)
- run `./runWebApp.sh` (this will launch the web server)
- run `./launchBrowser` (opens a browser with a tab for Admin and three Users)
You can then go to `127.0.0.1:5000/index?user=N` with N = 1,2 or 3 and `127.0.0.1:5000/admin` in your browser.
To compile the client code again you can run `npm run build` from `gainsierra/typescript`.
When creating a new app/contract you can (and possibly should) change the "my_app" string by:
- editing {.,chaincode}/my_app/typescript/package.json and replacing the "name" field
- changing CONTRACT_NAME in my_app/startFabric.sh
- changing CONTRACT_NAME in ./cleanup.sh
- renaming folders {.,chaincode}/my_app;
- changing class/type names wherever you find it appropriate
#
# SPDX-License-Identifier: Apache-2.0
#
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
#
# SPDX-License-Identifier: Apache-2.0
#
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# Compiled TypeScript files
dist
{
"name": "gainsierra-chaincode",
"version": "1.0.0",
"description": "Gainsierra chaincode implemented in TypeScript",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"pretest": "npm run lint",
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
"start": "fabric-chaincode-node start",
"build": "tsc",
"build:watch": "tsc -w",
"prepublishOnly": "npm run build"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "unstable",
"fabric-shim": "unstable"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.10",
"@types/sinon": "^5.0.7",
"@types/sinon-chai": "^3.2.1",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"nyc": "^14.1.1",
"sinon": "^7.1.1",
"sinon-chai": "^3.3.0",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"typescript": "^3.1.6"
},
"nyc": {
"extension": [
".ts",
".tsx"
],
"exclude": [
"coverage/**",
"dist/**"
],
"reporter": [
"text-summary",
"html"
],
"all": true,
"check-coverage": true,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}
}
import { Context, Contract } from 'fabric-contract-api';
export abstract class BasicContract extends Contract {
public static makeKey(a:string, b:string): string {
return a + b;
}
protected async query<T>(ctx: Context, whatis: string, id: string): Promise<T> {
const k = BasicContract.makeKey(whatis, id);
return this.queryByFullKey(ctx, k);
}
protected async queryByFullKey<T>(ctx: Context, k: string): Promise<T> {
const valAsBytes = await ctx.stub.getState(k);
if (!valAsBytes || valAsBytes.length === 0) {
throw new Error(`${k} does not exist`);
}
console.log("[queryByFullKey] valAsBytes:");
console.log(valAsBytes);
return JSON.parse(valAsBytes.toString());
}
protected async queryFullRange(ctx: Context, whatis: string): Promise<string> {
const startKey = BasicContract.makeKey(whatis,'0');
const endKey = BasicContract.makeKey(whatis,'999');
const iterator = await ctx.stub.getStateByRange(startKey, endKey);
const allResults = [];
console.log("About to start scanning data for all queries")
while (true) {
const res = await iterator.next();
if (res.value && res.value.value.toString()) {
console.log(res.value.value.toString());
const Key = res.value.key;
let Record;
try {
Record = JSON.parse(res.value.value.toString());
} catch (err) {
console.log(err);
Record = res.value.value.toString();
}
console.log(Record.toString());
allResults.push({ Key, Record});
}
if (res.done) {
console.log('end of data');
await iterator.close();
console.info(allResults);
return JSON.stringify(allResults);
}
}
}
protected async create<T>(ctx: Context, whatis: string, id: string, val: T) {
await ctx.stub.putState(
BasicContract.makeKey(whatis, id),
Buffer.from(JSON.stringify(val))
);
}
protected async replaceByFullKey<T>(ctx: Context, k: string, val: T) {
await ctx.stub.putState(k, Buffer.from(JSON.stringify(val)));
}
protected async replace<T>(ctx: Context, whatis: string, id: string, val: T) {
await this.create(ctx, whatis, id, val);
}
}
\ No newline at end of file
export enum Pos {
N,
S,
E,
W
}
export interface Tiles {
docType?: string;
map: Map<Pos, boolean>
}
export interface Bet {
docType?: string;
validFor: number;
pos: Pos;
expectedVal: boolean;
//amount: number, // we implicitly assume the bet amount is always a single coin
user: string
}
/*
* SPDX-License-Identifier: Apache-2.0
*/
import { Context } from 'fabric-contract-api';
import {BasicContract } from './basiccontract';
import { Bet, Tiles } from './datatypes';
function isWinning(bet: Bet, tiles: Tiles)
{
const actualVal = tiles.map[bet.pos];
return actualVal == bet.expectedVal;
}
const APPTIME = "_app_time"
export class GainSierra extends BasicContract {
public async initLedger(ctx: Context) {
console.info('============= START : Initialize Ledger ===========');
await this.reset(ctx);
console.info('============= END : Initialize Ledger ===========');
}
public async reset(ctx:Context) {
// starting tiles
const tilesStr = JSON.stringify(
{"map":{"N":false,"S":false,"E":false,"W":false}} );
await this.updateTiles(ctx, tilesStr);
const maxUsers = 3; // change as needed
// update accounts
for (var i = 1; i <= maxUsers; i++) {
await this.addSimple(ctx, BasicContract.makeKey("balance", i.toString()), "0");
}
//update time to 0
await this.resetTime(ctx)
}
public async clearBalance(ctx:Context) {
const maxUsers = 3; // change as needed
// update accounts
for (var i = 1; i <= maxUsers; i++) {
await this.addSimple(ctx, BasicContract.makeKey("balance", i.toString()), "0");
}
}
public async querySimple(ctx: Context, k: string): Promise<string>
{
const valAsBytes = await ctx.stub.getState(k);
if (!valAsBytes || valAsBytes.length === 0) {
throw new Error(`${k} does not exist`);
}
console.log(valAsBytes.toString());
return JSON.parse(valAsBytes.toString());
}
public async addSimple(ctx: Context, k: string, v:string)
{
await ctx.stub.putState(k, Buffer.from(JSON.stringify(v)));
}
public async updateTiles(ctx:Context, tiles: string) {
await this.replace(ctx, 'tiles', '0', tiles);
}
public async queryTiles(ctx:Context): Promise<string> {
return await this.query(ctx, 'tiles', '0');
}
public async queryAllBets(ctx: Context):Promise<string> {
return await this.queryFullRange(ctx, 'bet');
}
public async queryBet(ctx: Context, id: string):Promise<Bet> {
return await this.query(ctx, 'bet', id);
}
public async payAllBets(ctx:Context) {
/*
Logic of this simple payment method:
- Gather all the money from bets (call this resulting value V)
- See how many bets are won (call this number m)
- give each of the m winners V/m.
NB: for this to be profitable of course the chance of everybody
winning should be sort of low.
*/
const cur_time = await this.getTime(ctx);
const betsKV_str = await this.queryAllBets(ctx);
console.log(betsKV_str);
const betsKV = JSON.parse(betsKV_str);
const betsKeys = betsKV.map(item => item["Key"]);
const all_bets = betsKV.map(item => item["Record"]);
const bets = all_bets.filter(b => b.validFor == cur_time );
console.log("valid bets:");
console.log(bets);
// V is the total value of all bets
const V = bets.length;
// disable all bets
await this.clearAllBets(ctx);
const tilesStr = await this.queryTiles(ctx);
const tiles:Tiles = JSON.parse(tilesStr);
console.log("tiles:");
console.log(tiles);
// find winners
const winningBets = bets.filter(b => isWinning(b, tiles) );
console.log("winningBets:");
console.log(winningBets);
const m = winningBets.length;
const win = V/m;
console.log("Win:");
console.log(win);
for (let w of winningBets) {
await this.updateBalance(ctx, w.user, win);
}
await this.incrementTime(ctx);
}
public async queryBalance(ctx: Context, user: string) {
return await this.query(ctx, 'balance', user);
}
public async updateBalance(ctx: Context, user: string, delta: number) {
const curBalance = await this.query(ctx, 'balance', user);
console.log(`curBalance is ${curBalance}`);
const newBalance = Number(curBalance) + Number(delta);
console.log(`newBalance is ${newBalance}`);
await this.replace(ctx, 'balance', user, newBalance);
}
public async addBet(ctx:Context, id: string, bet: string) { // bet: Bet
const betObj = JSON.parse(bet);
betObj.validFor = await this.getTime(ctx);
await this.create(ctx, 'bet', id, betObj);
}
public async clearAllBets(ctx: Context) {
const betsKV_str = await this.queryAllBets(ctx);
const betsKV = JSON.parse(betsKV_str);
console.log(`Output of bets KV: ${betsKV}`);
const betsKeys = betsKV.map(item => item["Key"]);
for (const k of betsKeys) {
const valBytes:string = await this.queryByFullKey(ctx, k);
console.log("queryByFullKey done");
console.log(valBytes);
const val = valBytes;
console.log("val:");
console.log(val);
val["validNow"] = false;
await ctx.stub.putState(k, Buffer.from(JSON.stringify(val)));
}
}
public async resetTime(ctx: Context) {
await ctx.stub.putState(APPTIME, Buffer.from(JSON.stringify(0)));
}
public async getTime(ctx: Context) : Promise<number> {
const valAsBytes = await ctx.stub.getState(APPTIME);
return JSON.parse(valAsBytes.toString());
}
public async incrementTime(ctx: Context) {
const curTime = await this.getTime(ctx);
const nextTime = curTime+1;
await ctx.stub.putState(APPTIME, Buffer.from(JSON.stringify(nextTime)))
}
}
/*
* SPDX-License-Identifier: Apache-2.0
*/
import { GainSierra } from './gainsierra';
export { GainSierra } from './gainsierra';
export const contracts: any[] = [ GainSierra ];
export {Bet, Tiles, Pos} from './datatypes';
{
"compilerOptions": {
"outDir": "dist",
"target": "es2017",
"moduleResolution": "node",
"module": "commonjs",
"declaration": true,
"sourceMap": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./src/**/*.spec.ts"
]
}
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"indent": [true, "spaces", 4],
"linebreak-style": [true, "LF"],
"quotemark": [true, "single"],
"semicolon": [true, "always"],
"no-console": false,
"curly": true,
"triple-equals": true,
"no-string-throw": true,
"no-var-keyword": true,
"no-trailing-whitespace": true,
"object-literal-key-quotes": [true, "as-needed"]
},
"rulesDirectory": []
}
COMPOSE_PROJECT_NAME=net
IMAGE_TAG=latest
SYS_CHANNEL=byfn-sys-channel
/channel-artifacts/*.tx
/channel-artifacts/*.block
/crypto-config/*
/docker-compose-e2e.yaml
/ledgers
/ledgers-backup
/channel-artifacts/*.json
/org3-artifacts/crypto-config/*
\ No newline at end of file
## Build Your First Network (BYFN)
The directions for using this are documented in the Hyperledger Fabric
["Build Your First Network"](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html) tutorial.
*NOTE:* After navigating to the documentation, choose the documentation version that matches your version of Fabric
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
version: '2'
services:
orderer.example.com:
container_name: orderer.example.com
extends:
file: peer-base.yaml
service: orderer-base
volumes:
- ../channel-artifacts/genesis.block:/var/hyperledger/orderer/orderer.genesis.block
- ../crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp:/var/hyperledger/orderer/msp
- ../crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/:/var/hyperledger/orderer/tls
- orderer.example.com:/var/hyperledger/production/orderer
ports:
- 7050:7050
peer0.org1.example.com:
container_name: peer0.org1.example.com
extends:
file: peer-base.yaml
service: peer-base
environment:
- CORE_PEER_ID=peer0.org1.example.com
- CORE_PEER_ADDRESS=peer0.org1.example.com:7051
- CORE_PEER_LISTENADDRESS=0.0.0.0:7051
- CORE_PEER_CHAINCODEADDRESS=peer0.org1.example.com:7052
- CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:7052
- CORE_PEER_GOSSIP_BOOTSTRAP=peer1.org1.example.com:8051
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051
- CORE_PEER_LOCALMSPID=Org1MSP
volumes:
- /var/run/:/host/var/run/
- ../crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp
- ../crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls:/etc/hyperledger/fabric/tls
- peer0.org1.example.com:/var/hyperledger/production
ports:
- 7051:7051
peer1.org1.example.com:
container_name: peer1.org1.example.com
extends:
file: peer-base.yaml
service: peer-base
environment: