How to handle multiple web3 transactions in nodejs - javascript

I am handling a transaction like this:
web3.eth.getTransactionCount(sender_address, (err, txCount) =>{
console.log(txCount)
if(err){
console.log(err);
}else{
const txObject = {
nonce: web3.utils.toHex(txCount),
to: master_address,
value: web3.utils.toHex(web3.utils.toWei("0.1", "ether")),
gasLimit: web3.utils.toHex(21000),
gasPrice: web3.utils.toHex(web3.utils.toWei("10", "gwei"))
}
const tx = new Tx(txObject);
tx.sign(sender_private_key);
const serialized_tx = tx.serialize();
const raw = '0x' + serialized_tx.toString('hex');
web3.eth.sendSignedTransaction(raw, (err, txHash) =>{
if(err){
console.log(err);
}else{
console.log("txHash:", txHash);
}
}).then(function(receipt) {
//Insert amir
if(receipt["status"] == true){
console.log("Tx has been mined")
}else{
}
console.log(receipt)
});
}
});
At the moment this works if the sender makes 1 transaction, but if the sender makes two transactions before they are mined, the nonce is the same, and will therefore cause an error.
I have read in web3js documentation about chaining, but i am not sure if this is what i am looking for.
How would i change my existing code in order to work for multiple transactions before they are mined?

Your app should manage nonce and keep latest value, then use it in send transaction.
But maybe pending transactions count solve your problem
web3.eth.getTransactionCount(sender_address, 'pending', (err, txCount) =>{...

Related

Cannot Execute SQL Query in a .map()

I have an endpoint that receives an array in the req.body. I need to fetch that array and for each element of that array, i need to execute the SQL Update Query. This is the code:
const approveShifts = (req, res) => {
try {
const { guard_id } = req.params;
const { shiftIDs, isBooked } = req.body;
shiftIDs.map((shift_id) => {
connection.query(
`UPDATE shift SET isBooked=${isBooked}, fk_guard=${guard_id} WHERE shiftID=${shift_id}`,
(err, rows) => {
if (!err) {
res.status(200).json({
success: true,
message: `Successfully Approved Shift #${shift_id} for Guard #${guard_id}`,
});
} else {
res.status(404).json({
success: false,
message: "Shift Not Found!",
});
}
}
);
});
} catch (error) {
res.status(500).json({
success: false,
message: error.message,
});
}
};
This is my req.body:
{
"shiftIDs": [64],
"isBooked": 1
}
The issue is, no matter what kind of testing i do, the only output i get is "Shift Not Found!" from the else statement of the query. Nothing else happens. I can't get it to work. Can someone guide me ?
A couple of things here- firstly I recommend you use prepared statements instead of string templates for your query:
// (assuming this is mysql client?)
connection.query('UPDATE shift SET isBooked = ?, fk_guard = ? WHERE shiftID = ?', [isBooked, guard_id, shift_id], (err, rows, fields) => {...})
// if it's the mysql2 client, use connection.execute() instead of connection.query()
This works by replacing each ? with the value in the array, in order. This will help avoid SQL injection problems.
Secondly, you can do this in 1 query instead of mapping by using the IN SQL operator because you are setting the same value for isBooked and fk_guard for every shiftID:
// assuming shiftIDs is an array
connection.query('UPDATE shift SET isBooked = ?, fk_guard = ? WHERE shiftID IN (?)', [isBooked, guard_id, shiftIDs], (err, rows, fields) => {...});
And as someone else said, you should console.log(err) right before res.status(404) to see what the error is. And by the way, if the shift doesn't exist, no rows will be updated but no error will be thrown either, so your response wouldn't be 404.

Google Cloud Functions, resolveMX is not working with a list of domain

I have the following function. I have a list of domains (very big list, more than 100000), I'm trying to put them in a foreach and resolveMx all of them and save the mx records in another database.
Edit, this is the complete function:
const dns = require('dns');
const {BigQuery} = require('#google-cloud/bigquery');
const bigquery = new BigQuery(project="smartiodomains");
const functions = require('firebase-functions');
exports.getMxRecords = functions.https.onRequest( async (req, res) => {
const query = "SELECT string_field_0 FROM smartiodomains.Domains.sk_domains_table";
const options = {
query: query,
location: 'US',
};
const [job] = await bigquery.createQueryJob(options);
const [rows] = await job.getQueryResults();
const datasetId = 'Domains';
const tableId = 'smartio_records';
var index = 0;
rows.forEach((row) => {
dns.resolveMx(row.string_field_0, function(error,addresses){
if(error){
const rows = [
{domain:row.string_field_0, mx_records: 'No data found.', priority: 'No data found.'}
];
// Insert data into a table
bigquery
.dataset(datasetId)
.table(tableId)
.insert(rows);
res.write("Something");
}else{
res.write("Something else");
addresses.forEach( address => {
const rows = [
{domain:row.string_field_0, mx_records: address.exchange, priority: address.priority}
];
// Insert data into a table
bigquery
.dataset(datasetId)
.table(tableId)
.insert(rows).then((foundErrors) => {
if (foundErrors && foundErrors.insertErrors != undefined) {
console.log('Error: ', err);
}
})
.catch((err) => {
console.error('ERROR:', err);
});
});
}
});
});
});
As #Doug Stevenson suggested i add a response (res.write("Something")). Now i have one error and a warning:
1.- Memory Limit exceeded
2.- TeenyStatisticsWarning: Possible excessive concurrent requests detected. 5000 requests in-flight, which exceeds the configured threshold of 5000. Use the TEENY_REQUEST_WARN_CONCURRENT_REQUESTS environment variable or the concurrentRequests option of teeny-request to increase or disable (0) this warning.
Old error:
With this implementation i got this error in the logs of GCF:
getMxRecordsp5ter5a8u17q { Error: queryMx ETIMEOUT marketingweb.sk
Sorry for my bad english. And thanks for any help.
An HTTP function requires that you send a response to the client after all of the asynchronous work is complete. The function terminates immediately after you send that response. Right now, you're not sending any response, so the function never terminates, and it will always time out. You should send a response after all the calls to dns.resolveMx are fully complete.

MongoError: No transaction started

I am using mongoose transactions for the first time. Following the documentation and some articles, I was able to get it running using run-rs for local replicas. However, I encountered two issues,
Even though the transaction reflects on the replica sets, mongoose always throws the error MongoError: No transaction started. I have tried checking for solutions but can't find any to solve this problem.
Upon successful completion of the transaction, there's another async function that is meant to send notification emails. However, I realized that somehow, this notification email function runs before the transaction occurs, then the transaction function runs second. I am guessing this might have to do with promises, correct me if I am wrong.
Here's what the two functions look like.
await transactionDb
.handleMoneyTransfer({
senderId,
receiverId: paymentInfo.getReceiver(),
amount: paymentInfo.getAmountToPay(),
ref
})
return await sendNotificationEmail({ ref, user })
The handleMoneyTransfer function is meant to run first, then the sendNotificationEmail is meant to run next, but that's not the case here.
Here is the code that handles the mongoose transaction listed below.
async function handleMoneyTransfer({ senderId, receiverId, amount, ref }) {
const session = await mongoose.startSession()
try {
const sender = await User.findOne({ _id: senderId }).session(session)
sender.balance -= amount
await sender.save({ session })
const receiver = await User.findOne({ _id: receiverId }).session(session)
// receiver.balance += amount
const transactionInfo = await Transaction.findOne({
reference: ref
}).session(session)
const newEscrow = await new Escrow({
amount,
reference: ref,
buyerInfo: {
buyerId: sender._id,
email: sender.email
},
sellerInfo: {
sellerId: receiverId,
email: receiver.email
},
currentTransaction: {
transaction: transactionInfo
}
})
await newEscrow.save({ session })
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
} finally {
session.endSession()
}
}
Here is how I connect using mongoose
const setupDB = async (uri, dbUrl) => {
try {
await mongoose.connect(`${uri}/${dbUrl}`, {
useUnifiedTopology: true,
useNewUrlParser: true,
replicaSet: 'rs'
})
console.log('Connected')
} catch (e) {
return console.log(e)
}
}
which is translated to this
setupDB(
'mongodb://DESKTOP-SNA1HQK:27017,DESKTOP-SNA1HQK:27018,DESKTOP-SNA1HQK:27019',
'escrow?replicaSet=rs'
)
Now, I am stuck on fixing the No transaction started error and also getting these functions to run in the order they are placed.
Help will be very much appreciated, thank you in advance.
You seem to be missing the actual start of a transaction. Adding the following to your code should fix the issue:
async function handleMoneyTransfer({ senderId, receiverId, amount, ref }) {
const session = await mongoose.startSession()
session.startTransaction();
// rest of your code
}

Res value is null in an app.get call done from vue.js front-end to express back-end

I am calling this code from the front-end and confirmed that there is a proper db connection and that the Id value is properly passed, and that there is a corresponding value in the database, but for some reason, res is null. What am I missing?
app.get("/api/walletlogin/user/:userId", (req, res) => {
id = req.params.userId
var query = {_id: id}
db.collection("Users").findOne(query, (err, result) => {
if (result) {
console.log(result.userName)
} else {
console.log('No User')
}
})
Here is the front-end call:
axios.get('/api/walletlogin/user/' + accounts)
.then((response) => {
console.log('Logged in With ' + accounts)
router.push('/account')
})
.catch((errors) => {
console.log('Cannot log in')
})
}).catch((err) => {
console.log(err, 'err!!')
})
You could try to convert your id to an objectID.
var ObjectId = require('mongodb').ObjectId;
var id = ObjectId(req.params.userId);
to search by id, you must use the ObjectID class from the mongodb package. Here is an example invented by me, it does not reflect the real work, but I hope it will become clear on it:
const { ObjectID } = require('mongodb');
const id = '5ee4f69bfa0b960de8aec158'; // in your example is req.params.userId
db.collection('users').findOne({ _id: new ObjectID(id)}, (error, result) => {
if (error) {
throw error;
}
console.log(result);
})
I am adding the details of the issue initially encountered in case someone else would experience it in the future. The value that is passed from the front-end is a cryptocurrency address. For some reason, some of the characters passed were upper-case, while the same address had been stored in the database with these same characters as lower case. Thus, one needs to add code to make sure that the case of the letters found in the respective addresses is ignored.
J

How do you make multiple database calls from a single connection/transaction with Node.js and Tedious

I am attempting to use NodeJS with the Tedious (http://pekim.github.io/tedious/) sql server plugin to make multiple database calls. My intent is to:
1. Open a connection
2. Start a transaction
3. Make multiple database (stored procedure) calls, which will not return any data.
4. Commit transaction (or roll back on error).
5. Close connection
Here is an example .js file, (without using a transaction) for NodeJS where I am attempting to make multiple database calls and it is failing with the error "Requests can only be made in the LoggedIn state, not the SentClientRequest state." Nothing I try resolves this issue.
Does anyone know how to resolve this?
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var config = {
userName: 'login',
password: 'password',
server: '127.0.0.1',
options: { rowCollectionOnDone: true }
};
var max = 1;
for (var i = 0; i < max; i++) {
var connection = new Connection(config);
function executeStatement() {
request = new Request("select 42, 'hello world'", function (err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
}
});
request.on('row', function (columns) {
columns.forEach(function (column) {
console.log(column.value);
});
});
request.on('doneInProc', function (rowCount, more, rows) {
});
request.on('doneProc', function (rowCount, more, rows) {
console.log('statement completed!')
connection.execSql(request);
});
request.on('returnStatus', function (status) {
console.log('statement completed!')
});
connection.execSql(request);
}
connection.on('connect', function (err) {
// If no error, then good to go...
executeStatement();
});
}
console.log('Done!');
You're trying to execute a statement on a connection that is not established. You're missing an error handler before you call executeStatement.
connection.on('connect', function (err) {
if (err) {
console.log(err); // replace with your code
return;
};
// If no error, then good to go...
executeStatement();
});
Edit:
How to execute multiple statements in a transaction in serial:
var statements = ["select 1", "select 2", "select 3"];
var transaction = new sql.Transaction(connection);
transaction.begin(function(err) {
// ... error checks
async.mapSeries(statements, function(statement, next) {
var request = new sql.Request(transaction);
request.query(statement, next);
}, function(err, results) {
// ... error checks
transaction.commit(function(err, recordset) {
// ... error checks
console.log("Transaction commited.");
});
});
});
You should use tedious connection pools to create a pool of multiple connections.
For node js, a npm module is available at : https://www.npmjs.com/package/tedious-connection-pool
For every new value inside for loop you can acquire a new connection and use connection.reset on doneInProc event.
The case which you have been doing is performing 1st iteration of for loop correctly(LoggedIn State) and as you have proceeded without closing or releasing the connection you are using same connection object (SentClientRequest state).
Hence the same object is at final state when the code reaches second iteration of for loop.
Hope it resolves your issue
you can use Tedious Connection pools https://github.com/pekim/tedious-connection-pool
As #zevsuld and #mannutech said, tedious-connection-pool will enable multiple connections, and prevent erring out when simultaneous requests come into your server.
Below is a generic example that allows you to write multiple queries within one connection pool, and expose them for use in your api. I'm just adding this in case others come along who are trying to accomplish this type of implementation.
const ConnectionPool = require('tedious-connection-pool');
const path = require('path');
require('dotenv').config({
path: path.join(__dirname, '../../.env')
})
let Request = require('tedious').Request;
let poolConfig = {
min: 10,
max: 50,
log: true
}
let connectionConfig = {
userName: process.env.user,
password: process.env.password,
server: process.env.server
};
//create the pool
let pool = new ConnectionPool(poolConfig, connectionConfig);
pool.on('error', function(err) {
console.error(err);
});
// At this point in the code, we have established a connection pool. If you run node, you'll see it log out all then connections to your database.
// Let's add some methods which your server might use in fulfilling requests to various endpoints.
let query1 = (cb, res, query) => {
// acquire a connection:
pool.acquire(function(err, connection) {
if (err) {
console.error(err);
return;
} else {
// form your query
let sql_query = `SELECT column1, colum2 from TABLE WHERE column1 LIKE '${query.param}%%' ORDER BY column1 ASC`
// use the connection as usual:
request = new Request(sql_query, (err, rowCount) => {
if (err) {
console.log(err);
return;
} else {
// console.log('rowCount:', rowCount);
}
//release the connection back to the pool when finished
connection.release();
});
let records = [];
request.on("row", function(columns) {
let rowArray = [];
columns.forEach(function(column) {
rowArray.push(column.value);
});
records.push(rowArray);
});
request.on("doneInProc", function() {
cb(records, res);
});
// lastly exectue the request on the open connection.
connection.execSql(request);
}
});
};
let query2 = (cb, res, query) => {
// acquire a connection:
pool.acquire(function(err, connection) {
if (err) {
console.error(err);
return;
} else {
// form your query
let sql_query = `SELECT column3, colum4 from TABLE2 WHERE column3 LIKE '${query.param}%%' ORDER BY column3 ASC`;
// use the connection as usual:
request = new Request(sql_query, (err, rowCount) => {
if (err) {
console.log(err);
return;
} else {
// console.log('rowCount:', rowCount);
}
//release the connection back to the pool when finished
connection.release();
});
let records = [];
request.on("row", function(columns) {
let rowArray = [];
columns.forEach(function(column) {
rowArray.push(column.value);
});
records.push(rowArray);
});
request.on("doneInProc", function() {
cb(records, res);
});
// lastly exectue the request on the open connection.
connection.execSql(request);
}
});
};
// Let's expose these two functions to the rest of your API:
module.exports = {
query1,
query2
}

Categories