I want to call/trigger a transaction inside from another transaction. how that will be possible.
async function updateOrder(uo) { // eslint-disable-line no-unused-vars
// Get the asset registry for the asset.
assetRegistry = await getAssetRegistry('org.example.basic.OrderList');
for(var i=0;i< uo.asset.orderDtls.length;i++)
{
if(uo.asset.orderDtls[i].orderID==uo.orderID){
uo.asset.orderDtls[i].orderStatus="Accepted";
}
}
await assetRegistry.update(uo.asset);
Please provide any sample code/example to trigger another transaction whenever this transaction happen.
Please view the github issue here:
https://github.com/hyperledger/composer/issues/4375
It should answer your question. A quote from the issue:
/**
* TransactionOne
* #param {org.example.TransactionOne} The transaction one object
* #transaction
*/
async function transactionOne(tx) {
const factory = getFactory();
tx.subTransactions.forEach(async (subTransactionData, idx) => {
const subTx = factory.newResource(namespace, "TransactionTwo", tx.transactionId + ":" + idx);
subTx.subTransactionData= subTransactiondata;
await transactionTwo(subTx);
});
}
Related
I am trying to run a section of (nodejs using Web3.js) code whenever a swap occurs on a given pair contract (qPair is the pair contract derived from Quickswap's router contract (Polygon blockchain) and sPair is the same pair contract derived from Sushiswap's router contract (also Polygon blockchain)) but the code doesn't work as intended when implented as a class. I have it working in one file, but when I try to create a class for crypto pairs, the code wont work.
Here is the working code:
const main = async () => {
qPair = await getPairContract(quickswapFactoryContract, token0.address, token1.address)
sPair = await getPairContract(sushiswapFactoryContract, token0.address, token1.address)
/* The below code is listening for a swap event on the Quickswap exchange. When a swap event is
detected, the code checks the price of the token pair on Quickswap and compares it to the price
of the token pair on Sushiswap. If the price on Quickswap is higher than the price on Sushiswap, the
code will execute a trade on Quickswap. If the price on Sushiswap is higher than the price on
Quickswap, the code will execute a trade on Uniswap. */
qPair.events.Swap({}, async () => {
console.log("qPair activated")
/*
*
* Do stuff here
*
*/
})
/* The below code is listening for an event on the Sushiswap contract. When the event is detected,
the code will check the price of the token pair and determine if there is an arbitrage
opportunity. If there is an arbitrage opportunity, the code will execute the trade. */
sPair.events.Swap({}, async () => {
console.log("sPair activated")
/*
*
* Do stuff here
*
*/
})
console.log("Waiting for swap event...")
}
And here is the code that doesn't work:
const main = async () => {
qPair1 = new cryptoPair(<same token details as before go here>)
sPair1 = new cryptoPair(<same token details as before go here>)
qPair1.pairContract.events.Swap({}, async () => {
// The code here activates once (after main() reaches the bottom) and never again
})
sPair1.pairContract.events.Swap({}, async () => {
// The code here activates once (after main() reaches the bottom) and never again
})
console.log("waiting for swap event")
} // Once the debugger reaches here, the two "async" console logs activate
The class has the same code as the "working" code but instead would just do this._pairContract = await getPairContract() and then return that variable using a getter function.
Here is the (nonworking) class code:
module.exports = class cryptoPair {
constructor(token0Address, token0Decimals, token1Address, token1Decimals, factory) {
this._token0Address = token0Address;
this._token0Decimals = token0Decimals;
this._token1Address = token1Address;
this._token1Decimals = token1Decimals;
this._factory = factory;
}
// Setter functions
set token0(web3Token) {
this._token0 = web3Token;
}
set token1(web3Token) {
this._token1 = web3Token;
}
set token0Contract(web3Contract) {
this._token0Contract = web3Contract;
}
set token1Contract(web3Contract) {
this._token1Contract = web3Contract;
}
// Getter functions
get token0Address() {
return this._token0Address;
}
get token1Address() {
return this._token1Address;
}
get factory() {
return this._factory;
}
get token0Contract() {
return this._token0Contract;
}
get token1Contract() {
return this._token1Contract;
}
get token0() {
return this._token0;
}
get pairContract() {
return this._pairContract;
}
// The following two functions are nearly identically defined in the "working code"
// But instead don't use the "this.variableName" syntax
async defineTokens(t0Address, t0Decimals, t1Address, t1Decimals) {
try {
this._token0Contract = new web3.eth.Contract(IERC20.abi, t0Address)
this._token1Contract = new web3.eth.Contract(IERC20.abi, t1Address)
const t0Symbol = await this._token0Contract.methods.symbol().call()
const t0Name = await this._token0Contract.methods.name().call()
this._token0 = new Token(
ChainId.POLYGON,
t0Address,
t0Decimals,
t0Symbol,
t0Name
)
const t1Symbol = await this._token1Contract.methods.symbol().call()
const t1Name = await this._token1Contract.methods.name().call()
this._token1 = new Token(
ChainId.POLYGON,
t1Address,
t1Decimals,
t1Symbol,
t1Name
)
} catch (err) {
// For some reason, I keep getting the error "hex data is odd-length" in the
// class but not when this same code is outside of a class
console.log("Token creation failed, retrying...")
this.defineTokens(this._token0Address, this._token0Decimals, this._token1Address, this._token1Decimals)
}
}
async definePairContract() {
this._pairAddress = await this._factory.methods.getPair(this._token0Address, this._token1Address).call();
this._pairContract = new web3.eth.Contract(IUniswapV2Pair.abi, this._pairAddress);
}
}
To reiterate, the "working code" runs the inner code of the async events.Swap() code whenever a swap is triggered, but the same code when implemented as a class does not work. Is this because of the use of classes? Or did I make a mistake somewhere? Thanks in advance!
I fixed the issue. Of course, the issue was outside of the code provided where I defined web3. The working way defined it as:
web3 = new Web3(`wss://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`)
whereas the incorrect class was defining it as
provider = new HDWalletProvider({
privateKeys: [process.env.DEPLOYMENT_ACCOUNT_KEY],
providerOrUrl: `wss://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`
})
web3 = new Web3(provider)
I set up a simple Backendless API Service and am running it through CodeRunner. As a test, I'm simply getting a record from the database and returning it. I've tried every combination of return type definition in the class annotations that I can think of, and I've assured that the correct record exists and is being returned to the service, but I've never successfully had the record returned using the console, or via a SDK invocation. In every case, the body returned to the invocation is null. My current test uses "Object" as the return type for the getSchedule call - are database objects not objects?
Here is the entire service:
'use strict';
const { DateTime } = require("luxon");
const util = require("util");
class Scheduling {
/**
*
* #param {String} day
* #returns {Object}
*/
getSchedule( day ) {
let t = DateTime.fromISO(day).toMillis();
let q = Backendless.DataQueryBuilder.create().setWhereClause(`day = ${t}`);
Backendless.Data.of("schedules").find(q)
.then(rec => {
console.log(util.inspect(rec,3))
if (rec.length === 1) {
return rec[0]
}
else {
return {error: 404, msg: 'not found'}
}
})
}
}
Backendless.ServerCode.addService( Scheduling )
The "inspect" call indicates I am retrieving the correct record. No errors, the return status of the invocation is always 200. Obviously, I'm missing something about API service return types, please point me in the correct direction.
The problem is the response for the find method is returned after the invocation of getSchedule is complete (because the API invocation is asynchronous).
How about declaring the getSchedule with async and then await for the API invocation?
'use strict';
const { DateTime } = require("luxon");
const util = require("util");
class Scheduling {
/**
*
* #param {String} day
* #returns {Object}
*/
async getSchedule( day ) {
let t = DateTime.fromISO(day).toMillis();
let q = Backendless.DataQueryBuilder.create().setWhereClause(`day = ${t}`);
var rec = await Backendless.Data.of("schedules").find(q);
console.log(util.inspect(rec,3))
if (rec.length === 1) {
return rec[0]
}
else {
return {error: 404, msg: 'not found'}
}
}
}
Backendless.ServerCode.addService( Scheduling )
We're having an issue in our main data synchronization back-end function. Our client's mobile device is pushing changes daily, however last week they warned us some changes weren't updated in the main web app.
After some investigation in the logs, we found that there is indeed a single transaction that fails and rollback. However it appears that all the transactions before this one also rollback.
The code works this way. The data to synchronize is an array of "changesets", and each changset can update multiple tables at once. It's important that a changset be updated completely or not at all, so each is wrapped in a transaction. Then each transaction is executed one after the other. If a transaction fails, the others shouldn't be affected.
I suspect that all the transactions are actually combined somehow, possibly through the main db.task. Instead of just looping to execute the transactions, we're using a db.task to execute them in batch avoid update conflicts on the same tables.
Any advice how we could execute these transactions in batch and avoid this rollback issue?
Thanks, here's a snippet of the synchronization code:
// Begin task that will execute transactions one after the other
db.task(task => {
const transactions = [];
// Create a transaction for each changeset (propriete/fosse/inspection)
Object.values(data).forEach((change, index) => {
const logchange = { tx: index };
const c = {...change}; // Use a clone of the original change object
transactions.push(
task.tx(t => {
const queries = [];
// Propriete
if (Object.keys(c.propriete.params).length) {
const params = proprietes.parse(c.propriete.params);
const propriete = Object.assign({ idpropriete: c.propriete.id }, params);
logchange.propriete = { idpropriete: propriete.idpropriete };
queries.push(t.one(`SELECT ${Object.keys(params).join()} FROM propriete WHERE idpropriete = $1`, propriete.idpropriete).then(previous => {
logchange.propriete.previous = previous;
return t.result('UPDATE propriete SET' + qutil.setequal(params) + 'WHERE idpropriete = ${idpropriete}', propriete).then(result => {
logchange.propriete.new = params;
})
}));
}
else delete c.propriete;
// Fosse
if (Object.keys(c.fosse.params).length) {
const params = fosses.parse(c.fosse.params);
const fosse = Object.assign({ idfosse: c.fosse.id }, params);
logchange.fosse = { idfosse: fosse.idfosse };
queries.push(t.one(`SELECT ${Object.keys(params).join()} FROM fosse WHERE idfosse = $1`, fosse.idfosse).then(previous => {
logchange.fosse.previous = previous;
return t.result('UPDATE fosse SET' + qutil.setequal(params) + 'WHERE idfosse = ${idfosse}', fosse).then(result => {
logchange.fosse.new = params;
})
}));
}
else delete c.fosse;
// Inspection (rendezvous)
if (Object.keys(c.inspection.params).length) {
const params = rendezvous.parse(c.inspection.params);
const inspection = Object.assign({ idvisite: c.inspection.id }, params);
logchange.rendezvous = { idvisite: inspection.idvisite };
queries.push(t.one(`SELECT ${Object.keys(params).join()} FROM rendezvous WHERE idvisite = $1`, inspection.idvisite).then(previous => {
logchange.rendezvous.previous = previous;
return t.result('UPDATE rendezvous SET' + qutil.setequal(params) + 'WHERE idvisite = ${idvisite}', inspection).then(result => {
logchange.rendezvous.new = params;
})
}));
}
else delete change.inspection;
// Cheminees
c.cheminees = Object.values(c.cheminees).filter(cheminee => Object.keys(cheminee.params).length);
if (c.cheminees.length) {
logchange.cheminees = [];
c.cheminees.forEach(cheminee => {
const params = cheminees.parse(cheminee.params);
const ch = Object.assign({ idcheminee: cheminee.id }, params);
const logcheminee = { idcheminee: ch.idcheminee };
queries.push(t.one(`SELECT ${Object.keys(params).join()} FROM cheminee WHERE idcheminee = $1`, ch.idcheminee).then(previous => {
logcheminee.previous = previous;
return t.result('UPDATE cheminee SET' + qutil.setequal(params) + 'WHERE idcheminee = ${idcheminee}', ch).then(result => {
logcheminee.new = params;
logchange.cheminees.push(logcheminee);
})
}));
});
}
else delete c.cheminees;
// Lock from further changes on the mobile device
// Note: this change will be sent back to the mobile in part 2 of the synchronization
queries.push(t.result('UPDATE rendezvous SET timesync = now() WHERE idvisite = $1', [c.idvisite]));
console.log(`transaction#${++transactionCount}`);
return t.batch(queries).then(result => { // Transaction complete
logdata.transactions.push(logchange);
});
})
.catch(function (err) { // Transaction failed for this changeset, rollback
logdata.errors.push({ error: err, change: change }); // Provide error message and original change object to mobile device
console.error(JSON.stringify(logdata.errors));
})
);
});
console.log(`Total transactions: ${transactions.length}`);
return task.batch(transactions).then(result => { // All transactions complete
// Log everything that was uploaded from the mobile device
log.log(res, JSON.stringify(logdata));
});
I apologize, this is almost impossible to make a final good answer when the question is wrong on too many levels...
It's important that a change set be updated completely or not at all, so each is wrapped in a transaction.
If the change set requires data integrity, the whole thing must be one transaction, and not a set of transactions.
Then each transaction is executed one after the other. If a transaction fails, the others shouldn't be affected.
Again, data integrity is what a single transaction guarantees, you need to make it into one transaction, not multiple.
I suspect that all the transactions are actually combined somehow, possibly through the main db.task.
They are combined, and not through task, but through method tx.
Any advice how we could execute these transactions in batch and avoid this rollback issue?
By joining them into a single transaction.
You would use a single tx call at the top, and that's it, no tasks needed there. And in case the code underneath makes use of its own transactions, you can update it to allow conditional transactions.
Also, when building complex transactions, an app benefits a lot from using the repository patterns shown in pg-promise-demo. You can have methods inside repositories that support conditional transactions.
And you should redo your code to avoid horrible things it does, like manual query formatting. For example, never use things like SELECT ${Object.keys(params).join()}, that's a recipe for disaster. Use the proper query formatting that pg-promise gives you, like SQL Names in this case.
I have several repos with methods and some of these methods use transaction (.tx).
For example, in my DevicesRepository below, the 'add' method have to insert a new Device, which means:
1. Insert a System and return the ID (SystemsRepository)
2. insert the device with the returner systemId and get the new id
3. Insert other pieces (other repos) that uses the deviceId
My problem is that in that transaction I don't know how to access to the other repo methods.
I could use the other repos from my Database object (Database.systems.add, Database.OtherRepo.add, [...]), but if I do that
tx doc
When invoked on the root Database object, the method allocates the connection from the pool, executes the callback, and once finished - releases the connection back to the pool. However, when invoked inside another task or transaction, the method reuses the parent connection.
task doc
When executing more than one request at a time, one should allocate and release the connection only once, while executing all the required queries within the same connection session. More importantly, a transaction can only work within a single connection.
Thanks! :)
P.S : I can add how I initialize the DB and repos
./db/repos/devices.js
'use strict';
var Database = null, pgp = null, Collections = null;
async function add(params) {
// I can use Database.systems.add
return Database.tx('Insert-New-Device', async function(transaction) {
let device = params.data.device;
const system = await transaction.systems.add(params);
device.systemid = system.systemId;
const query = pgp.helpers.insert(device, Collections.insert);
query += " RETURNING deviceId";
device.deviceId = await transaction.one(query);
const OtherRepoInsert = await transaction.otherRepos.add(params);
device.otherRepos.id = OtherRepoInsert.otherReposId;
return device
})
.then(data => { return data; })
.catch(ex => { throw new Error(ex); });
}
function createColumnsets() { /* hidden for brevity (almost the same as the pg-promise-demo */ }
const DevicesRepository = {
add: add
};
module.exports = (db) => {
Database = db;
pgp = db.$config.pgp;
Collections = createColumnsets();
return DevicesRepository;
}
./db/repos/systems.js
'use strict';
var Database = null, pgp = null, Collections = null;
async function add(params) {
var system = params.data.system;
system.archid=2;
system.distributionid=3;
var query = pgp.helpers.insert(system, Collections.insert);
if(params.return) query += " RETURNING *";
return Database.any(query)
.then(data => { return data; })
.catch(ex => { throw new Error(ex); });
}
function createColumnsets() { /* hidden for brevity (almost the same as the pg-promise-demo */ }
const SystemsRepository = {
add: add
};
module.exports = (db) => {
Database = db;
pgp = db.$config.pgp;
Collections = createColumnsets();
return SystemsRepository;
}
I found the real problem.
If you go to my first post, you can see that each of my repo exports an initialization function :
1. which is called by the pg-promise 'extend' event
2. which takes one param : the context
3. which uses this param to initialize the 'pgp' variable in the repo with db.$config.pgp
As explained in the demo, this event occurs when the db is loaded for the first time in the appl and for every task and transaction.
In my case :
The first time the event occurs (full app initialization), the event's param 'obj' is the database context (containing $config, $pool, ...) so it works
When the event occurs for a task or transaction, the event's param 'obj' is a Task context, where $config does not exists so the event can not extend the context with my repo. An exception 'can not read property helpers of undefined' is thrown but does not appear and does not crash my app, I don't know why, maybe catched in the event. That is why I could not use my repo in the transaction.
I modified my code like it and it works :
./db/index.js
'use strict';
/* hidden for brevity */
// pg-promise initialization options:
const initOptions = {
promiseLib: promise,
extend(obj, dc) {
obj.roles = repos.Roles(obj, pgp);
obj.shells = repos.Shells(obj, pgp);
obj.systems = repos.Systems(obj, pgp);
obj.devices = repos.Devices(obj, pgp);
}
};
const pgp = require('pg-promise')(initOptions);
const db = pgp(config);
/* hidden for brevity */
./db/index.js
'use strict';
/* hidden for brevity */
// pg-promise initialization options:
const initOptions = {
promiseLib: promise,
extend(obj, dc) {
obj.roles = repos.Roles(obj, pgp);
obj.shells = repos.Shells(obj, pgp);
obj.systems = repos.Systems(obj, pgp);
obj.devices = repos.Devices(obj, pgp);
}
};
const pgp = require('pg-promise')(initOptions);
const db = pgp(config);
/* hidden for brevity */
./db/repos/{repoFiles}.js
/* hidden for brevity */
module.exports = (db, pgpLib) => {
Database = db;
pgp = pgpLib;
Collections = createColumnsets();
return DevicesRepository;
}
Property $config is there for integration purposes. That's why it exists only on the root Database level, and not inside tasks or transactions.
In order to make use of the helpers namespace, you should pass pgp into repositories when you initialize them, as shown within pg-promise-demo:
extend(obj, dc) {
obj.users = new repos.Users(obj, pgp);
obj.products = new repos.Products(obj, pgp);
}
You can establish the transaction outside the calls, then pass it in to those functions to have them use it.
That said, I'd recommend looking into a slightly higher-level query builder library such as Knex.js to save you from some of these (and future) headaches.
I am trying to write a query processor function in Hyperledger Composer. However, it is just returning an empty array '[]' and I am not sure why. When doing a GET request from 'org.land.Deal'. I have 2 items being returned. But from the query processor function, only the blank array is being returned.
Here is the transaction function:
/**
* Get Live Deals
* #param {org.land.GetLiveDeals} getLiveDeals - the transaction
* #transaction
*/
async function getLiveDeals(){
const liveDeals = [];
const dealRegistry = await getAssetRegistry('org.land.Deal');
const allDeals = await assetRegistry.getAll();
for(const deal of allDeals){
liveDeals.push(deal);
}
return liveDeals;
}
Here is the transaction model:
#commit(false)
#returns(Deal[])
transaction GetLiveDeals {
}
I hope this change will work out;
const allDeals = await dealRegistry.getAll();
For more info please do refer - Composer getAll()