UNPKG

immudb-node

Version:

Node.js SDK for immudb written in TypeScript

1,642 lines (1,352 loc) 54.2 kB
import * as dotenv from 'dotenv'; import { Metadata } from 'grpc'; import * as grpc from '@grpc/grpc-js'; import * as empty from 'google-protobuf/google/protobuf/empty_pb'; dotenv.config(); import * as schemaTypes from './proto/schema_pb'; import * as services from './proto/schema_grpc_pb'; import * as util from './util'; import { proofTx, txFrom } from './tx' import State from './state'; import * as interfaces from './interfaces'; import { verifyInclusion, verifyDualProof } from './verification' import { CLIENT_INIT_PREFIX, DEFAULT_DATABASE, DEFAULT_ROOTPATH } from './consts' import { getSQLValue } from './common'; import Parameters, { SQLValue, SQLColumnDescription, SQLTableDescription, SQLRowDescription, } from '../types/parameters' class ImmudbClient { public state: State; public client: services.ImmuServiceClient; private static instance: ImmudbClient; private _auth: any; private _token: any; private _metadata: Metadata; private _activeDatabase: any; private _serverUUID: any; constructor({ host = (process.env.IMMUDB_HOST as string) || '127.0.0.1', port = (process.env.IMMUDB_PORT as string) || '3322', certs, rootPath = DEFAULT_ROOTPATH, }: interfaces.Config) { // init insecure grpc auth this._auth = grpc.credentials.createInsecure(); // init secure grpc auth if (certs !== undefined) { this._auth = grpc.credentials.createSsl(Buffer.from(JSON.stringify(certs))); } // initialize client from service this.client = new services.ImmuServiceClient(`${host}:${port}`, this._auth); // init empty grpc metadata this._metadata = new grpc.Metadata(); // init state this.state = new State({ client: this.client, rootPath }) } public static async getInstance(config: interfaces.Config): Promise<ImmudbClient> { const { user = process.env.IMMUDB_USER as string, password = process.env.IMMUDB_PWD as string, database: databasename = process.env.IMMUDB_DEFAULT_DB as string, autoDatabase = true, autoLogin = true } = config try { if (!ImmudbClient.instance) { console.log( `${CLIENT_INIT_PREFIX} creating new ImmudbClient instance with config`, '\n', `${util.maskConfig(config)}` ); ImmudbClient.instance = new ImmudbClient(config); console.log(`${CLIENT_INIT_PREFIX} init new instance`); await ImmudbClient.instance.initClient( user, password, databasename, autoLogin, autoDatabase ); } else { console.log(`${CLIENT_INIT_PREFIX} using already available ImmudbClient instance`); } return new Promise((resolve) => resolve(ImmudbClient.instance)); } catch (err) { await ImmudbClient.instance.shutdown(); return new Promise((_, reject) => reject(err)); } } public async initClient( user?: string, password?: string, databasename?: string, autoLogin = true, autoDatabase = true ) { // by default automatically manage user login with dotenv variables if (autoLogin) { // login if (user && password) { const resLogin = await this.login({ user, password }); const token = resLogin ? util.maskString(resLogin.token) : ''; console.log('ImmudbClient: login', token); } } else { console.log( `${CLIENT_INIT_PREFIX} skipped automatic init login (manual client login is required)` ); if (autoDatabase) { console.warn( `${CLIENT_INIT_PREFIX} it's not possible to 'autoDatabase' if 'autoLogin' is set to false`, '\n', `(the following ops will fallback to use '${DEFAULT_DATABASE}' database)` ); } } // by default automatically manage database ops with dotenv variables if (autoLogin && autoDatabase) { // get current database list const resList = await this.listDatabases(); if (resList && resList.databasesList.some(db => db.databasename === databasename)) { // useDatabase database specified if it // already exists await this.useDatabase({ databasename: DEFAULT_DATABASE }); console.log(`${CLIENT_INIT_PREFIX} useDatabase '${DEFAULT_DATABASE}'`); } else if (databasename) { // run createDatabase and useDatabase if databasename // is different than the default one await this.createDatabase({ databasename }); console.log(`${CLIENT_INIT_PREFIX} createDatabase '${databasename}'`); await this.useDatabase({ databasename }); console.log(`${CLIENT_INIT_PREFIX} useDatabase '${databasename}'`); } else { // run createDatabase and useDatabase if default // databasename is missing await this.useDatabase({ databasename: DEFAULT_DATABASE }); console.log(`${CLIENT_INIT_PREFIX} useDatabase '${databasename}'`); } } else { console.log( `${CLIENT_INIT_PREFIX} skipped automatic init database`, '\n', `(manual client database ops are required, '${DEFAULT_DATABASE}' database will be used otherwise)` ); } // fetch health status await this.health(); } async shutdown() { this.state.commit(); this.logout(); process.exit(0); } async login( params: Parameters.Login ): Promise<schemaTypes.LoginResponse.AsObject | undefined> { try { const { user, password } = params; const req = new schemaTypes.LoginRequest(); req.setUser(util.utf8Encode(user)); req.setPassword(util.utf8Encode(password)); return new Promise((resolve, reject) => this.client.login(req, this._metadata, (err, res) => { if (err) { console.error('Login Error', err); return reject(err); } this._token = res.getToken(); this._metadata.remove('authorization'); this._metadata.add('authorization', `Bearer ${this._token}`); resolve({ token: this._token, warning: util.utf8Decode(res.getWarning()), }); }) ); } catch (err) { console.error('Login Error', err); } } async createDatabase({ databasename }: Parameters.CreateDatabase): Promise<empty.Empty | undefined> { try { const req = new schemaTypes.Database(); req.setDatabasename(databasename); return new Promise((resolve, reject) => this.client.createDatabase(req, this._metadata, (err, res) => { if (err) { console.error('Create database error', err); return reject(err); } return resolve(res); }) ); } catch (err) { console.error('Create database error', err); } } async useDatabase( { databasename }: Parameters.UseDatabase ): Promise<schemaTypes.UseDatabaseReply.AsObject | undefined> { try { const req = new schemaTypes.Database(); req.setDatabasename(databasename); return new Promise((resolve, reject) => this.client.useDatabase(req, this._metadata, async (err, res) => { if (err) { console.error('Use database error', err); return reject(err); } else { const token = res.getToken(); this._metadata.remove('authorization'); this._metadata.add('authorization', `Bearer ${token}`); this._activeDatabase = databasename; resolve(res.toObject()); } }) ); } catch (err) { console.error('Use database error', err); } } async set({ key, value }: Parameters.Set): Promise<schemaTypes.TxMetadata.AsObject | undefined> { try { const req = new schemaTypes.SetRequest(); const kv = new schemaTypes.KeyValue(); kv.setKey(util.utf8Encode(key)); kv.setValue(util.utf8Encode(value)); req.setKvsList([kv]); return new Promise((resolve, reject) => this.client.set(req, this._metadata, async (err, res) => { if (err) { console.error('Set error', err); return reject(err); } else { const resObject = res.toObject() resolve(resObject); } }) ); } catch (err) { console.error('Set error', err); } } async get({ key }: Parameters.Get): Promise<schemaTypes.Entry.AsObject | undefined> { try { const req = new schemaTypes.KeyRequest(); req.setKey(util.utf8Encode(key)); return new Promise((resolve, reject) => this.client.get(req, this._metadata, (err, res) => { if (err) { console.error('Get error', err); reject(err.message); } else { resolve({ tx: res && res.getTx(), key: util.utf8Decode(res && res.getKey()), value: util.utf8Decode(res && res.getValue()), }); } }) ); } catch (err) { console.error(err); } } async listDatabases(): Promise<schemaTypes.DatabaseListResponse.AsObject | undefined> { try { const req = new empty.Empty(); return new Promise((resolve, reject) => this.client.databaseList(req, this._metadata, (err, res) => { if (err) { console.error('List databases error', err); return reject(err); } const dl = res && res.getDatabasesList(); const l: Array<schemaTypes.Database.AsObject> = []; for (let i = 0; dl && i < dl.length; i++) { l.push(dl[i].toObject()); } resolve({ databasesList: l, }); }) ); } catch (err) { console.error(err); } } async changePermission( params: Parameters.ChangePermission ): Promise<empty.Empty | undefined> { try { const { action, permission, username, database } = params const req = new schemaTypes.ChangePermissionRequest(); req.setAction(action); req.setPermission(permission); req.setUsername(username); req.setDatabase(database); return new Promise((resolve, reject) => this.client.changePermission(req, this._metadata, (err, res) => { if (err) { console.error('Change permission error', err); return reject(err); } else { resolve(res); } }) ); } catch (err) { console.error(err); } } async listUsers(): Promise<schemaTypes.UserList.AsObject | undefined> { try { const req = new empty.Empty(); return new Promise((resolve, reject) => this.client.listUsers(req, this._metadata, (err, res) => { if (err) { console.error('List users error', err); reject(err.message); } const ul = res && res.getUsersList(); const l: Array<schemaTypes.User.AsObject> = []; for (let i = 0; ul && i < ul.length; i++) { const u = ul[i]; const pl = u.getPermissionsList(); const p: Array<schemaTypes.Permission.AsObject> = []; for (let j = 0; j < pl.length; j++) { p.push({ database: pl[j].getDatabase(), permission: pl[j].getPermission(), }); } l.push({ user: util.utf8Decode(u.getUser()), permissionsList: p, createdby: u.getCreatedby(), createdat: u.getCreatedat(), active: u.getActive(), }); } resolve({ usersList: l, }); }) ); } catch (err) { console.error(err); } } async createUser(params: Parameters.CreateUser): Promise<empty.Empty | undefined> { try { const req = new schemaTypes.CreateUserRequest(); req.setUser(util.utf8Encode(params && params.user)); req.setPassword(util.utf8Encode(params && params.password)); req.setPermission(params && params.permission); req.setDatabase(params && params.database); return new Promise((resolve, reject) => this.client.createUser(req, this._metadata, (err, res) => { if (err) { console.error('Create user error', err); return reject(err); } resolve(res); }) ); } catch (err) { console.error(err); } } async changePassword( params: Parameters.ChangePassword ): Promise<empty.Empty | undefined> { try { const req = new schemaTypes.ChangePasswordRequest(); req.setUser(util.utf8Encode(params && params.user)); req.setOldpassword(util.utf8Encode(params && params.oldpassword)); req.setNewpassword(util.utf8Encode(params && params.newpassword)); return new Promise((resolve, reject) => this.client.changePassword(req, this._metadata, (err, res) => { if (err) { console.error('Change password error', err); return reject(err); } resolve(res); }) ); } catch (err) { console.error(err); } } async logout(): Promise<empty.Empty | undefined> { try { const req = new empty.Empty(); return new Promise((resolve, reject) => this.client.logout(req, this._metadata, (err, res) => { if (err) { console.error('Logout error', err); reject(err.message); } resolve(res); }) ); } catch (err) { console.error(err); } } async setActiveUser( params: Parameters.SetActiveUser ): Promise<empty.Empty | undefined> { try { const req = new schemaTypes.SetActiveUserRequest(); req.setUsername(params && params.username); req.setActive(params && params.active); return new Promise((resolve, reject) => this.client.setActiveUser(req, this._metadata, (err, res) => { if (err) { console.error('Set active user error', err); return reject(err); } resolve(res); }) ); } catch (err) { console.error(err); } } async health(): Promise<schemaTypes.HealthResponse.AsObject | undefined> { try { const req = new empty.Empty(); return new Promise((resolve, reject) => { const call = this.client.health(req, this._metadata, (err, res) => { if (err) { console.error('Health error', err); return reject(err); } resolve({ status: res.getStatus(), version: res.getVersion(), }); }) call.on('_metadata', meta => { this._serverUUID = meta.get('immudb-uuid')[0] }) }); } catch (err) { console.error(err); } } async count( { prefix }: Parameters.Count ): Promise<schemaTypes.EntryCount.AsObject | undefined> { try { const req = new schemaTypes.KeyPrefix(); req.setPrefix(util.utf8Encode(prefix)); return new Promise((resolve, reject) => this.client.count(req, this._metadata, (err, res) => { if (err) { console.error('Count error', err); return reject(err); } resolve(res.toObject()); }) ); } catch (err) { console.error(err); } } async countAll(): Promise<schemaTypes.EntryCount.AsObject | undefined> { try { const req = new empty.Empty(); return new Promise((resolve, reject) => this.client.countAll(req, this._metadata, (err, res) => { if (err) { console.error('Count all error', err); return reject(err); } resolve(res.toObject()); }) ); } catch (err) { console.error(err); } } async scan( { seekkey, prefix, desc, limit, sincetx, nowait }: Parameters.Scan = {} ): Promise<schemaTypes.Entries.AsObject | undefined> { try { const req = new schemaTypes.ScanRequest(); if (seekkey !== undefined) { req.setSeekkey(util.utf8Encode(seekkey)); } if (prefix !== undefined) { req.setPrefix(util.utf8Encode(prefix)); } if (desc !== undefined) { req.setDesc(desc); } if (limit !== undefined) { req.setLimit(limit); } if (sincetx !== undefined) { req.setSincetx(sincetx); } if (nowait !== undefined) { req.setNowait(nowait); } return new Promise((resolve, reject) => this.client.scan(req, this._metadata, (err, res) => { if (err) { console.error('Scan error', err); return reject(err); } const result: Array<schemaTypes.Entry.AsObject> = []; const il = res && res.getEntriesList(); for (let i = 0; il && i < il.length; i++) { const item = il[i]; result.push({ tx: item.getTx(), key: util.utf8Decode(item.getKey()), value: util.utf8Decode(item.getValue()), }); } resolve({ entriesList: result, }); }) ); } catch (err) { console.error(err); } } async history( { key, offset, limit, desc, sincetx }: Parameters.History ): Promise<schemaTypes.Entries.AsObject | undefined> { try { const req = new schemaTypes.HistoryRequest(); req.setKey(util.utf8Encode(key)); offset && req.setOffset(offset); limit && req.setLimit(limit); desc && req.setDesc(desc); sincetx && req.setSincetx(sincetx); return new Promise((resolve, reject) => this.client.history(req, this._metadata, (err, res) => { if (err) { console.error('History error', err); return reject(err); } const entriesList = res.getEntriesList(); const result = entriesList.reduce( (entries, entry) => entries.concat({ tx: entry.getTx(), key: util.utf8Decode(entry.getKey()), value: util.utf8Decode(entry.getValue()), }), [] as Array<schemaTypes.Entry.AsObject> ) resolve({ entriesList: result }) }) ); } catch (err) { console.error(err); } } async zScan( { set, seekkey, seekscore, seekattx, inclusiveseek, limit, desc, sincetx, nowait, minscore, maxscore }: Parameters.ZScan ): Promise<schemaTypes.ZEntries.AsObject | undefined> { try { const req = new schemaTypes.ZScanRequest(); req.setSet(util.utf8Encode(set)); seekkey && req.setSeekkey(util.utf8Encode(seekkey)); seekscore && req.setSeekscore(seekscore); seekattx && req.setSeekattx(seekattx); inclusiveseek && req.setInclusiveseek(inclusiveseek); limit && req.setLimit(limit); desc && req.setDesc(desc); sincetx && req.setSincetx(sincetx); nowait && req.setNowait(nowait); if (minscore) { const minScore = new schemaTypes.Score() minScore.setScore(minscore.score) req.setMinscore(minScore) } if (maxscore) { const maxScore = new schemaTypes.Score() maxScore.setScore(maxscore.score) req.setMaxscore(maxScore) } return new Promise((resolve, reject) => this.client.zScan(req, this._metadata, (err, res) => { if (err) { console.error('zScan error', err); return reject(err); } const entriesList = res.getEntriesList(); const result = entriesList.reduce( (entries, entry) => entries.concat({ set: util.utf8Decode(entry.getSet()), key: util.utf8Decode(entry.getKey()), score: entry.getScore(), attx: entry.getAttx() }), [] as Array<schemaTypes.ZEntry.AsObject> ) resolve({ entriesList: result }) }) ); } catch (err) { console.error(err); } } async currentState(): Promise<schemaTypes.ImmutableState.AsObject | undefined> { try { const req = new empty.Empty(); return new Promise((resolve, reject) => this.client.currentState(req, this._metadata, (err, res) => { if (err) { reject(err); } else { const signature = res.getSignature(); const state: schemaTypes.ImmutableState.AsObject = { db: res.getDb(), txid: res.getTxid(), txhash: res.getTxhash_asU8(), signature: signature?.toObject() } this.state.set({ databaseName: this._activeDatabase, serverName: this._serverUUID }, state); resolve({ db: this._activeDatabase, txid: res.getTxid(), txhash: res.getTxhash(), signature: { signature: util.utf8Encode(signature && signature.getSignature()), publickey: util.utf8Encode(signature && signature.getPublickey()) }, }); } }) ); } catch (err) { console.error(err); } } async zAdd( params: Parameters.ZAdd ): Promise<schemaTypes.TxMetadata.AsObject | undefined> { const reqParams = Object.assign({}, params, { attx: 0 }) return this.zAddAt(reqParams) } async zAddAt({ set, score = 0, key, attx = 0 }: Parameters.ZAddAt): Promise<schemaTypes.TxMetadata.AsObject | undefined> { try { const req = new schemaTypes.ZAddRequest(); req.setSet(util.utf8Encode(set)); req.setScore(score); req.setKey(util.utf8Encode(key)); req.setAttx(attx) req.setBoundref(attx > 0) return new Promise((resolve, reject) => this.client.zAdd(req, this._metadata, (err, res) => { if (err) { console.error('zAdd error', err); return reject(err); } resolve({ id: res.getId(), prevalh: util.getAlh(res), ts: res.getTs(), nentries: res.getNentries(), eh: res.getEh(), bltxid: res.getBltxid(), blroot: res.getBlroot(), }); }) ); } catch (err) { console.error(err); } } async verifiedZAdd(params: Parameters.VerifiedZAdd): Promise<schemaTypes.TxMetadata.AsObject | undefined> { const reqParams = Object.assign({}, params, { attx: 0 }) return await this.verifiedZAddAt(reqParams) } async verifiedZAddAt({ set, score, key, attx }: Parameters.VerifiedZAddAt): Promise<schemaTypes.TxMetadata.AsObject | undefined> { try { const state = await this.state.get({ serverName: this._serverUUID, databaseName: this._activeDatabase, metadata: this._metadata }) const req = new schemaTypes.VerifiableZAddRequest() const uintSet = util.utf8Encode(set) const uintKey = util.utf8Encode(key) const zar = new schemaTypes.ZAddRequest() zar.setSet(uintSet) zar.setScore(score) zar.setKey(uintKey) zar.setAttx(attx) zar.setBoundref(attx > 0) req.setZaddrequest(zar) req.setProvesincetx(state.getTxid()) return new Promise((resolve, reject) => this.client.verifiableZAdd(req, this._metadata, (err, res) => { if (err) { console.error('verifiedZAddAt error', err); reject(err) } else { const resTx = res.getTx() if (resTx === undefined) { console.error('Error getting verifiedZAddAt tx') reject() } else { const txMetadata = resTx.getMetadata() if (txMetadata === undefined) { console.error('Error getting verifiedZAddAt txMetadata') reject() } else { const nEntries = txMetadata.getNentries() if (nEntries !== 1) { console.error('nEntries verification failed for verifiedZAddAt') reject() } const tx = txFrom(resTx) const eKv = util.encodeZAdd(uintSet, score, uintKey, attx) const inclusionProof = proofTx(tx, eKv.getKey_asU8()) if (inclusionProof === undefined) { console.error('Error getting inclusionProof for verifiedZAddAt') reject() } else { let verifies = verifyInclusion(inclusionProof, util.digestKeyValue(eKv), tx.htree.root) if (verifies === false) { console.error('Inclusion verification for verifiedZAddAt failed') reject() } const dualProof = res.getDualproof() if (dualProof === undefined) { console.error('Error getting dualProof for verifiedZAddAt') reject() } else { const tTxMetadata = dualProof.getTargettxmetadata() if (tTxMetadata === undefined) { console.error('Error getting tx metadata from dualProof in verifiedZAddAt') reject() } else { if (!util.equalArray(tx.htree.root, tTxMetadata.getEh_asU8())) { console.error('verifiedZAddAt error') } const txid = state.getTxid() const txhash = state.getTxhash_asU8() let sourceId let sourceAlh if (txid === 0) { sourceId = tx.id sourceAlh = tx.alh } else { sourceId = txid sourceAlh = txhash } const targetId = tx.id const targetAlh = util.getAlh(tTxMetadata) verifies = verifyDualProof(dualProof, sourceId, targetId, sourceAlh, targetAlh) if (verifies === false) { console.error('Dual verification for verifiedZAddAt failed') reject() } this.state.set( { serverName: this._serverUUID, databaseName: this._activeDatabase }, { txid: targetId, txhash: targetAlh, signature: res.getSignature()?.toObject(), db: this._activeDatabase } ) resolve(tTxMetadata.toObject()) } } } } } } })) } catch (err) { console.error(err) } } async setReference(params: Parameters.SetReference): Promise<schemaTypes.TxMetadata.AsObject | undefined> { const setReferenceAtParameters = Object.assign({}, params, { attx: 0 }) return await this.setReferenceAt(setReferenceAtParameters) } async setReferenceAt({ key, referencedKey, attx }: Parameters.SetReferenceAt): Promise<schemaTypes.TxMetadata.AsObject | undefined> { try { const req = new schemaTypes.ReferenceRequest(); req.setKey(util.utf8Encode(key)); req.setReferencedkey(util.utf8Encode(referencedKey)); req.setAttx(attx) req.setBoundref(attx > 0) return new Promise((resolve, reject) => this.client.setReference(req, this._metadata, (err, res) => { if (err) { console.error('Reference error', err); return reject(err); } resolve({ id: res.getId(), prevalh: util.getAlh(res), ts: res.getTs(), nentries: res.getNentries(), eh: res.getEh(), bltxid: res.getBltxid(), blroot: res.getBlroot(), }) })) } catch (err) { console.error(err); } } async verifiedSetReference(params: Parameters.VerifiedSetReference): Promise<schemaTypes.TxMetadata.AsObject | undefined> { const vSetReferenceAtParameters = Object.assign({}, params, { attx: 0 }) return await this.verifiedSetReferenceAt(vSetReferenceAtParameters) } async verifiedSetReferenceAt({ key, referencedKey, attx }: Parameters.VerifiedSetReferenceAt): Promise<schemaTypes.TxMetadata.AsObject | undefined> { try { const state = await this.state.get({ serverName: this._serverUUID, databaseName: this._activeDatabase, metadata: this._metadata }) const txid = state.getTxid() const txhash = state.getTxhash_asU8() const uint8Key = util.utf8Encode(key) const uint8RefKey = util.utf8Encode(referencedKey) const req = new schemaTypes.VerifiableReferenceRequest() const refReq = new schemaTypes.ReferenceRequest() refReq.setKey(uint8Key) refReq.setReferencedkey(uint8RefKey) refReq.setAttx(attx) refReq.setBoundref(attx > 0) req.setReferencerequest(refReq) req.setProvesincetx(txid) return new Promise((resolve, reject) => this.client.verifiableSetReference(req, this._metadata, (err, res) => { if (err) { console.error('verifiedSetReferenceAt error', err) reject(err) } else { const resTx = res.getTx() if (resTx === undefined) { console.error('Error getting transaction from verifiedSetReferenceAt response') reject() } else { const resTxMetadata = resTx.getMetadata() if (resTxMetadata === undefined) { console.error('Error getting transaction metadata from verifiedSetReferenceAt response') reject() } else { const nEntries = resTxMetadata.getNentries() if (nEntries !== 1) { console.error('nEntries verification failed for verifiedSetReferenceAt') reject() } const tx = txFrom(resTx) const inclusionProof = proofTx(tx, util.prefixKey(uint8Key)) const eKv = new schemaTypes.KeyValue() eKv.setKey(util.prefixKey(uint8Key)) eKv.setValue(util.encodeReferenceValue(uint8RefKey, attx)) if (inclusionProof === undefined) { console.error('Error getting inclusionProof from verifiedSetReferenceAt response') reject() } else { let verifies = verifyInclusion(inclusionProof, util.digestKeyValue(eKv), tx.htree.root) if (verifies === false) { console.error('Inclusion verification failed for verifiedSetReferenceAt') reject() } const dualProof = res.getDualproof() if (dualProof === undefined) { console.error('Error getting dualProof from verifiedSetReferenceAt response') reject() } else { let sourceId let sourceAlh if (txid === 0) { sourceId = tx.id sourceAlh = tx.alh } else { sourceId = txid sourceAlh = txhash } const targetId = tx.id const targetAlh = util.getAlh(resTxMetadata) verifies = verifyDualProof(dualProof, sourceId, targetId, sourceAlh, targetAlh) if (!verifies) { console.error('Dual verification failed for verifiedSetReferenceAt') reject() } this.state.set( { serverName: this._serverUUID, databaseName: this._activeDatabase }, { db: this._activeDatabase, txid: targetId, txhash: targetAlh, signature: res.getSignature()?.toObject() } ) const txMetadataObject: schemaTypes.TxMetadata.AsObject = { id: resTxMetadata.getId(), prevalh: util.getAlh(resTxMetadata), ts: resTxMetadata.getTs(), nentries: resTxMetadata.getNentries(), eh: resTxMetadata.getEh_asU8(), bltxid: resTxMetadata.getBltxid(), blroot: resTxMetadata.getBlroot_asU8(), } resolve(txMetadataObject) } } } } } })) } catch (err) { console.error(err); } } async setAll({ kvsList }: Parameters.SetAll): Promise<schemaTypes.TxMetadata.AsObject | undefined> { try { const req = new schemaTypes.SetRequest(); const kvls = kvsList.map(({ key, value }) => { const kv = new schemaTypes.KeyValue(); kv.setKey(util.utf8Encode(key)) kv.setValue(util.utf8Encode(value)) return kv }) req.setKvsList(kvls) return new Promise((resolve, reject) => this.client.set(req, this._metadata, (err, res) => { if (err) { console.error('setAll error', err) reject(err) } else { resolve({ id: res.getId(), prevalh: util.getAlh(res), ts: res.getTs(), nentries: res.getNentries(), eh: res.getEh(), bltxid: res.getBltxid(), blroot: res.getBlroot(), }); } })) } catch (err) { console.error(err) } } async execAll({ operationsList }: Parameters.ExecAll): Promise<schemaTypes.TxMetadata.AsObject | undefined> { try { const req = new schemaTypes.ExecAllRequest(); const opl = operationsList.map(({ kv, zadd, ref }) => { const op = new schemaTypes.Op(); const keyValue = new schemaTypes.KeyValue() const zAddReq = new schemaTypes.ZAddRequest(); const refReq = new schemaTypes.ReferenceRequest(); if (kv !== undefined && kv !== null) { const { key, value } = kv keyValue.setKey(key) keyValue.setValue(value) } if (zadd !== undefined && zadd !== null) { const { set, score, key, attx, boundref } = zadd zAddReq.setSet(set) zAddReq.setScore(score) zAddReq.setKey(key) zAddReq.setAttx(attx) zAddReq.setBoundref(boundref) } if (ref !== undefined && ref !== null) { const { key, referencedkey, attx, boundref } = ref refReq.setKey(key) refReq.setReferencedkey(referencedkey) refReq.setAttx(attx) refReq.setBoundref(boundref) } op.setKv(keyValue); op.setZadd(zAddReq) op.setRef(refReq) return op }) req.setOperationsList(opl) return new Promise((resolve, reject) => this.client.execAll(req, this._metadata, (err, res) => { if (err) { console.error('execeAll error', err) reject(err) } else { resolve({ id: res.getId(), prevalh: util.getAlh(res), ts: res.getTs(), nentries: res.getNentries(), eh: res.getEh(), bltxid: res.getBltxid(), blroot: res.getBlroot(), }); } })) } catch (err) { console.error(err) } } async getAll({ keysList, sincetx }: Parameters.GetAll): Promise<schemaTypes.Entries.AsObject | undefined> { try { const req = new schemaTypes.KeyListRequest(); const encodedKeys = keysList.map(util.utf8Encode) req.setKeysList(encodedKeys); req.setSincetx(sincetx); return new Promise((resolve, reject) => this.client.getAll(req, this._metadata, (err, res) => { if (err) { console.error('Get all error', err); reject(err); } const entriesList = res.getEntriesList(); const result = entriesList.reduce( (entries, entry) => entries.concat({ tx: entry.getTx(), key: util.utf8Decode(entry.getKey()), value: util.utf8Decode(entry.getValue()), }), [] as Array<schemaTypes.Entry.AsObject> ) resolve({ entriesList: result }) })) } catch (err) { console.error(err); } } async verifiedSet({ key, value }: Parameters.VerifiedSet): Promise<schemaTypes.TxMetadata.AsObject | undefined> { try { const state = await this.state.get({ databaseName: this._activeDatabase, serverName: this._serverUUID, metadata: this._metadata }) const txid = state.getTxid() const txhash = state.getTxhash_asU8() const req = new schemaTypes.VerifiableSetRequest(); const kv = new schemaTypes.KeyValue(); const setRequest = new schemaTypes.SetRequest(); const uint8Key = util.utf8Encode(key) const uint8Value = util.utf8Encode(value) kv.setKey(uint8Key) kv.setValue(uint8Value) setRequest.setKvsList([kv]) req.setProvesincetx(txid) req.setSetrequest(setRequest) return new Promise((resolve, reject) => this.client.verifiableSet(req, this._metadata, async (err, res) => { if (err) { console.error('verifiedSet error', err) reject(err) } else { const verifiableTx = res.getTx() if (verifiableTx === undefined) { console.error('Error getting verifiableTx from verifiedSet response') reject() } else { const tx = txFrom(verifiableTx) const inclusionProof = proofTx(tx, util.prefixKey(uint8Key)) if (inclusionProof === undefined) { console.error('Error getting inclusionProof for verifiedSet') reject() } else { const eKv = util.encodeKeyValue(uint8Key, uint8Value) let verifies = verifyInclusion(inclusionProof, util.digestKeyValue(eKv), tx.htree.root) if (!verifies) { console.error('verifiedSet inclusion verification failed', err) reject(err) } const dualProof = res.getDualproof() if (!dualProof) { } else { const tTxMetadata = dualProof.getTargettxmetadata() const sTxMetadata = dualProof.getSourcetxmetadata() if (!tTxMetadata || !sTxMetadata) { } else { if (!util.equalArray(tx.htree.root, tTxMetadata.getEh_asU8())) { console.error('verifiedSet error') } let sourceId: number let sourceAlh: Uint8Array if (txid === 0) { sourceId = tx.id sourceAlh = util.getAlh(sTxMetadata) } else { sourceId = txid sourceAlh = txhash } const targetId = tx.id const targetAlh = util.getAlh(tTxMetadata) verifies = verifyDualProof(dualProof, sourceId, targetId, sourceAlh, targetAlh) if (!verifies) { console.error('verifiedSet dual verification failed', err) reject(err) } this.state.set({ serverName: this._serverUUID, databaseName: this._activeDatabase }, { db: this._activeDatabase, txid: targetId, txhash: targetAlh, signature: res.getSignature()?.toObject() }) resolve(tTxMetadata.toObject()) } } } } } })) } catch (err) { console.error(err) } } async verifiedGet({ key, attx, sincetx }: Parameters.VerifiedGet): Promise<schemaTypes.Entry.AsObject | undefined> { try { const state = await this.state.get({ databaseName: this._activeDatabase, serverName: this._serverUUID, metadata: this._metadata }) const txid = state.getTxid() const txhash = state.getTxhash_asU8() const req = new schemaTypes.VerifiableGetRequest(); const kr = new schemaTypes.KeyRequest(); const uint8Key = util.utf8Encode(key) kr.setKey(uint8Key) if (attx !== undefined) { kr.setAttx(attx) } if (sincetx !== undefined) { kr.setSincetx(sincetx) } req.setKeyrequest(kr); req.setProvesincetx(txid); return new Promise((resolve, reject) => this.client.verifiableGet(req, this._metadata, async (err, res) => { if (err) { console.error(err); reject(err) } else { const inclusionproof = res.getInclusionproof() const verifiabletx = res.getVerifiabletx() const entry = res.getEntry() if (!inclusionproof || !verifiabletx || !entry) { console.error('Server verifiedGet error'); reject() } else { const referencedby = entry.getReferencedby() let vTx: number let kv = new schemaTypes.KeyValue() if (referencedby === undefined) { vTx = entry.getTx() kv.setKey(util.prefixKey(uint8Key)) kv.setValue(util.prefixValue(entry.getValue_asU8())) } else { const encRefKey = referencedby.getKey_asU8() const atTx = referencedby.getAttx() const entryKey = entry.getKey_asU8() vTx = referencedby.getTx() kv.setKey(util.prefixKey(encRefKey)) kv.setValue(util.encodeReferenceValue(entryKey, atTx)) } const dualproof = verifiabletx.getDualproof() if (dualproof === undefined) { console.error('Server verifiedGet error'); reject() } else { const targettxmetadata = dualproof.getTargettxmetadata() const sourcetxmetadata = dualproof.getSourcetxmetadata() if (targettxmetadata === undefined || sourcetxmetadata === undefined) { console.error('Server verifiedGet error'); reject() } else { let eh let sourceId let sourceAlh let targetId let targetAlh if (txid <= vTx) { const tPrevalh = util.getAlh(targettxmetadata) eh = targettxmetadata.getEh_asU8() sourceId = txid sourceAlh = txhash targetId = vTx targetAlh = tPrevalh } else { const sPrevalh = util.getAlh(sourcetxmetadata) eh = sourcetxmetadata.getEh_asU8() sourceId = vTx sourceAlh = sPrevalh targetId = txid targetAlh = txhash } let verifies = verifyInclusion(inclusionproof, util.digestKeyValue(kv), eh) if (!verifies) { console.error('verifiedGet inclusion verification failed'); reject() } verifies = verifyDualProof( dualproof, sourceId, targetId, sourceAlh, targetAlh ) if (!verifies) { console.error('verifiedGet dual verification failed'); reject() } this.state.set( { serverName: this._serverUUID, databaseName: this._activeDatabase }, { txid: targetId, txhash: targetAlh, signature: verifiabletx.getSignature()?.toObject(), db: this._activeDatabase } ) const referencedBy = entry.getReferencedby() resolve({ tx: vTx, key: util.utf8Decode(entry.getKey_asU8()), value: util.utf8Decode((entry.getValue_asU8())), referencedby: referencedBy?.toObject() }) } } } } })) } catch (err) { console.error(err) } } async verifiedGetAt({ key, attx }: Parameters.VerifiedGetAt) { return await this.verifiedGet({ key, attx }) } async verifiedGetSince({ key, sincetx }: Parameters.VerifiedGetSince) { return await this.verifiedGet({ key, sincetx }) } async updateAuthConfig(params: Parameters.UpdateAuthConfig): Promise<empty.Empty | undefined> { try { const req = new schemaTypes.AuthConfig(); req.setKind(params && params.kind); return new Promise((resolve, reject) => this.client.updateAuthConfig(req, this._metadata, (err, res) => { if (err) { console.error('Update auth config error', err); return reject(err); } resolve(res); }) ); } catch (err) { console.error(err); } } async updateMTLSConfig(params: Parameters.UpdateMTLSConfig): Promise<empty.Empty | undefined> { try { const req = new schemaTypes.MTLSConfig(); req.setEnabled(params && params.enabled); return new Promise((resolve, reject) => this.client.updateMTLSConfig(req, this._metadata, (err, res) => { if (err) { console.error('Update mtls config error', err); return reject(err); } else { resolve(res); } }) ); } catch (err) { console.error(err); } } async txById({ tx }: Parameters.TxById): Promise<schemaTypes.Tx.AsObject | undefined> { try { const req = new schemaTypes.TxRequest(); req.setTx(tx) return new Promise((resolve, reject) => this.client.txById(req, this._metadata, (err, res) => { if (err) { console.error('txById error', err); reject(err); } else { const { metadata, entriesList } = res.toObject() resolve({ metadata, entriesList }) } })) } catch (err) { console.error(err); } } async verifiedTxById({ tx }: Parameters.VerifiedTxById): Promise<schemaTypes.Tx.AsObject | undefined> { try { const state = await this.state.get({ databaseName: this._activeDatabase, serverName: this._serverUUID, metadata: this._metadata }) const txid = state.getTxid() const txhash = state.getTxhash_asU8() const req = new schemaTypes.VerifiableTxRequest() req.setTx(tx) req.setProvesincetx(txid) return new Promise((resolve, reject) => this.client.verifiableTxById(req, this._metadata, (err, res) => { if (err) { console.error('verifiedTxById error', err) reject(err) } else { const dualProof = res.getDualproof() if (dualProof === undefined) { console.error('Error getting verifiedTxById dualProof') reject() } else { const resTxId = res.getTx()?.getMetadata()?.getId() if (resTxId === undefined) { console.error('Error getting verifiedTxById txId') reject() } else { const targettxmetadata = dualProof.getTargettxmetadata() const sourcetxmetadata = dualProof.getSourcetxmetadata() if (targettxmetadata === undefined || sourcetxmetadata === undefined) { console.error('Error getting verifiedTxById txmetadata') reject() } else { let sourceId let sourceAlh let targetId let targetAlh if (txid <= resTxId) { sourceId = txid sourceAlh = txhash targetId = resTxId targetAlh = util.getAlh(targettxmetadata) } else { sourceId = resTxId sourceAlh = util.getAlh(sourcetxmetadata) targetId = txid targetAlh = txhash } const verifies = verifyDualProof( dualProof, sourceId, targetId, sourceAlh, targetAlh ) if (verifies === false) { console.error('verifiedTxById dual verification failed') reject() } this.state.set({ serverName: this._serverUUID, databaseName: this._activeDatabase, }, { db: this._activeDatabase, txhash: targetAlh, txid: targetId, signature: res.getSignature()?.toObject() }) resolve(res.getTx()?.toObject()) } } } } })) } catch (err) { console.error(err) } } async txScan(params: Parameters.TxScan): Promise<schemaTypes.TxList.AsObject | undefined> { try { const req = new schemaTypes.TxScanRequest(); req.setInitialtx(params.initialtx); req.setLimit(params.limit); req.setDesc(params.desc); return new Promise((resolve, reject) => this.client.txScan(req, this._metadata, (err, res) => { if (err) { console.error('txScan error', err) reject(err) } else { resolve(res.toObject()) } })) } catch (err) { console.error(err) } } async compactIndex(): Promise<empty.Empty | undefined> { try { const req = new empty.Empty() return new Promise((resolve, reject) => this.client.compactIn