When I want to declare an error in my handler, it causes an infinite loop in my processor which displays in the console :
{ InvalidTransaction: InvalidTransaction: The campaign does not exist!
at VoteHandler.apply (/app/handler.js:79:19)
at <anonymous> name: 'InvalidTransaction' }
"The campaign does not exist!" being my error message.
Example :
const { TransactionHandler } = require('sawtooth-sdk/processor/handler');
const { InvalidTransaction } = require('sawtooth-sdk/processor/exceptions');
class VoteHandler extends TransactionHandler {
constructor() {
super(FAMILY_NAME, ['1.0'], [NAMESPACE]);
}
// apply function - execution starts here when a transaction reaches the validator for this transaction processsor
async apply(batches, context) {
var request = JSON.parse(decoder.decode(batches.payload));
let campaign = {};
let address = NAMESPACE + hash(request.idCampaign).substring(0, 14) + hash(batches.header.signerPublicKey).substring(0, 50);
switch(request.type){
case "INITIALIZE":
await context.getState([address]).then((addressValues) => {
if(addressValues[address].length == 0){
console.log("Create the campaign!");
}else{
throw new InvalidTransaction("The campaign already exists!");
}
}).catch((err) => {
throw new InvalidTransaction("Error when initializing the campaign!");
});
break;
case "NEW_VOTE":
await context.getState([address]).then((addressValues) => {
if(addressValues[address].length == 0){
throw new InvalidTransaction("The campaign doesn't exist, you can't vote!");
}else{
console.log("Vote taken into account!");
}
}).catch((err) => {
throw new InvalidTransaction("The campaign does not exist!");
});
break;
default:
throw new InvalidTransaction("The type of action is not defined!");
}
console.log('==============State==============');
console.log(campaign);
console.log('=============Message=============');
console.log(request);
console.log('=================================');
if(Object.keys(campaign).length !== 0){
return context.setState({
[address]: encoder.encode(JSON.stringify(campaign))
});
}
}
}
I develop my processor in JS and I could see code examples in java, js, python, ... They always do the same thing, that is to say simply declare the exception by importing it from the sawtooth-sdk package (sawtooth-sdk/processor/exceptions).
Java :
throw new InvalidTransactionException("Version does not match");
LINK : Daml Transaction Java
Python :
raise InvalidTransaction('Invalid action: Take requires an existing game')
LINK : Sawtooth xo pyton
JS :
throw new InternalError('State Error!')
LINK : Simple Wallet JS
I also tried to get the exceptions from the package #restroom-mw/sawtooth-sdk/ but the error is still there.
IMAGE : Import exceptions
Normally my error report should return an error code with my error message to my client.
Related
is it a good practice at all and if yes what is the correct way to break a transaction with different error states/messages for different situations?
I have a transaction running over an 'offer' entry doing 'seats' reservation:
I want to break it if one of the following 3 conditions is true and return state/message to the caller function.
if the asking user already made a reservation of seats of this offer.
if there is not enought seats.
if this offer does not exists.
And if everything is ok the transaction should complete normally and return state/message to the caller function that reservation is made.
I'm not sure how to break the transaction in case of one of the conditions is true.
if I use throw new Error('description of the problem.') then this will be an Exception and it's not handled by catch() handler of the transaction Promise
and I'm not sure how to handle this exception because it's an asynchronous function here.
so I think i should not use an exception.
Here is what I mean:
dealSeats = function(entryRef, data) {
const TAG = '[dealSeats]: ';
return entryRef.transaction((entry)=>{
if (entry) {
if ((entry.deals) && (entry.deals[data.uid])) {
**? how to break the transaction with state/message 'You already have a deal.' ? and how to handle it below ?**
} else if (entry.details.seatsCount >= data.details.seatsCount) {
entry.details.seatsCount -= data.details.seatsCount;
var deal = [];
deal.status = 'asked';
deal.details = data.details;
if (!entry.deals) {
entry.deals = {};
}
entry.deals[data.uid] = deal;
} else {
**? how to break the transaction with state/message 'Not enought seats.' ? and how to handle it below ?**
}
}
return entry;
**? how to check if 'entry' is really null ? i.e. offer does not exists ?** and break and handle it.
})
.then((success)=>{
return success.snapshot.val();
})
.catch((error)=>{
return Promise.reject(error);
});
}
here is my data in realtime database:
activeOffers
-LKohyZ58cnzn0vCnt9p
details
direction: "city"
seatsCount: 2
timeToGo: 5
uid: "-ABSIFJ0vCnt9p8387a" ---- offering user
here is my test data sent by Postman:
{
"data":
{
"uid": "-FGKKSDFGK12387sddd", ---- the requesting/asking user
"id": "-LKpCACQlL25XTWJ0OV_",
"details":
{
"direction": "city",
"seatsCount": 1,
"timeToGo": 5
}
}
}
==== updated with final source ====
many thanks to Renaud Tarnec!
So here is my final source that is working fine. If someone sees a potential problem please let me know. Thanks.
dealSeats = function(entryRef, data) {
const TAG = '[dealSeats]: ';
var abortReason;
return entryRef.transaction((entry)=>{
if (entry) {
if ((entry.deals) && (entry.deals[data.uid])) {
abortReason = 'You already made a reservation';
return; // abort transaction
} else if (entry.details.seatsCount >= data.details.seatsCount) {
entry.details.seatsCount -= data.details.seatsCount;
var deal = [];
deal.status = 'asked';
deal.details = data.details;
if (!entry.deals) {
entry.deals = {};
}
entry.deals[data.uid] = deal;
// Reservation is made
} else {
abortReason = 'Not enought seats';
return; // abort transaction
}
}
return entry;
})
.then((result)=>{ // resolved
if (!result.committed) { // aborted
return abortReason;
} else {
let value = result.snapshot.val();
if (value) {
return value;
} else {
return 'Offer does not exists';
}
}
})
.catch((reason)=>{ // rejected
return Promise.reject(reason);
});
}
The only pain is a warning during deploy in VSCode terminal about this abortions by returning no value:
warning Arrow function expected no return value consistent-return
currently I'm not sure if I could do anything about it.
Look at this doc in the Firebase API Reference documentation: https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
Below is the code from this doc. Look how return; is used to abort the transaction (The doc also says: "you abort the transaction by not returning a value from your update function"). And note how this specific case is handled in the onComplete() callback function that is called when the transaction completes (within else if (!committed){} ).
// Try to create a user for ada, but only if the user id 'ada' isn't
// already taken
var adaRef = firebase.database().ref('users/ada');
adaRef.transaction(function(currentData) {
if (currentData === null) {
return { name: { first: 'Ada', last: 'Lovelace' } };
} else {
console.log('User ada already exists.');
return; // Abort the transaction.
}
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('We aborted the transaction (because ada already exists).');
} else {
console.log('User ada added!');
}
console.log("Ada's data: ", snapshot.val());
});
So IMHO you should adopt the same pattern and at the places in your code where you ask "**? how to break the transaction" you do return;.
Update: You can differentiate the abortion cases by using a variable, as follows. If you add, via the Firebase console, a node age with value > 20 to users.ada.name, the first abortion cause will be "triggered".
var adaRef = firebase.database().ref('users/ada');
var transactionAbortionCause; //new variable
adaRef.transaction(function(currentData) {
if (currentData === null) {
return { name: { first: 'Ada', last: 'Lovelace' } };
} else if (currentData.name.age > 20) {
transactionAbortionCause = 'User ada is older than 20'; //update the variable
console.log('User ada is older than 20');
return; // Abort the transaction.
} else {
transactionAbortionCause = 'User ada already exists'; //update the variable
console.log('User ada already exists');
return; // Abort the transaction.
}
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('We aborted the transaction because ' + transactionAbortionCause); //use the variable
} else {
console.log('User ada added!');
}
console.log("Ada's data: ", snapshot.val());
});
If I am not mistaking, you could also do that with promises, as you do in your code. The doc says that the transaction returns a non-null firebase.Promise containing {committed: boolean, snapshot: nullable firebase.database.DataSnapshot} and explains that this promise "can optionally be used instead of the onComplete callback to handle success and failure".
So by:
Doing return; for your two cases of abortion, and
Reading the value of the committed boolean
you should be able to handle the abortion cases in your code by doing
.then((result)=>{
if (result.commited) {... } else { /*abortion!*/}
})
I have not tested this approach however
I submitted a transaction to Hyperledger Fabric, but I'd like to get object created by this.
The object that I get from this is Undefined.
Obs: The transaction is successfully created in Hyperledger Fabric.
async submit(resource, method) {
try{
this.businessNetworkDefinition = await this.bizNetworkConnection.connect(cardname);
if (!this.businessNetworkDefinition) {
console.log("Error in network connection");
throw "Error in network connection";
}
let factory = this.businessNetworkDefinition.getFactory();
let transaction = factory.newTransaction(NS, method);
Object.assign(transaction, resource)
return await this.bizNetworkConnection.submitTransaction(transaction);
}catch(error){
console.log(error);
throw error;
}
}
Currently the submitTransaction function is not returning anything. It is a bug or working as intended.
To go into more detail: When you delve through the source code of the composer you will finally get to the following code in composer-connector-hlfv1.
invokeChainCode(securityContext, functionName, args, options) {
const method = 'invokeChainCode';
LOG.entry(method, securityContext, functionName, args, options);
if (!this.businessNetworkIdentifier) {
return Promise.reject(new Error('No business network has been specified for this connection'));
}
// Check that a valid security context has been specified.
HLFUtil.securityCheck(securityContext);
// Validate all the arguments.
if (!functionName) {
return Promise.reject(new Error('functionName not specified'));
} else if (!Array.isArray(args)) {
return Promise.reject(new Error('args not specified'));
}
try {
args.forEach((arg) => {
if (typeof arg !== 'string') {
throw new Error('invalid arg specified: ' + arg);
}
});
} catch(error) {
return Promise.reject(error);
}
let txId = this._validateTxId(options);
let eventHandler;
// initialize the channel if it hasn't been initialized already otherwise verification will fail.
LOG.debug(method, 'loading channel configuration');
return this._initializeChannel()
.then(() => {
// check the event hubs and reconnect if possible. Do it here as the connection attempts are asynchronous
this._checkEventhubs();
// Submit the transaction to the endorsers.
const request = {
chaincodeId: this.businessNetworkIdentifier,
txId: txId,
fcn: functionName,
args: args
};
return this.channel.sendTransactionProposal(request); // node sdk will target all peers on the channel that are endorsingPeer
})
.then((results) => {
// Validate the endorsement results.
LOG.debug(method, `Received ${results.length} result(s) from invoking the composer runtime chaincode`, results);
const proposalResponses = results[0];
let {validResponses} = this._validatePeerResponses(proposalResponses, true);
// Submit the endorsed transaction to the primary orderers.
const proposal = results[1];
const header = results[2];
// check that we have a Chaincode listener setup and ready.
this._checkCCListener();
eventHandler = HLFConnection.createTxEventHandler(this.eventHubs, txId.getTransactionID(), this.commitTimeout);
eventHandler.startListening();
return this.channel.sendTransaction({
proposalResponses: validResponses,
proposal: proposal,
header: header
});
})
.then((response) => {
// If the transaction was successful, wait for it to be committed.
LOG.debug(method, 'Received response from orderer', response);
if (response.status !== 'SUCCESS') {
eventHandler.cancelListening();
throw new Error(`Failed to send peer responses for transaction '${txId.getTransactionID()}' to orderer. Response status '${response.status}'`);
}
return eventHandler.waitForEvents();
})
.then(() => {
LOG.exit(method);
})
.catch((error) => {
const newError = new Error('Error trying invoke business network. ' + error);
LOG.error(method, newError);
throw newError;
});
}
As you can see at the end, all that is happening is waiting for Events and Log.exit which return nothing. So currently you have to get your transaction result in another way.
The only way I could get something from my chaincode is through events. There's native interface that might be able to query for transaction data or something like this, but i haven't looked into it yet.
I'm trying to implement unit test for the following piece of my code
try {
return await request.post(options);
} catch (err) {
if (err.statusCode === 401) {
log.info('Not authenticated. Refreshing token...');
const tokenResponse =
await Janus.refreshToken(graph.username, graph.password, graph.host, graph.port);
const token = tokenResponse.body.token;
graph.token = token;
return gremlinQuery(graph, query);
}
log.error(`Gremlin script didn't pass : ${err}`);
}
In order to test the lines contained in the catch part, I stub the post function:
stubPost.callsFake(() => Promise.reject(new Error()));
How can I implement an error with the property statusCode? Error constructor is waiting for a string as an input. Can't I pass it an object or something like that?
One way to do this could be as below
MyApiError = function(data) {
this.code = data.code;
this.message = data.message
}
MyApiError.prototype = Error.prototype;
var e = new MyApiError({code: 33, message: 'test'});
e.code; // 33
e.message; // 'test'
I'm trying to start a windows service from a node script. This service has a bad habit of hanging and sometimes requires a retry to start successfully. I have a promise while loop setup (Please feel free to suggest a better way). The problem I'm having, is with each loop the sc.pollInterval output writes duplicate results in the console. Below is an example of the duplicate content I see in the console, this is after the second iteration in the loop, i'd like it to only display that content once.
sc \\abnf34873 start ColdFusion 10 Application Server
sc \\abnf34873 queryex ColdFusion 10 Application Server
SERVICE_NAME: ColdFusion 10 Application Server
TYPE : 10 WIN32_OWN_PROCESS
STATE : 2 START_PENDING
(NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x7d0
PID : 0
FLAGS :
SERVICE_NAME: ColdFusion 10 Application Server
TYPE : 10 WIN32_OWN_PROCESS
STATE : 2 START_PENDING
(NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x7d0
PID : 13772
FLAGS :
Here is the code I have. Basically, I'm going to try to start the service 3 times. If it doesn't, then I throw and error. One thing to note, when I attempt to start the service, but it's stuck in 'Start_pending' state, I kill the process and then try to start it again.
var retryCount = 0;
// Start the colfusion service
gulp.task('start-coldfusion-service', function(done) {
var serviceStarted = false;
console.log("Starting coldfusion service..");
// This says we're going to ask where it's at every 30 seconds until it's in the desired state.
sc.pollInterval(30);
sc.timeout(60);
retryCount = 0;
tryServiceStart().then(function(result) {
// process final result here
done();
}).catch(function(err) {
// process error here
});
});
function tryServiceStart() {
return startService().then(function(serviceStarted) {
if (serviceStarted == false) {
console.log("Retry Count: " + retryCount);
// Try again..
return tryServiceStart();
} else {
return result;
}
});
}
function startService() {
return new Promise(function(resolve, reject) {
var started = true;
// Make sure the coldfusion service exists on the target server
sc.query(targetServer, { name: 'ColdFusion 10 Application Server'}).done(function(services) {
// if the service exists and it is currentl stopped, then we're going to start it.
if (services.length == 1) {
var pid = services[0].pid;
if (services[0].state.name == 'STOPPED') {
sc.start(targetServer, 'ColdFusion 10 Application Server')
.catch(function(error) {
started = false;
console.log("Problem starting Coldfusion service! error message: " + error.message);
console.log("retrying...");
retryCount++;
if (parseInt(retryCount) > 2) {
throw Error(error.message);
}
})
.done(function(displayName) {
if (started) {
console.log('Coldfusion service started successfully!');
}
resolve(started);
});
} else if (services[0].state.name == 'START_PENDING') {
kill(pid, {force: true}).catch(function (err) {
console.log('Problem killing process..');
}).then(function() {
console.log('Killed hanging process..');
resolve(false);
});
}
} else {
console.log("Could not find the service in a stopped state.");
resolve(false);
}
});
});
}
Not too sure why you get duplicate results in the console, however below are some ideas on how the code might be better written, chiefly by promisifying at the lowest level.
Sticking fairly closely to the original concept, I ended up with this ...
Promisify sc commands
sc commands return something which is promise-like but with a .done() method that does not, in all probability, possess the full power of a genuine .then()
promisify each command as .xxxAsync()
by adopting each command's .done as .then, Promise.resolve() should be able to assimilate the promise-like thing returned by the command.
;(function() {
commands.forEach(command => {
sc[command].then = sc[command].done;
sc[command + 'Async'] = function() {
return Promise.resolve(sc[command](...arguments));
};
}).
}(['start', 'query'])); // add other commands as required
gulp.task()
promise chain follows its success path if service was opened, otherwise its error path
no need to test a result to detect error conditions in the success path.
gulp.task('start-coldfusion-service', function(done) {
console.log('Starting coldfusion service..');
// This says we're going to ask where it's at every 30 seconds until it's in the desired state.
sc.pollInterval(30);
sc.timeout(60);
tryServiceStart(2) // tryServiceStart(maxRetries)
.then(done) // success! The service was started.
.catch(function(err) {
// the only error to end up here should be 'Maximum tries reached'.
console.err(err);
// process error here if necessary
});
});
tryServiceStart()
orchestrate retries here
function tryServiceStart(maxRetries) {
return startService()
// .then(() => {}) // success! No action required here, just stay on the success path.
.catch((error) => {
// all throws from startService() end up here
console.error(error); // log intermediate/final error
if(--maxRetries > 0) {
return tryServiceStart();
} else {
throw new Error('Maximum tries reached');
}
});
}
startService()
form a fully capable promise chain by calling the promisified versions of sc.query() and sc.start()
console.log() purged in favour of throwing.
thrown errors will be caught and logged back in tryServiceStart()
function startService() {
// Make sure the coldfusion service exists on the target server
return sc.queryAsync(targetServer, { name: 'ColdFusion 10 Application Server'})
.then((services) => {
// if the service exists and it is currently stopped, then start it.
if (services.length == 1) {
switch(services[0].state.name) {
case 'STOPPED':
return sc.startAsync(targetServer, 'ColdFusion 10 Application Server')
.catch((error) => {
throw new Error("Problem starting Coldfusion service! error message: " + error.message);
});
break;
case 'START_PENDING':
return kill(services[0].pid, { 'force': true })
.then(() => {
throw new Error('Killed hanging process..'); // successful kill but still an error as far as startService() is concerned.
})
.catch((err) => {
throw new Error('Problem killing process..');
});
break;
default:
throw new Error("Service not in a stopped state.");
}
} else {
throw new Error('Could not find the service.');
}
});
}
Checked only for syntax error, so may well need debugging.
Offered FWIW. Feel free to adopt/raid as appropriate.
I have found another npm package called promise-retry that seems to have addressed the issue I was having. At the same time, I believe it made my code a little more clear as to what it's doing.
gulp.task('start-coldfusion-service', function(done) {
var serviceStarted = false;
console.log("Starting coldfusion service..");
// Since starting a service on another server isn't exactly fast, we have to poll the status of it.
// This says we're going to ask where it's at every 30 seconds until it's in the desired state.
sc.pollInterval(30);
sc.timeout(60);
promiseRetry({retries: 3}, function (retry, number) {
console.log('attempt number', number);
return startService()
.catch(function (err) {
console.log(err);
if (err.code === 'ETIMEDOUT') {
retry(err);
} else if (err === 'killedProcess') {
retry(err);
}
throw Error(err);
});
})
.then(function (value) {
done();
}, function (err) {
console.log("Unable to start the service after 3 tries!");
process.exit();
});
});
function startService() {
var errorMsg = "";
return new Promise(function(resolve, reject) {
var started = true;
// Make sure the coldfusion service exists on the target server
sc.query(targetServer, { name: 'ColdFusion 10 Application Server'}).done(function(services) {
// if the service exists and it is currentl stopped, then we're going to start it.
if (services.length == 1) {
var pid = services[0].pid;
if (services[0].state.name == 'STOPPED') {
sc.start(targetServer, 'ColdFusion 10 Application Server')
.catch(function(error) {
started = false;
errorMsg = error;
console.log("Problem starting Coldfusion service! error message: " + error.message);
console.log("retrying...");
})
.done(function(displayName) {
if (started) {
console.log('Coldfusion service started successfully!');
resolve(started);
} else {
reject(errorMsg);
}
});
} else if (services[0].state.name == 'START_PENDING') {
kill(pid, {force: true}).catch(function (err) {
console.log('Problem killing process..');
}).then(function() {
console.log('Killed hanging process..');
reject("killedProcess");
});
} else {
// Must already be started..
resolve(true);
}
} else {
console.log("Could not find the service in a stopped state.");
resolve(false);
}
});
});
}
I have the following code that is throwing me some firebase exception in the console if the data that I want to save to firebase is invalid.
I want to catch it and display it to the screen in a controlled manner rather than finding out from console.
I dont know why my .catch is not catching any of the firebase exceptions?
this.databaseService.saveCodesToFirebase(jsonFromCsv)
.then(result => {
this.alertService.alertPopup('Success', 'Code Updated')
})
.catch(error => {
this.errorMessage = 'Error - ' + error.message
})
saveCodesToFirebase(myObj: Object) {
let ref = firebase.database().ref();
let path = this.userService.getCurrentUser().companyId + '/codes/'
let lastUpdatedPath = this.userService.getCurrentUser().companyId + '/lastUpdated/';
var updates = {}
updates[path] = jobObject;
updates[lastUpdatedPath] = Math.round(new Date().getTime() / 1000);
return ref.child('codes').update(updates);
}
EXCEPTION: Firebase.update failed: First argument contains an invalid key () in property 'codes.apple20170318.codes'. Keys must be
non-empty strings and can't contain ".", "#", "$", "/", "[", or "]"
There's not much to go on here but my best guess is that the object you're passing to saveCodesToFirebase() has keys that contain dots in them, like the one shown in the error message: jobCodes.apple20170318.codes.
If you want to keep this model you will have to sanitize that object to replace any invalid characters in its keys (and its children keys, recursively) before doing the update() operation.
When it comes to catching the exception, you'll have to use a try/catch block. The .catch() attached to the promise in this case is only useful to detect errors returned by the server, but here it's the update() method itself the one synchronously throwing the exception.
One possible approach would be like this:
try {
this.databaseService.saveCodesToFirebase(jsonFromCsv)
.then(result => {
this.alertService.alertPopup('Success', 'Code Updated')
})
.catch(error => {
this.errorMessage = 'Error - ' + error.message
})
} catch (error) {
this.errorMessage = 'Error - ' + error.message
}
So, in Javascript you usually should have just one catch clause per try statement. You could achieve what you want using the syntax below:
try {
// some code
} catch (err) {
if( err istanceof FirebaseError ) {
this.errorMessage = 'Error - ' + err.message;
} else {
this.errorMessage = 'Error - Generic error';
}
}
you can find more information here in the section Conditional catch-blocks