From 2ada16e2223fa8f1ff9c13550ea5c4eec5306fdc Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 19 Oct 2012 21:14:23 -0700 Subject: [PATCH 1/8] Change OfferCancel to succeed if offer not found. --- src/TransactionAction.cpp | 36 ++++++++++++++++++++++++------------ src/TransactionErr.cpp | 2 +- src/TransactionErr.h | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/TransactionAction.cpp b/src/TransactionAction.cpp index 81b9fe85d..c4aa817c9 100644 --- a/src/TransactionAction.cpp +++ b/src/TransactionAction.cpp @@ -1136,24 +1136,37 @@ Log(lsINFO) << boost::str(boost::format("doOfferCreate: saTakerPays=%s saTakerGe TER TransactionEngine::doOfferCancel(const SerializedTransaction& txn) { TER terResult; - const uint32 uSequence = txn.getFieldU32(sfOfferSequence); - const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + const uint32 uOfferSequence = txn.getFieldU32(sfOfferSequence); + const uint32 uAccountSequenceNext = mTxnAccount->getFieldU32(sfSequence); - if (sleOffer) + Log(lsDEBUG) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; + + if (!uOfferSequence || uAccountSequenceNext-1 <= uOfferSequence) { - Log(lsWARNING) << "doOfferCancel: uSequence=" << uSequence; + Log(lsINFO) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; - terResult = mNodes.offerDelete(sleOffer, uOfferIndex, mTxnAccountID); + terResult = temBAD_SEQUENCE; } else { - Log(lsWARNING) << "doOfferCancel: offer not found: " - << NewcoinAddress::createHumanAccountID(mTxnAccountID) - << " : " << uSequence - << " : " << uOfferIndex.ToString(); + const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uOfferSequence); + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - terResult = terOFFER_NOT_FOUND; + if (sleOffer) + { + Log(lsWARNING) << "doOfferCancel: uOfferSequence=" << uOfferSequence; + + terResult = mNodes.offerDelete(sleOffer, uOfferIndex, mTxnAccountID); + } + else + { + Log(lsWARNING) << "doOfferCancel: offer not found: " + << NewcoinAddress::createHumanAccountID(mTxnAccountID) + << " : " << uOfferSequence + << " : " << uOfferIndex.ToString(); + + terResult = tesSUCCESS; + } } return terResult; @@ -1179,7 +1192,6 @@ TER TransactionEngine::doContractAdd(const SerializedTransaction& txn) // place contract in ledger // run create code - if (mLedger->getParentCloseTimeNC() >= expiration) { diff --git a/src/TransactionErr.cpp b/src/TransactionErr.cpp index f85f0cda4..9f50b4d3a 100644 --- a/src/TransactionErr.cpp +++ b/src/TransactionErr.cpp @@ -33,6 +33,7 @@ bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) { temBAD_PUBLISH, "temBAD_PUBLISH", "Malformed: Bad publish." }, { temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" }, { temBAD_SET_ID, "temBAD_SET_ID", "Malformed." }, + { temBAD_SEQUENCE, "temBAD_SEQUENCE", "Malformed: Sequence in not in the past." }, { temCREATEXNS, "temCREATEXNS", "Can not specify non XNS for Create." }, { temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." }, { temDST_NEEDED, "temDST_NEEDED", "Destination not specified." }, @@ -53,7 +54,6 @@ bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) { terNO_DST, "terNO_DST", "The destination does not exist." }, { terNO_LINE, "terNO_LINE", "No such line." }, { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, - { terOFFER_NOT_FOUND, "terOFFER_NOT_FOUND", "Can not cancel offer." }, { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction." }, { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, diff --git a/src/TransactionErr.h b/src/TransactionErr.h index e2aa0e443..f672caf7f 100644 --- a/src/TransactionErr.h +++ b/src/TransactionErr.h @@ -34,6 +34,7 @@ enum TER // aka TransactionEngineResult temBAD_PATH_LOOP, temBAD_PUBLISH, temBAD_TRANSFER_RATE, + temBAD_SEQUENCE, temBAD_SET_ID, temCREATEXNS, temDST_IS_SRC, @@ -83,7 +84,6 @@ enum TER // aka TransactionEngineResult terNO_DST, terNO_LINE, terNO_LINE_NO_ZERO, - terOFFER_NOT_FOUND, // XXX If we check sequence first this could be hard failure. terPRE_SEQ, terSET_MISSING_DST, terUNFUNDED, From 5d9fdae5cf3c37415eca8b2b4e1c279bbd256a79 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 19 Oct 2012 21:15:22 -0700 Subject: [PATCH 2/8] JS: Fix UInt160 to use config. --- js/amount.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/js/amount.js b/js/amount.js index faac4e3de..36d57889b 100644 --- a/js/amount.js +++ b/js/amount.js @@ -19,10 +19,14 @@ var UInt160 = function () { this.value = NaN; }; +UInt160.json_rewrite = function (j) { + return UInt160.from_json(j).to_json(); +}; + // Return a new UInt160 from j. UInt160.from_json = function (j) { return 'string' === typeof j - ? (new UInt160()).parse_json(j in accounts ? accounts[j].account : j) + ? (new UInt160()).parse_json(j) : j.clone(); }; @@ -40,6 +44,8 @@ UInt160.prototype.copyTo = function(d) { // value = NaN on error. UInt160.prototype.parse_json = function (j) { // Canonicalize and validate + if (j in accounts) + j = accounts[j].account; switch (j) { case undefined: @@ -163,6 +169,10 @@ var Amount = function () { this.issuer = new UInt160(); }; +Amount.json_rewrite = function(j) { + return Amount.from_json(j).to_json(); +}; + Amount.from_json = function(j) { return (new Amount()).parse_json(j); }; From 083e78e124602a0b47eaf0bdfbda5f40007a6811 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 19 Oct 2012 21:16:09 -0700 Subject: [PATCH 3/8] JS: Add offer_cancel. --- js/remote.js | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/js/remote.js b/js/remote.js index 3f1f85517..4f57c5c3a 100644 --- a/js/remote.js +++ b/js/remote.js @@ -90,7 +90,7 @@ Request.prototype.ledger_index = function (ledger_index) { }; Request.prototype.account_root = function (account) { - this.message.account_root = UInt160.from_json(account).to_json(); + this.message.account_root = UInt160.json_rewrite(account); return this; }; @@ -168,7 +168,9 @@ var Remote = function (trusted, websocket_ip, websocket_port, config, trace) { // Cache for various ledgers. // XXX Clear when ledger advances. this.ledgers = { - 'current' : {} + 'current' : { + 'account_root' : {} + } }; }; @@ -626,6 +628,8 @@ Remote.prototype._server_subscribe = function () { // Ask the remote to accept the current ledger. // - To be notified when the ledger is accepted, server_subscribe() then listen to 'ledger_closed' events. +// A good way to be notified of the result of this is: +// remote.once('ledger_closed', function (ledger_closed, ledger_closed_index) { ... } ); Remote.prototype.ledger_accept = function () { if (this.stand_alone || undefined === this.stand_alone) { @@ -655,12 +659,13 @@ Remote.prototype.request_account_balance = function (account, current) { // Return the next account sequence if possible. // <-- undefined or Sequence Remote.prototype.account_seq = function (account, advance) { - var account_info = this.accounts[account]; + var account = UInt160.json_rewrite(account); + var account_info = this.accounts[account]; var seq; if (account_info && account_info.seq) { - var seq = account_info.seq; + seq = account_info.seq; if (advance) account_info.seq += 1; } @@ -668,6 +673,14 @@ Remote.prototype.account_seq = function (account, advance) { return seq; } +Remote.prototype.set_account_seq = function (account, seq) { + var account = UInt160.json_rewrite(account); + + if (!this.accounts[account]) this.accounts[account] = {}; + + this.accounts[account].seq = seq; +} + // Return a request to refresh accounts[account].seq. Remote.prototype.account_seq_cache = function (account, current) { var self = this; @@ -690,7 +703,7 @@ Remote.prototype.account_seq_cache = function (account, current) { // Mark an account's root node as dirty. Remote.prototype.dirty_account_root = function (account) { - delete this.ledgers.current.account_root[account]; + delete this.ledgers.current.account_root[UInt160.json_rewrite(account)]; }; // Return a request to get a ripple balance. @@ -802,6 +815,7 @@ var Transaction = function (remote) { self.set_state('client_proposed'); self.emit('proposed', { + 'transaction' : message.transaction, 'result' : message.engine_result, 'result_code' : message.engine_result_code, 'result_message' : message.engine_result_message, @@ -999,13 +1013,23 @@ Transaction.prototype.account_secret = function (account) { return this.remote.config.accounts[account] ? this.remote.config.accounts[account].secret : undefined; }; +Transaction.prototype.offer_cancel = function (src, sequence) { + this.secret = this.account_secret(src); + this.transaction.TransactionType = 'OfferCancel'; + this.transaction.Account = UInt160.from_json(src).to_json(); + this.transaction.OfferSequence = Number(sequence); + + return this; +}; + +// XXX Expiration should use a time. Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expiration) { this.secret = this.account_secret(src); this.transaction.TransactionType = 'OfferCreate'; this.transaction.Account = UInt160.from_json(src).to_json(); this.transaction.Fee = fees.offer.to_json(); - this.transaction.TakerPays = taker_pays.to_json(); - this.transaction.TakerGets = taker_gets.to_json(); + this.transaction.TakerPays = Amount.json_rewrite(taker_pays); + this.transaction.TakerGets = Amount.json_rewrite(taker_gets); if (expiration) this.transaction.Expiration = expiration; @@ -1024,7 +1048,7 @@ Transaction.prototype.payment = function (src, dst, deliver_amount) { this.secret = this.account_secret(src); this.transaction.TransactionType = 'Payment'; this.transaction.Account = UInt160.from_json(src).to_json(); - this.transaction.Amount = Amount.from_json(deliver_amount).to_json(); + this.transaction.Amount = Amount.json_rewrite(deliver_amount); this.transaction.Destination = UInt160.from_json(dst).to_json(); return this; From c3ebbf1f777dbd26018759c0b86d1f222b2167b3 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 19 Oct 2012 21:17:03 -0700 Subject: [PATCH 4/8] Cosmetic. --- src/SerializeProto.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SerializeProto.h b/src/SerializeProto.h index f7d46a50f..1a7b67641 100644 --- a/src/SerializeProto.h +++ b/src/SerializeProto.h @@ -151,3 +151,5 @@ FIELD(Template, ARRAY, 5) FIELD(Necessary, ARRAY, 6) FIELD(Sufficient, ARRAY, 7) + +// vim:ts=4 From 3691def75e209c7c3202bce9d5834a8f23533cdc Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 19 Oct 2012 21:32:01 -0700 Subject: [PATCH 5/8] UT: Add offer-test.js --- test/offer-test.js | 306 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 test/offer-test.js diff --git a/test/offer-test.js b/test/offer-test.js new file mode 100644 index 000000000..a47468ba4 --- /dev/null +++ b/test/offer-test.js @@ -0,0 +1,306 @@ + +var async = require("async"); +var buster = require("buster"); +var fs = require("fs"); + +var server = require("./server.js"); +var remote = require("../js/remote.js"); +var config = require("./config.js"); + +var Amount = require("../js/amount.js").Amount; + +require("../js/amount.js").setAccounts(config.accounts); + +buster.testRunner.timeout = 5000; + +var alpha; + +// success: callback(), error: callback(err) +var createAccounts = function (remote, src, amount, accounts, callback) { + async.forEachSeries(accounts, function (account, callback) { + remote.transaction() + .payment(src, account, Amount.from_json(amount)) + .flags('CreateAccount') + .on('proposed', function (m) { + console.log("proposed: %s", JSON.stringify(m)); + + buster.assert.equals(m.result, 'tesSUCCESS'); + + callback(); + }) + .on('error', function (m) { + console.log("error: %s", JSON.stringify(m)); + + callback('error'); + }) + .submit(); + }, callback); +}; + +// success: callback(), error: callback(err) +var creditLimit = function (remote, src, amount, callback) { + remote.transaction() + .ripple_line_set(src, Amount.from_json(amount)) + .on('proposed', function (m) { + console.log("proposed: %s", JSON.stringify(m)); + + buster.assert.equals(m.result, 'tesSUCCESS'); + + callback(); + }) + .on('error', function (m) { + console.log("error: %s", JSON.stringify(m)); + + callback('error'); + }) + .submit(); +}; + +buster.testCase("Work in progress", { + 'setUp' : + function (done) { + server.start("alpha", + function (e) { + buster.refute(e); + + alpha = remote.remoteConfig(config, "alpha", 'TRACE'); + + alpha + .once('ledger_closed', done) + .connect(); + } +// , 'MOCK' + ); + }, + + 'tearDown' : + function (done) { + alpha + .on('disconnected', function () { + server.stop("alpha", function (e) { + buster.refute(e); + done(); + }); + }) + .connect(false); + }, + + "offer create then cancel in one ledger" : + function (done) { + var final_create; + + async.waterfall([ + function (callback) { + alpha.transaction() + .offer_create("root", "500", "100/USD/root") + .on("proposed", function (m) { + console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result != 'tesSUCCESS', m); + }) + .on("final", function (m) { + console.log("FINAL: offer_create: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + + final_create = m; + }) + .submit(); + }, + function (m, callback) { + alpha.transaction() + .offer_cancel("root", m.transaction.Sequence) + .on("proposed", function (m) { + console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m)); + callback(m.result != 'tesSUCCESS', m); + }) + .on("final", function (m) { + console.log("FINAL: offer_cancel: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + buster.assert(final_create); + done(); + }) + .submit(); + }, + function (m, callback) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: %d: %s", ledger_closed_index, ledger_closed); + }) + .ledger_accept(); + } + ], function (error) { + console.log("result: error=%s", error); + buster.refute(error); + + if (error) done(); + }); + }, + + "offer_create then ledger_accept then offer_cancel then ledger_accept." : + function (done) { + var final_create; + var offer_seq; + + async.waterfall([ + function (callback) { + alpha.transaction() + .offer_create("root", "500", "100/USD/root") + .on("proposed", function (m) { + console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + offer_seq = m.transaction.Sequence; + + callback(m.result != 'tesSUCCESS'); + }) + .on("final", function (m) { + console.log("FINAL: offer_create: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + + final_create = m; + + callback(); + }) + .submit(); + }, + function (callback) { + if (!final_create) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: %d: %s", ledger_closed_index, ledger_closed); + + }) + .ledger_accept(); + } + else { + callback(); + } + }, + function (callback) { + console.log("CANCEL: offer_cancel: %d", offer_seq); + + alpha.transaction() + .offer_cancel("root", offer_seq) + .on("proposed", function (m) { + console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m)); + callback(m.result != 'tesSUCCESS'); + }) + .on("final", function (m) { + console.log("FINAL: offer_cancel: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + buster.assert(final_create); + + done(); + }) + .submit(); + }, + // See if ledger_accept will crash. + function (callback) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: A: %d: %s", ledger_closed_index, ledger_closed); + callback(); + }) + .ledger_accept(); + }, + function (callback) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: B: %d: %s", ledger_closed_index, ledger_closed); + callback(); + }) + .ledger_accept(); + }, + ], function (error) { + console.log("result: error=%s", error); + buster.refute(error); + + if (error) done(); + }); + }, + + "offer cancel past and future sequence" : + function (done) { + var final_create; + + async.waterfall([ + function (callback) { + alpha.transaction() + .payment('root', 'alice', Amount.from_json("10000")) + .flags('CreateAccount') + .on("proposed", function (m) { + console.log("PROPOSED: CreateAccount: %s", JSON.stringify(m)); + callback(m.result != 'tesSUCCESS', m); + }) + .on('error', function(m) { + console.log("error: %s", m); + + buster.assert(false); + callback(m); + }) + .submit(); + }, + // Past sequence but wrong + function (m, callback) { + alpha.transaction() + .offer_cancel("root", m.transaction.Sequence) + .on("proposed", function (m) { + console.log("PROPOSED: offer_cancel past: %s", JSON.stringify(m)); + callback(m.result != 'tesSUCCESS', m); + }) + .submit(); + }, + // Same sequence + function (m, callback) { + alpha.transaction() + .offer_cancel("root", m.transaction.Sequence+1) + .on("proposed", function (m) { + console.log("PROPOSED: offer_cancel same: %s", JSON.stringify(m)); + callback(m.result != 'temBAD_SEQUENCE', m); + }) + .submit(); + }, + // Future sequence + function (m, callback) { + // After a malformed transaction, need to recover correct sequence. + alpha.set_account_seq("root", alpha.account_seq("root")-1); + + alpha.transaction() + .offer_cancel("root", m.transaction.Sequence+2) + .on("proposed", function (m) { + console.log("ERROR: offer_cancel future: %s", JSON.stringify(m)); + callback(m.result != 'temBAD_SEQUENCE'); + }) + .submit(); + }, + // See if ledger_accept will crash. + function (callback) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: A: %d: %s", ledger_closed_index, ledger_closed); + callback(); + }) + .ledger_accept(); + }, + function (callback) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: B: %d: %s", ledger_closed_index, ledger_closed); + callback(); + }) + .ledger_accept(); + }, + function (callback) { + callback(); + } + ], function (error) { + console.log("result: error=%s", error); + buster.refute(error); + + done(); + }); + }, +}); +// vim:sw=2:sts=2:ts=8 From c6cb4836312c52ae31b02a8421d55e6e357d60e4 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 19 Oct 2012 21:36:05 -0700 Subject: [PATCH 6/8] UT: offer-test.js remove extra functions --- test/offer-test.js | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/test/offer-test.js b/test/offer-test.js index a47468ba4..cb421fdef 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -15,47 +15,6 @@ buster.testRunner.timeout = 5000; var alpha; -// success: callback(), error: callback(err) -var createAccounts = function (remote, src, amount, accounts, callback) { - async.forEachSeries(accounts, function (account, callback) { - remote.transaction() - .payment(src, account, Amount.from_json(amount)) - .flags('CreateAccount') - .on('proposed', function (m) { - console.log("proposed: %s", JSON.stringify(m)); - - buster.assert.equals(m.result, 'tesSUCCESS'); - - callback(); - }) - .on('error', function (m) { - console.log("error: %s", JSON.stringify(m)); - - callback('error'); - }) - .submit(); - }, callback); -}; - -// success: callback(), error: callback(err) -var creditLimit = function (remote, src, amount, callback) { - remote.transaction() - .ripple_line_set(src, Amount.from_json(amount)) - .on('proposed', function (m) { - console.log("proposed: %s", JSON.stringify(m)); - - buster.assert.equals(m.result, 'tesSUCCESS'); - - callback(); - }) - .on('error', function (m) { - console.log("error: %s", JSON.stringify(m)); - - callback('error'); - }) - .submit(); -}; - buster.testCase("Work in progress", { 'setUp' : function (done) { From 7ed60afad535c17545f4eff23a272561e18ebd88 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 19 Oct 2012 22:31:05 -0700 Subject: [PATCH 7/8] JS: Fix config nicknames for remote-test.js. --- test/remote-test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/remote-test.js b/test/remote-test.js index 733b29dc2..86f31554e 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -2,10 +2,11 @@ var buster = require("buster"); var config = require("./config.js"); var server = require("./server.js"); -var amount = require("../js/amount.js"); var remote = require("../js/remote.js"); -var Amount = amount.Amount; +var Amount = require("../js/amount.js").Amount; + +require("../js/amount.js").setAccounts(config.accounts); var fastTearDown = true; From b395b943ca432428bc49c5550786ad32e9449eda Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sat, 20 Oct 2012 02:08:19 -0400 Subject: [PATCH 8/8] new offer_create test to crash server --- test/offer-test.js | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/test/offer-test.js b/test/offer-test.js index cb421fdef..bae3b947f 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -180,6 +180,101 @@ buster.testCase("Work in progress", { }); }, + + "new user offer_create then ledger_accept then offer_cancel then ledger_accept." : + function (done) { + var final_create; + var offer_seq; + + async.waterfall([ + function (callback) { + alpha.transaction() + .payment('root', 'alice', "1000") + .flags('CreateAccount') + .on('proposed', function (m) { + console.log("proposed: %s", JSON.stringify(m)); + buster.assert.equals(m.result, 'tesSUCCESS'); + callback(); + }) + .submit() + }, + function (callback) { + alpha.transaction() + .offer_create("alice", "500", "100/USD/alice") + .on("proposed", function (m) { + console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + offer_seq = m.transaction.Sequence; + + callback(m.result != 'tesSUCCESS'); + }) + .on("final", function (m) { + console.log("FINAL: offer_create: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + + final_create = m; + + callback(); + }) + .submit(); + }, + function (callback) { + if (!final_create) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: %d: %s", ledger_closed_index, ledger_closed); + + }) + .ledger_accept(); + } + else { + callback(); + } + }, + function (callback) { + console.log("CANCEL: offer_cancel: %d", offer_seq); + + alpha.transaction() + .offer_cancel("alice", offer_seq) + .on("proposed", function (m) { + console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m)); + callback(m.result != 'tesSUCCESS'); + }) + .on("final", function (m) { + console.log("FINAL: offer_cancel: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + buster.assert(final_create); + + done(); + }) + .submit(); + }, + // See if ledger_accept will crash. + function (callback) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: A: %d: %s", ledger_closed_index, ledger_closed); + callback(); + }) + .ledger_accept(); + }, + function (callback) { + alpha + .once("ledger_closed", function (ledger_closed, ledger_closed_index) { + console.log("LEDGER_CLOSED: B: %d: %s", ledger_closed_index, ledger_closed); + callback(); + }) + .ledger_accept(); + }, + ], function (error) { + console.log("result: error=%s", error); + buster.refute(error); + if (error) done(); + }); + }, + "offer cancel past and future sequence" : function (done) { var final_create;