Merge branch 'master' of github.com:jedmccaleb/NewCoin

This commit is contained in:
JoelKatz
2012-10-20 12:20:54 -07:00
8 changed files with 434 additions and 25 deletions

View File

@@ -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);
};

View File

@@ -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;

View File

@@ -151,3 +151,5 @@
FIELD(Template, ARRAY, 5)
FIELD(Necessary, ARRAY, 6)
FIELD(Sufficient, ARRAY, 7)
// vim:ts=4

View File

@@ -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)
{

View File

@@ -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." },

View File

@@ -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,

360
test/offer-test.js Normal file
View File

@@ -0,0 +1,360 @@
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;
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();
});
},
"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;
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

View File

@@ -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;