From 41afaa0e014cae295f458aa217fb1eae3f92c2e4 Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Wed, 29 Sep 2021 18:47:10 +0200 Subject: [PATCH] first steps for send coins --- .gitmodules | 3 ++ backend/package.json | 3 ++ .../graphql/resolvers/TransactionResolver.ts | 17 ++++++ .../graphql/resolvers/createTransaction.ts | 6 +++ backend/src/graphql/resolvers/getPublicKey.ts | 32 +++++++++++ backend/src/graphql/resolvers/sendCoins.ts | 53 +++++++++++++++++++ backend/src/proto | 1 + backend/src/util/validate.ts | 21 ++++++++ 8 files changed, 136 insertions(+) create mode 100644 backend/src/graphql/resolvers/createTransaction.ts create mode 100644 backend/src/graphql/resolvers/getPublicKey.ts create mode 100644 backend/src/graphql/resolvers/sendCoins.ts create mode 160000 backend/src/proto create mode 100644 backend/src/util/validate.ts diff --git a/.gitmodules b/.gitmodules index 22790ccc7..5026a5b8a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "login_server/dependencies/protobuf"] path = login_server/dependencies/protobuf url = https://github.com/protocolbuffers/protobuf.git +[submodule "backend/src/proto"] + path = backend/src/proto + url = git@github.com:gradido/gradido_protocol.git diff --git a/backend/package.json b/backend/package.json index bb7848013..9809b8be6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,6 +15,7 @@ "lint": "eslint . --ext .js,.ts" }, "dependencies": { + "@apollo/protobufjs": "^1.2.2", "apollo-server-express": "^2.25.2", "axios": "^0.21.1", "class-validator": "^0.13.1", @@ -23,6 +24,7 @@ "express": "^4.17.1", "graphql": "^15.5.1", "jsonwebtoken": "^8.5.1", + "libsodium-wrappers": "^0.7.9", "mysql2": "^2.3.0", "reflect-metadata": "^0.1.13", "type-graphql": "^1.1.1", @@ -31,6 +33,7 @@ "devDependencies": { "@types/express": "^4.17.12", "@types/jsonwebtoken": "^8.5.2", + "@types/libsodium-wrappers": "^0.7.9", "@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/parser": "^4.28.0", "eslint": "^7.29.0", diff --git a/backend/src/graphql/resolvers/TransactionResolver.ts b/backend/src/graphql/resolvers/TransactionResolver.ts index 3762cccee..bacdc4628 100644 --- a/backend/src/graphql/resolvers/TransactionResolver.ts +++ b/backend/src/graphql/resolvers/TransactionResolver.ts @@ -11,6 +11,8 @@ import { Balance as dbBalance } from '../../typeorm/entity/Balance' import listTransactions from './listTransactions' import { roundFloorFrom4 } from '../../util/round' import { calculateDecay } from '../../util/decay' +import sendCoins from './sendCoins' +import getPublicKey from './getPublicKey' @Resolver() export class TransactionResolver { @@ -69,6 +71,21 @@ export class TransactionResolver { if (!result.success) { throw new Error(result.data) } + + const recipiantPublicKey = await getPublicKey(email, context.sessionId) + if(!recipiantPublicKey) { + throw new Error('recipiant not known') + } + + // get public key for current logged in user + const loginResult = await apiGet(CONFIG.LOGIN_API_URL + 'login?session_id=' + context.sessionId) + if (!loginResult.success) throw new Error(result.data) + + // load user and balance + const userEntity = await dbUser.findByPubkeyHex(result.data.user.public_hex) + + const transaction = sendCoins(userEntity, recipiantPublicKey, amount, memo) + return 'success' } } diff --git a/backend/src/graphql/resolvers/createTransaction.ts b/backend/src/graphql/resolvers/createTransaction.ts new file mode 100644 index 000000000..66256cff6 --- /dev/null +++ b/backend/src/graphql/resolvers/createTransaction.ts @@ -0,0 +1,6 @@ + + +export default function createTransaction() +{ + +} \ No newline at end of file diff --git a/backend/src/graphql/resolvers/getPublicKey.ts b/backend/src/graphql/resolvers/getPublicKey.ts new file mode 100644 index 000000000..f60616317 --- /dev/null +++ b/backend/src/graphql/resolvers/getPublicKey.ts @@ -0,0 +1,32 @@ +import { apiPost } from '../../apis/HttpRequest' +import CONFIG from '../../config' +import { isHexPublicKey } from '../../util/validate' + +// target can be email, username or public_key +// groupId if not null and another community, try to get public key from there +export default async function getPublicKey(target: string, sessionId:number, groupId: number = 0): Promise +{ + // if it is already a public key, return it + if(isHexPublicKey(target)) { + return target + } + + // assume it is a email address if it's contain a @ + if(/@/i.test(target)) { + const result = await apiPost(CONFIG.LOGIN_API_URL + 'getUserInfos', { + session_id: sessionId, + email: target, + ask: ['user.pubkeyhex'] + }) + if (result.success) { + return result.data.userData.pubkeyhex + } + } + + // if username is used add code here + + // if we have multiple communities add code here + + return undefined + +} \ No newline at end of file diff --git a/backend/src/graphql/resolvers/sendCoins.ts b/backend/src/graphql/resolvers/sendCoins.ts new file mode 100644 index 000000000..ccb221005 --- /dev/null +++ b/backend/src/graphql/resolvers/sendCoins.ts @@ -0,0 +1,53 @@ +import protobuf from '@apollo/protobufjs' +import { from_hex } from 'libsodium-wrappers' +import { isHexPublicKey, hasUserAmount } from '../../util/validate' +import { User as dbUser } from '../../typeorm/entity/User' + + +/** + * + * @param senderPublicKey as hex string + * @param recipiantPublicKey as hex string + * @param amount as float + * @param memo + * @param groupId + */ +export default async function sendCoins( + senderUser: dbUser, + recipiantPublicKey:string, + amount:number, + memo:string, + groupId:number = 0) +{ + if(senderUser.pubkey.length != 32) { + throw new Error('invalid sender public key') + } + if(!isHexPublicKey(recipiantPublicKey)) { + throw new Error('invalid recipiant public key') + } + if(amount <= 0) { + throw new Error('invalid amount') + } + if(!hasUserAmount(senderUser, amount)) { + throw new Error('user hasn\'t enough GDD') + } + const protoRoot = await protobuf.load('../../proto/gradido/GradidoTransfer.proto') + + const GradidoTransfer = protoRoot.lookupType('proto.gradido.GradidoTransfer') + const TransferAmount = protoRoot.lookupType('proto.gradido.TransferAmount') + + const transferAmount = TransferAmount.create({ + pubkey: senderUser.pubkey, + amount: amount / 10000 + }) + + // no group id is given so we assume it is a local transfer + if(!groupId) { + const LocalTransfer = protoRoot.lookupType('proto.gradido.LocalTransfer') + const localTransfer = LocalTransfer.create({ + sender: transferAmount, + recipiant: from_hex(recipiantPublicKey) + }) + return GradidoTransfer.create({local: localTransfer}) + } +} \ No newline at end of file diff --git a/backend/src/proto b/backend/src/proto new file mode 160000 index 000000000..cc9acbb21 --- /dev/null +++ b/backend/src/proto @@ -0,0 +1 @@ +Subproject commit cc9acbb212201a560d86fc87a47665497f11f27d diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts new file mode 100644 index 000000000..d95f64328 --- /dev/null +++ b/backend/src/util/validate.ts @@ -0,0 +1,21 @@ +import { User as dbUser } from '../typeorm/entity/User' +import { Balance as dbBalance } from '../typeorm/entity/Balance' +import { getRepository } from 'typeorm' +import { calculateDecay } from './decay' +import { UserResolver } from '../graphql/resolvers' + +function isHexPublicKey(publicKey:string): boolean { + return /^[0-9A-Fa-f]{64}$/i.test(publicKey) +} + +async function hasUserAmount(user:dbUser, amount:number): Promise { + if(amount < 0) return false + const balanceRepository = getRepository(dbBalance) + const balance = await balanceRepository.findOne({ userId: user.id }) + if(!balance) return false + + const decay = await calculateDecay(balance.amount, balance.recordDate, new Date()) + return decay > amount +} + +export { isHexPublicKey, hasUserAmount } \ No newline at end of file