diff --git a/server/config/index.js b/server/config/index.js index 50638e3..0789ee2 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -1,5 +1,5 @@ const config = { - start_node: true, + start_node: false, logging: 'debug', bcoin_http: 'localhost', bcoin: { diff --git a/server/lib/api/address.js b/server/lib/api/address.js index 719a8cc..5b19c4a 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -2,6 +2,7 @@ const logger = require('../logger'); const request = require('request'); const config = require('../../config'); const util = require('../util'); +const db = require('../db'); const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; const TTL = config.api.request_ttl; @@ -10,63 +11,44 @@ module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { const addr = req.params.addr || ''; - if (!util.isBitcoinAddress(addr)) { - return res.status(400).send({ - error: 'Invalid bitcoin address', - }); - } + db.txs.getTxByAddress(addr, 0, 999999999, (error, txs) => { + if (error) { + logger.log('error', + `getTxByBlock ${error}`); + return res.status(404).send(); + } - logger.log('debug', - 'Warning: Requesting data from Bcoin by address, may take some time'); - // Get Bcoin data - return request(`${API_URL}/tx/address/${addr}`, - { timeout: TTL }, - (error, bcoinRes, bcoinTxs) => { - if (error) { - logger.log('error', - `${error}`); - return res.status(404).send({}); + // Sum the matching outputs for every tx + const totalReceived = txs.reduce((total, tx) => total + tx.outputs.reduce((sum, output) => { + if (output.address === req.params.addr) { + return sum + output.value; } - let txs = {}; - try { - txs = JSON.parse(bcoinTxs); - } catch (e) { - logger.log('error', - `${e}`); - return res.status(404).send({}); + return sum; + }, 0), 0) || 0; + + // Sum the matching inputs for every tx + const totalSpent = txs.reduce((total, tx) => total + tx.inputs.reduce((sum, input) => { + if (input.coin && input.coin.address === req.params.addr) { + return sum + input.coin.value; } + return sum; + }, 0), 0) || 0; - // Sum the matching outputs for every tx - const totalReceived = txs.reduce((total, tx) => total + tx.outputs.reduce((sum, output) => { - if (output.address === req.params.addr) { - return sum + output.value; - } - return sum; - }, 0), 0) || 0; - - // Sum the matching inputs for every tx - const totalSpent = txs.reduce((total, tx) => total + tx.inputs.reduce((sum, input) => { - if (input.coin && input.coin.address === req.params.addr) { - return sum + input.coin.value; - } - return sum; - }, 0), 0) || 0; - - // Match Insight API - return res.json({ - addrStr: req.params.addr, - balance: (totalReceived - totalSpent) / 1e8, - balanceSat: totalReceived - totalSpent, - totalReceived: totalReceived / 1e8, - totalReceivedSat: totalReceived, - totalSent: totalSpent / 1e8, - totalSentSat: totalSpent, - unconfirmedBalance: 0, - unconfirmedBalanceSat: 0, - unconfirmedTxApperances: 0, - txApperances: txs.length, - }); + // Match Insight API + return res.json({ + addrStr: req.params.addr, + balance: (totalReceived - totalSpent) / 1e8, + balanceSat: totalReceived - totalSpent, + totalReceived: totalReceived / 1e8, + totalReceivedSat: totalReceived, + totalSent: totalSpent / 1e8, + totalSentSat: totalSpent, + unconfirmedBalance: 0, + unconfirmedBalanceSat: 0, + unconfirmedTxApperances: 0, + txApperances: txs.length, }); + }); }); // Stubbed by # to help with tasking diff --git a/server/lib/api/block.js b/server/lib/api/block.js index ad14eb5..18f499a 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -60,7 +60,7 @@ module.exports = function BlockAPI(router) { limit, (err, blocks) => { if (err) { - logger.log('err', + logger.log('error', `/blocks: ${err}`); return res.status(404).send(); } @@ -96,7 +96,7 @@ module.exports = function BlockAPI(router) { 1, (err, block) => { if (err) { - logger.log('err', + logger.log('error', `/rawblock/:blockHash: ${err}`); return res.status(404).send(); } @@ -113,7 +113,7 @@ module.exports = function BlockAPI(router) { 1, (err, block) => { if (err) { - logger.log('err', + logger.log('error', `/block-index/:height: ${err}`); return res.status(404).send(); } diff --git a/server/lib/api/index.js b/server/lib/api/index.js index 9de150c..f857182 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -37,13 +37,11 @@ const MessageAPI = require('./message')(api); app.use('/insight-api', api); // 404 -app.use((req, res) => { - res.status(404).send({ +app.use((req, res) => res.status(404).send({ status: 404, url: req.originalUrl, error: 'Not found', - }); -}); + })); // Socket server const server = require('http').Server(app); diff --git a/server/lib/api/message.js b/server/lib/api/message.js index a15802a..e6e01b3 100644 --- a/server/lib/api/message.js +++ b/server/lib/api/message.js @@ -27,7 +27,7 @@ function verifyMessage(req, res) { code: 1, }); } - res.json({ result: valid }); + return res.json({ result: valid }); } module.exports = function messageAPI(router) { @@ -39,7 +39,5 @@ module.exports = function messageAPI(router) { verifyMessage(req, res); }); - router.get('/utils/estimatefee', (req, res) => { - res.send('estimate fees'); - }); + router.get('/utils/estimatefee', (req, res) => res.send('estimate fees')); }; diff --git a/server/lib/api/status.js b/server/lib/api/status.js index 514d5db..3cf26ba 100644 --- a/server/lib/api/status.js +++ b/server/lib/api/status.js @@ -48,12 +48,12 @@ module.exports = function statusAPI(router) { } else { getStatus((err, status) => { if (err) { - logger.log('err', + logger.log('error', `/status getStatus: ${err}`); return res.status(404).send(err); } if (!status) { - logger.log('err', + logger.log('error', '/status getStatus: no Status'); return res.status(404).send(); } @@ -79,12 +79,12 @@ module.exports = function statusAPI(router) { router.get('/sync', (req, res) => { getStatus((err, status) => { if (err) { - logger.log('err', + logger.log('error', `/sync: ${err}`); return res.status(404).send(err); } if (!status) { - logger.log('err', + logger.log('error', '/sync: no status'); return res.status(404).send(); } @@ -100,7 +100,7 @@ module.exports = function statusAPI(router) { }); // Copied from previous source router.get('/peer', (req, res) => { - res.json({ + return res.json({ connected: true, host: '127.0.0.1', port: null, @@ -108,7 +108,7 @@ module.exports = function statusAPI(router) { }); router.get('/version', (req, res) => { - res.json({ + return res.json({ version: pkg.version, }); }); diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 5a93f14..6d6c4a4 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -12,7 +12,7 @@ module.exports = function transactionAPI(router) { // Txs by txid router.get('/tx/:txid', (req, res) => { if (!util.isTxid(req.params.txid)) { - return res.status(400).send({ + return res.status(404).send({ error: 'Invalid transaction id', }); } @@ -20,62 +20,46 @@ module.exports = function transactionAPI(router) { // Get max block height for calculating confirmations const height = db.blocks.bestHeight(); // Bcoin transaction data - return request(`${API_URL}/tx/${req.params.txid}`, - { timeout: TTL }, - (error, localRes, tx) => { - if (error) { - logger.log('error', - `${error}`); - return res.status(404).send(); - } - // Catch JSON errors - try { - tx = JSON.parse(tx); - } catch (e) { - logger.log('error', - `${e}`); - return res.status(404).send(); - } - if (!tx || !tx.hash) { - logger.log('error', - 'No results found'); - return res.status(404).send(); - } + const txid = req.params.txid || ''; - // Return UI JSON - return res.send({ - txid: tx.hash, - version: tx.version, - time: tx.ps, - blocktime: tx.ps, - locktime: tx.locktime, - blockhash: tx.block, - fees: tx.fee / 1e8, - confirmations: (height - tx.height) + 1, - valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, - vin: tx.inputs.map(input => ({ - addr: input.coin ? input.coin.address : '', - value: input.coin ? input.coin.value / 1e8 : 0, - scriptSig: { - asm: input.script, - }, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - asm: output.script, - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', - }); + db.txs.getTxById(txid, (err, transaction) => { + if (err) { + logger.log('error', + `/tx/:tid getTxById: ${err.err}`); + return res.status(404).send(); + } + + const tx = transaction; + return res.send({ + txid: tx.hash, + version: tx.version, + time: tx.ps, + blocktime: tx.ps, + locktime: tx.locktime, + blockhash: tx.block, + fees: tx.fee / 1e8, + size: tx.size, + confirmations: (height - tx.height) + 1, + valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, + vin: tx.inputs.map(input => ({ + addr: input.address, + value: input.value / 1e8, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', }); + }); }); // /txs is overloaded. Next ver separate concerns // query by block // query by address - // last n txs + // last n txs - haha jk YOU 404 router.get('/txs', (req, res) => { const pageNum = parseInt(req.query.pageNum, 10) || 0; const rangeStart = pageNum * MAX_TXS; @@ -88,46 +72,35 @@ module.exports = function transactionAPI(router) { }); } const height = db.blocks.bestHeight(); - // Get Bcoin data - return request(`${API_URL}/block/${req.query.block}`, - { timeout: TTL }, - (error, localRes, block) => { + + return db.txs.getTxCountByBlock(req.query.block, (err, count) => { + if (err) { + logger.log('error', + `getTxByBlock ${err}`); + return res.status(404).send(); + } + const totalPages = Math.ceil(count / MAX_TXS); + + return db.txs.getTxByBlock(req.query.block, pageNum, MAX_TXS, (error, txs) => { if (error) { logger.log('error', - `${error}`); + `getTxByBlock ${error}`); return res.status(404).send(); } - // Catch JSON errors - try { - block = JSON.parse(block); - } catch (e) { - logger.log('error', - `${e}`); - return res.status(404).send(); - } - - if (block.error) { - logger.log('error', - `${'No tx results'}`); - return res.status(404).send(); - } - // Setup UI JSON - const totalPages = Math.ceil(block.txs.length / MAX_TXS); - block.txs = block.txs.slice(rangeStart, rangeEnd); - return res.send({ pagesTotal: totalPages, - txs: block.txs.map(tx => ({ + txs: txs.map(tx => ({ txid: tx.hash, fees: tx.fee / 1e8, - confirmations: (height - block.height) + 1, + size: tx.size, + confirmations: (height - tx.height) + 1, valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, vin: tx.inputs.map(input => ({ - addr: input.coin ? input.coin.address : '', - value: input.coin ? input.coin.value / 1e8 : 0, scriptSig: { asm: input.script, }, + addr: input.address, + value: input.value / 1e8, })), vout: tx.outputs.map(output => ({ scriptPubKey: { @@ -140,6 +113,7 @@ module.exports = function transactionAPI(router) { })), }); }); + }); } else if (req.query.address) { if (!util.isBitcoinAddress(req.query.address)) { return res.status(400).send({ @@ -151,45 +125,34 @@ module.exports = function transactionAPI(router) { const height = db.blocks.bestHeight(); const addr = req.query.address || ''; - logger.log('debug', - 'Warning: Requesting data from Bcoin by address, may take some time'); + db.txs.getTxCountByAddress(req.query.address, (err, count) => { + if (err) { + logger.log('error', + `getTxByBlock ${err}`); + return res.status(404).send(); + } + const totalPages = Math.ceil(count / MAX_TXS); - return request(`${API_URL}/tx/address/${addr}`, - { timeout: TTL }, - (error, localRes, txs) => { + return db.txs.getTxByAddress(req.query.address, pageNum, MAX_TXS, (error, txs) => { if (error) { logger.log('error', - `${error}`); + `getTxByBlock ${error}`); return res.status(404).send(); } - // Catch JSON errors - try { - txs = JSON.parse(txs); - } catch (e) { - logger.log('error', - `${e}`); - return res.status(404).send(); - } - // Bcoin returns error as part of data object - if (txs.error) { - logger.log('error', - `${'No tx results'}`); - return res.status(404).send(); - } - // Setup UI JSON return res.send({ - pagesTotal: 1, + pagesTotal: totalPages, txs: txs.map(tx => ({ txid: tx.hash, fees: tx.fee / 1e8, - confirmations: (height - tx.height) + 1, + size: tx.size, + confirmations: (height - tx.height) + 1, valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, vin: tx.inputs.map(input => ({ - addr: input.coin ? input.coin.address : '', - value: input.coin ? input.coin.value / 1e8 : 0, scriptSig: { asm: input.script, }, + addr: input.address, + value: input.value / 1e8, })), vout: tx.outputs.map(output => ({ scriptPubKey: { @@ -202,21 +165,21 @@ module.exports = function transactionAPI(router) { })), }); }); + }); + } else { + // Get last n txs + db.txs.getTopTransactions((err, txs) => { + if (err) { + logger.log('err', + `/txs getTopTransactions ${err}`); + return res.status(404).send(err); + } + return res.json(txs); + }); } - // Get last n txs - db.txs.getTopTransactions((err, txs) => { - if (err) { - logger.log('err', - `/txs getTopTransactions ${err}`); - return res.status(404).send(err); - } - return res.json(txs); - }); }); - router.get('/rawtx/:txid', (req, res) => { - res.send(req.params.txid); - }); + router.get('/rawtx/:txid', (req, res) => res.send(req.params.txid)); router.post('/tx/send', (req, res) => { const rawtx = req.body.rawtx || ''; @@ -228,10 +191,10 @@ module.exports = function transactionAPI(router) { if (err) { logger.log('error', `${err}`); - return res.status(400).send(err); + return res.status(404).send(err); } - res.json(true); + return res.json(true); }); }); }; diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index 2b3bb75..3037fbd 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -6,6 +6,8 @@ const MAX_BLOCKS = config.api.max_blocks; // ~ 12 hours let bestBlockHeight = 0; +// This naive querying will be replaced by more advanced mongo + function getBlocks(params, options, limit, cb) { // Do not return mongo ids const defaultOptions = { _id: 0 }; @@ -56,7 +58,7 @@ function getBlock(params, options, limit, cb) { return cb(null, blocks[0]); }); } -// Highest known height in mongo +// Highest known height in mongo - Not Used function getBestHeight() { getBlock({}, {}, 1, (err, block) => { if (err) { diff --git a/server/lib/db/transactions.js b/server/lib/db/transactions.js index 151e5d6..fe12e0b 100644 --- a/server/lib/db/transactions.js +++ b/server/lib/db/transactions.js @@ -5,14 +5,14 @@ const config = require('../../config'); // For now, blocks handles these calls. // These will be replaced with more advanced mongo // No optimization yet. +// Will be replaced with a more sophisticated api soon const MAX_TXS = config.api.max_txs; const MAX_PAGE_TXS = config.api.max_page_txs; -// For Paging -function getTransactions(params, options, limit, cb) { +function getTransactions(params, options, limit, skip, cb) { // Do not return mongo ids - const defaultOptions = { _id: 0 }; + const defaultOptions = { }; // Copy over mongo options Object.assign(defaultOptions, options); // Simple sanitizing @@ -42,12 +42,13 @@ function getTransactions(params, options, limit, cb) { } return cb(null, txs); }) - .sort({ height: -1 }) + .sort({ height: 1 }) + .skip() .limit(limit); } -function getTransaction(params, options, limit, cb) { - getTransactions(params, options, limit, (err, tx) => { +function getTransaction(params, options, limit, skip, cb) { + getTransactions(params, options, limit, skip, (err, tx) => { if (err) { logger.log('error', `getTransaction: ${err.err}`); @@ -82,8 +83,120 @@ function getTopTransactions(cb) { .limit(MAX_TXS); } +function getTxById(txid, cb) { + getTransaction( + { hash: txid }, + { }, + 1, + 0, + (err, transaction) => { + if (err) { + logger.log('error', + `getTxById: ${txid} ${err.err}`); + return cb(err); + } + return cb(null, transaction); + }); +} + +function getTxByBlock(blockHash, page, limit, cb) { + getTransactions( + { block: blockHash }, + {}, + limit, + page * limit, + (err, tx) => { + if (err) { + logger.log('error', + `getTxByBlock: ${err.err}`); + return cb(err); + } + if (!tx.length > 0) { + return cb({ err: 'Tx not found' }); + } + return cb(null, tx); + }); +} + +function getTxByAddress(address, page, limit, cb) { + getTransactions( + { + $or: [ + { 'inputs.address': address }, + { 'outputs.address': address }], + }, + {}, + limit, + page * limit, + (err, tx) => { + if (err) { + logger.log('error', + `getTxByAddress: ${err.err}`); + return cb(err); + } + if (!tx.length > 0) { + return cb({ err: 'Tx not found' }); + } + return cb(null, tx); + }); +} + +function getTxCountByBlock(blockHash, cb) { + Transactions.count( + { block: blockHash }, + (err, count) => { + if (err) { + logger.log('error', + `getTxCountByBlock ${err}`); + return cb(err); + } + return cb(null, count); + }); +} + +function getTxCountByAddress(address, cb) { + Transactions.count( + { $or: [ + { 'inputs.address': address }, + { 'outputs.address': address }], + }, + (err, count) => { + if (err) { + logger.log('error', + `getTxCountByAddress ${err}`); + return cb(err); + } + return cb(null, count); + }); +} + + +function updateInput(txid, inputid, value, address) { + Transactions.findOneAndUpdate( + { _id: txid, 'inputs._id': inputid }, + { + $set: { + 'inputs.$.value': value, + 'inputs.$.address': address, + }, + }, + (err, tx) => { + if (err) { + logger.log('error', + `updateInput: ${err}`); + } + }, + ); +} + module.exports = { getTransaction, getTransactions, getTopTransactions, + getTxById, + getTxByBlock, + getTxCountByBlock, + getTxByAddress, + getTxCountByAddress, + updateInput, }; diff --git a/server/lib/node/index.js b/server/lib/node/index.js index 9a99379..2e53167 100644 --- a/server/lib/node/index.js +++ b/server/lib/node/index.js @@ -1,7 +1,7 @@ const FullNode = require('bcoin/lib/node/fullnode'); const logger = require('../../lib/logger'); const BlockParser = require('../parser').Block; -const TxParser = require('../parser').Transaction; +const TxParser = require('../parser').Transaction; const config = require('../../config'); const socket = require('../../lib/api/socket'); const db = require('../../lib/db'); @@ -24,6 +24,10 @@ function start() { db.blocks.bestHeight(entry.height); }); + node.chain.on('full', (block) => { + + }); + node.on('error', (err) => { logger.log('error', `${err}`); diff --git a/server/lib/parser/index.js b/server/lib/parser/index.js index 5bd9726..8989195 100644 --- a/server/lib/parser/index.js +++ b/server/lib/parser/index.js @@ -1,5 +1,5 @@ const Block = require('./block'); -const Transaction = require('./transaction'); +const Transaction = require('./transaction'); module.exports = { Block, diff --git a/server/lib/parser/transaction.js b/server/lib/parser/transaction.js index 7b0a8ab..51f853e 100644 --- a/server/lib/parser/transaction.js +++ b/server/lib/parser/transaction.js @@ -1,10 +1,10 @@ -const TxModel = require('../../models/transaction'); -const InputModel = require('../../models/input'); +const TxModel = require('../../models/transaction'); +const InputModel = require('../../models/input'); const OutputModel = require('../../models/output'); -const config = require('../../config'); -const util = require('../../lib/util'); -const logger = require('../logger'); -const db = require('../db'); +const config = require('../../config'); +const util = require('../../lib/util'); +const logger = require('../logger'); +const db = require('../db'); // Bleh, Bcoin pulls in blocks 20 at a time // Crappy delay for now otherwise async saves @@ -12,7 +12,10 @@ const db = require('../db'); // the last 20 that hasn't saved. // Aggregate stuff will replace all of this. +let counter = 0; + function parse(entry, txs) { + counter++; txs.forEach((tx) => { const txJSON = tx.toJSON(); const txRAW = tx.toRaw(); @@ -58,10 +61,48 @@ function parse(entry, txs) { if (err) { logger.log('error', err.message); } + // As long as this modulo is divisible by 20 we should be OK for now. + // Closer to 20 = chattier at start but ideal later on + if (counter % 20 === 0) { + findEmptyInputs(); + counter = 0; + } }); }); } +function findEmptyInputs() { + db.txs.getTransactions( + { + 'inputs.prevout.hash': { $ne: '0000000000000000000000000000000000000000000000000000000000000000' }, + 'inputs.address': '', + }, + {}, + 100, + 0, + (err, txs) => { + if (err) { + return logger.log('error', + `No Empty Inputs found: ${err.err}`); + } + // For each tx with unmarked inputs + return txs.forEach((inputTx) => { + inputTx.inputs.forEach((input) => { + const txHash = input.prevout.hash; + const outIdx = input.prevout.index; + + return db.txs.getTxById(txHash, (err, tx) => { + if (err) { + return logger.log('error', + `No Tx found: ${txHash} ${err.err}`); + } + return db.txs.updateInput(inputTx._id, input._id, tx.outputs[outIdx].value, tx.outputs[outIdx].address); + }); + }); + }); + }); +} + module.exports = { parse, }; diff --git a/server/models/block.js b/server/models/block.js index 84aed55..8c86062 100644 --- a/server/models/block.js +++ b/server/models/block.js @@ -4,21 +4,21 @@ const Transaction = require('./transaction'); const Schema = mongoose.Schema; const BlockSchema = new Schema({ - hash: { type: String, default: '' }, - height: { type: Number, default: 0 }, - size: { type: Number, default: 0 }, - version: { type: Number, default: 0 }, - prevBlock: { type: String, default: '' }, - merkleRoot: { type: String, default: '' }, - ts: { type: Number, default: 0 }, - bits: { type: Number, default: 0 }, - nonce: { type: Number, default: 0 }, + hash: { type: String, default: '' }, + height: { type: Number, default: 0 }, + size: { type: Number, default: 0 }, + version: { type: Number, default: 0 }, + prevBlock: { type: String, default: '' }, + merkleRoot: { type: String, default: '' }, + ts: { type: Number, default: 0 }, + bits: { type: Number, default: 0 }, + nonce: { type: Number, default: 0 }, txs: [{ type: String, default: '' }], - chainwork: { type: Number, default: 0 }, - reward: { type: Number, default: 0 }, - network: { type: String, default: '' }, - poolInfo: { type: Object, default: {} }, - rawBlock: { type: String, default: '' }, + chainwork: { type: Number, default: 0 }, + reward: { type: Number, default: 0 }, + network: { type: String, default: '' }, + poolInfo: { type: Object, default: {} }, + rawBlock: { type: String, default: '' }, }, { toJSON: { virtuals: true, diff --git a/server/models/input.js b/server/models/input.js index a6ebc1b..dc29b43 100644 --- a/server/models/input.js +++ b/server/models/input.js @@ -3,6 +3,7 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; const InputSchema = new Schema({ + value: { type: Number, default: 0 }, prevout: { type: Object, default: {} }, script: { type: String, default: '' }, witness: { type: String, default: '' }, diff --git a/server/public/img/bcoin.png b/server/public/img/bcoin.png new file mode 100644 index 0000000..71ba8aa Binary files /dev/null and b/server/public/img/bcoin.png differ diff --git a/server/public/img/leveldb.png b/server/public/img/leveldb.png index da13a67..b4a1777 100644 Binary files a/server/public/img/leveldb.png and b/server/public/img/leveldb.png differ