From 2ff3583b7dda54d623cb960f5c3d3ac597eba7bb Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Wed, 30 Jan 2013 18:55:42 -0800 Subject: [PATCH 01/21] Cleanup. --- src/cpp/ripple/Amount.cpp | 2 +- src/cpp/ripple/KeyCache.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpp/ripple/Amount.cpp b/src/cpp/ripple/Amount.cpp index 6ec1c62105..0fa87c3bc1 100644 --- a/src/cpp/ripple/Amount.cpp +++ b/src/cpp/ripple/Amount.cpp @@ -142,7 +142,7 @@ STAmount::STAmount(SField::ref n, const Json::Value& v) std::vector elements; boost::split(elements, val, boost::is_any_of("\t\n\r ,/")); - if ((elements.size() < 0) || (elements.size() > 3)) + if (elements.size() > 3) throw std::runtime_error("invalid amount string"); value = elements[0]; diff --git a/src/cpp/ripple/KeyCache.h b/src/cpp/ripple/KeyCache.h index 95b0277b67..63bc2e80c6 100644 --- a/src/cpp/ripple/KeyCache.h +++ b/src/cpp/ripple/KeyCache.h @@ -23,7 +23,7 @@ public: KeyCache(const std::string& name, int size = 0, int age = 120) : mName(name), mTargetSize(size), mTargetAge(age) { - assert((mTargetSize >= 0) && (mTargetAge > 2)); + assert((size >= 0) && (age > 2)); } void getSize() From b662167683cc39fb76bed819b46b0db710c968d6 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Wed, 30 Jan 2013 20:56:11 -0800 Subject: [PATCH 02/21] Small cleanups. --- src/cpp/ripple/HashedObject.cpp | 12 +++--------- src/cpp/ripple/Ledger.cpp | 3 --- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/cpp/ripple/HashedObject.cpp b/src/cpp/ripple/HashedObject.cpp index 44cfffbd44..204f4eb003 100644 --- a/src/cpp/ripple/HashedObject.cpp +++ b/src/cpp/ripple/HashedObject.cpp @@ -166,15 +166,9 @@ void HashedObjectStore::bulkWrite() HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) { - HashedObject::pointer obj; - { - obj = mCache.fetch(hash); - if (obj) - { -// cLog(lsTRACE) << "HOS: " << hash << " fetch: incache"; - return obj; - } - } + HashedObject::pointer obj = mCache.fetch(hash); + if (obj) + return obj; if (mNegativeCache.isPresent(hash)) return obj; diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index 61e7f59deb..3f2124de3d 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -504,10 +504,7 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) ScopedLock sl(theApp->getLedgerDB()->getDBLock()); if (!db->executeSQL(sql) || !db->startIterRows()) - { - cLog(lsDEBUG) << "No ledger for query: " << sql; return Ledger::pointer(); - } db->getStr("LedgerHash", hash); ledgerHash.SetHex(hash, true); From 8660356fd450f8523e909358d1ddd1e0da6a25f1 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Thu, 31 Jan 2013 03:10:21 +0100 Subject: [PATCH 03/21] Create a ripple-lib NPM package for the JavaScript code. --- package.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 11ee2d9db8..70566db75b 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,17 @@ { - "name": "ripple", + "name": "ripple-lib", "version": "0.7.0", - "description": "Open-source peer-to-peer payment network", + "description": "Ripple JavaScript client library", + + "files": [ + "src/js/*.js", + "build/sjcl.js" + ], + "main": "src/js", "directories": { "test": "test" }, + "dependencies": { "async": "~0.1.22", "ws": "~0.4.22", @@ -15,13 +22,15 @@ "buster": "~0.6.2", "grunt-webpack": "~0.4.0" }, + "scripts": { "test": "buster test" }, + "repository": { "type": "git", "url": "git://github.com/jedmccaleb/NewCoin.git" }, - "readmeFilename": "README" + "readmeFilename": "README.md" } From 687e3c2c115b9bab98379937e0668449b0df76bf Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Thu, 31 Jan 2013 00:02:15 -0800 Subject: [PATCH 04/21] Don't wait 20 seconds for the first sweep. For tiny nodes, that can be too long. --- src/cpp/ripple/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpp/ripple/Application.cpp b/src/cpp/ripple/Application.cpp index a2d608279c..bb615cec3f 100644 --- a/src/cpp/ripple/Application.cpp +++ b/src/cpp/ripple/Application.cpp @@ -50,7 +50,7 @@ Application::Application() : getRand(mNonce256.begin(), mNonce256.size()); getRand(reinterpret_cast(&mNonceST), sizeof(mNonceST)); mJobQueue.setThreadCount(); - mSweepTimer.expires_from_now(boost::posix_time::seconds(20)); + mSweepTimer.expires_from_now(boost::posix_time::seconds(10)); mSweepTimer.async_wait(boost::bind(&Application::sweep, this)); } From 1f3479a87d6814745f4de6ed4125cbb0395ce0bb Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Thu, 31 Jan 2013 00:05:29 -0800 Subject: [PATCH 05/21] Rewrite the TaggedCache code to integrate the map and cache. Benchmarking showed the use of a separate map and cache was expensive. The 'visitor' code is gone, but nobody used it and it can be replaced if needed. --- src/cpp/ripple/TaggedCache.h | 276 ++++++++++++++++------------------- 1 file changed, 127 insertions(+), 149 deletions(-) diff --git a/src/cpp/ripple/TaggedCache.h b/src/cpp/ripple/TaggedCache.h index a5930494fd..2a0e6a719b 100644 --- a/src/cpp/ripple/TaggedCache.h +++ b/src/cpp/ripple/TaggedCache.h @@ -31,30 +31,35 @@ public: typedef boost::weak_ptr weak_data_ptr; typedef boost::shared_ptr data_ptr; - typedef bool (*visitor_func)(const c_Key&, c_Data&); - protected: - typedef std::pair cache_entry; + class cache_entry + { + public: + time_t last_use; + data_ptr ptr; + weak_data_ptr weak_ptr; + + cache_entry(time_t l, const data_ptr& d) : last_use(l), ptr(d), weak_ptr(d) { ; } + }; + typedef std::pair cache_pair; typedef boost::unordered_map cache_type; typedef typename cache_type::iterator cache_iterator; - typedef boost::unordered_map map_type; - typedef typename map_type::iterator map_iterator; mutable boost::recursive_mutex mLock; std::string mName; // Used for logging unsigned int mTargetSize; // Desired number of cache entries (0 = ignore) int mTargetAge; // Desired maximum cache age + int mCacheCount; // Number of items cached cache_type mCache; // Hold strong reference to recent objects - map_type mMap; // Track stored objects time_t mLastSweep; public: TaggedCache(const char *name, int size, int age) - : mName(name), mTargetSize(size), mTargetAge(age), mLastSweep(time(NULL)) { ; } + : mName(name), mTargetSize(size), mTargetAge(age), mCacheCount(0), mLastSweep(time(NULL)) { ; } int getTargetSize() const; int getTargetAge() const; @@ -66,8 +71,6 @@ public: void setTargetSize(int size); void setTargetAge(int age); void sweep(); - void visitAll(visitor_func); // Visits all tracked objects, removes selected objects - void visitCached(visitor_func); // Visits all cached objects, uncaches selected objects bool touch(const key_type& key); bool del(const key_type& key, bool valid); @@ -108,13 +111,13 @@ template void TaggedCache::setTa template int TaggedCache::getCacheSize() { boost::recursive_mutex::scoped_lock sl(mLock); - return mCache.size(); + return mCacheCount; } template int TaggedCache::getTrackSize() { boost::recursive_mutex::scoped_lock sl(mLock); - return mMap.size(); + return mCache.size(); } template void TaggedCache::sweep() @@ -123,134 +126,106 @@ template void TaggedCache::sweep time_t mLastSweep = time(NULL); time_t target = mLastSweep - mTargetAge; + int cacheRemovals = 0, mapRemovals = 0, cc = 0; - // Pass 1, remove old objects from cache - int cacheRemovals = 0; - if ((mTargetSize == 0) || (mCache.size() > mTargetSize)) + if ((mTargetSize != 0) && (mCache.size() > mTargetSize)) { - if (mTargetSize != 0) - { - target = mLastSweep - (mTargetAge * mTargetSize / mCache.size()); - if (target > (mLastSweep - 2)) - target = mLastSweep - 2; + target = mLastSweep - (mTargetAge * mTargetSize / mCache.size()); + if (target > (mLastSweep - 2)) + target = mLastSweep - 2; + Log(lsINFO, TaggedCachePartition) << mName << " is growing fast " << + mCache.size() << " of " << mTargetSize << + " aging at " << (mLastSweep - target) << " of " << mTargetAge; + } - Log(lsINFO, TaggedCachePartition) << mName << " is growing fast " << - mCache.size() << " of " << mTargetSize << - " aging at " << (mLastSweep - target) << " of " << mTargetAge; - } - else - target = mLastSweep - mTargetAge; - - cache_iterator cit = mCache.begin(); - while (cit != mCache.end()) - { - if (cit->second.first < target) + cache_iterator cit = mCache.begin(); + while (cit != mCache.end()) + { + if (!cit->second.ptr) + { // weak + if (cit->second.weak_ptr.expired()) { - ++cacheRemovals; + ++mapRemovals; mCache.erase(cit++); } else ++cit; } - } - - // Pass 2, remove dead objects from map - int mapRemovals = 0; - map_iterator mit = mMap.begin(); - while (mit != mMap.end()) - { - if (mit->second.expired()) - { - ++mapRemovals; - mMap.erase(mit++); - } else - ++mit; + { // strong + if (cit->second.last_use < target) + { // expired + --mCacheCount; + ++cacheRemovals; + cit->second.ptr.reset(); + if (cit->second.weak_ptr.expired()) + { + ++mapRemovals; + mCache.erase(cit++); + } + else + ++cit; + } + else + { + ++cc; + ++cit; + } + } } + assert(cc == mCacheCount); if (TaggedCachePartition.doLog(lsTRACE) && (mapRemovals || cacheRemovals)) Log(lsTRACE, TaggedCachePartition) << mName << ": cache = " << mCache.size() << "-" << cacheRemovals << - ", map = " << mMap.size() << "-" << mapRemovals; -} - -template void TaggedCache::visitAll(visitor_func func) -{ // Visits all tracked objects, removes selected objects - boost::recursive_mutex::scoped_lock sl(mLock); - - map_iterator mit = mMap.begin(); - while (mit != mMap.end()) - { - data_ptr cachedData = mit->second.lock(); - if (!cachedData) - mMap.erase(mit++); // dead reference found - else if (func(mit->first, mit->second)) - { - mCache.erase(mit->first); - mMap.erase(mit++); - } - else - ++mit; - } -} - -template void TaggedCache::visitCached(visitor_func func) -{ // Visits all cached objects, uncaches selected objects - boost::recursive_mutex::scoped_lock sl(mLock); - - cache_iterator cit = mCache.begin(); - while (cit != mCache.end()) - { - if (func(cit->first, cit->second.second)) - mCache.erase(cit++); - else - ++cit; - } + ", map-=" << mapRemovals; } template bool TaggedCache::touch(const key_type& key) { // If present, make current in cache boost::recursive_mutex::scoped_lock sl(mLock); - // Is the object in the map? - map_iterator mit = mMap.find(key); - if (mit == mMap.end()) - return false; - if (mit->second.expired()) - { // in map, but expired - mMap.erase(mit); - return false; - } - - // Is the object in the cache? cache_iterator cit = mCache.find(key); - if (cit != mCache.end()) - { // in both map and cache - cit->second.first = time(NULL); + if (cit == mCache.end()) // Don't have the object + return false; + + if (cit->second.ptr) + { // Have the object in cache + cit->second.last_use = time(NULL); return true; } - // In map but not cache, put in cache - mCache.insert(cache_pair(key, cache_entry(time(NULL), data_ptr(cit->second.second)))); - return true; + cit->second.ptr = cit->second.weak_ptr.lock(); + if (cit->second.ptr) + { // We just put the object back in cache + ++mCacheCount; + cit->second.last_use = time(NULL); + return true; + } + + // Object fell out + mCache.erase(cit); + return false; } template bool TaggedCache::del(const key_type& key, bool valid) { // Remove from cache, if !valid, remove from map too. Returns true if removed from cache boost::recursive_mutex::scoped_lock sl(mLock); - if (!valid) - { // remove from map too - map_iterator mit = mMap.find(key); - if (mit == mMap.end()) // not in map, cannot be in cache - return false; - mMap.erase(mit); - } - cache_iterator cit = mCache.find(key); if (cit == mCache.end()) return false; - mCache.erase(cit); - return true; + + bool ret = false; + if (cit->second.ptr) + { + --mCacheCount; + cit->second.reset(); + ret = true; + } + + if (!valid || cit->second.weak_ptr.expired()) + mCache.erase(cit); + return true; } template @@ -259,40 +234,50 @@ bool TaggedCache::canonicalize(const key_type& key, boost::shared // Return values: true=we had the data already boost::recursive_mutex::scoped_lock sl(mLock); - map_iterator mit = mMap.find(key); - if (mit == mMap.end()) - { // not in map + cache_iterator cit = mCache.find(key); + if (cit == mCache.end()) + { mCache.insert(cache_pair(key, cache_entry(time(NULL), data))); - mMap.insert(std::make_pair(key, data)); + ++mCacheCount; return false; } - data_ptr cachedData = mit->second.lock(); - if (!cachedData) - { // in map, but expired. Update in map, insert in cache - mit->second = data; - mCache.insert(cache_pair(key, cache_entry(time(NULL), data))); + cit->second.last_use = time(NULL); + + if (cit->second.ptr) + { + if (replace) + { + cit->second.ptr = data; + cit->second.weak_ptr = data; + } + else + data = cit->second.ptr; return true; } - // in map and cache, canonicalize - if (replace) - mit->second = data; - else - data = cachedData; - - // Valid in map, is it in cache? - cache_iterator cit = mCache.find(key); - if (cit != mCache.end()) + data_ptr cachedData = cit->second.weak_ptr.lock(); + if (cachedData) { - cit->second.first = time(NULL); // Yes, refesh if (replace) - cit->second.second = data; + { + cit->second.ptr = data; + cit->second.weak_ptr = data; + } + else + { + cit->second.ptr = cachedData; + data = cachedData; + } + ++mCacheCount; + return true; } - else // no, add to cache - mCache.insert(cache_pair(key, cache_entry(time(NULL), data))); - return true; + cit->second.ptr = data; + cit->second.weak_ptr = data; + ++mCacheCount; + + return false; } template @@ -300,29 +285,22 @@ boost::shared_ptr TaggedCache::fetch(const key_type& key) { // fetch us a shared pointer to the stored data object boost::recursive_mutex::scoped_lock sl(mLock); - // Is it in the cache? cache_iterator cit = mCache.find(key); - if (cit != mCache.end()) + if (cit == mCache.end()) + return data_ptr(); + cit->second.last_use = time(NULL); + + if (cit->second.ptr) + return cit->second.ptr; + + cit->second.ptr = cit->second.weak_ptr.lock(); + if (cit->second.ptr) { - cit->second.first = time(NULL); // Yes, refresh - return cit->second.second; + ++mCacheCount; + return cit->second.ptr; } - - // Is it in the map? - map_iterator mit = mMap.find(key); - if (mit == mMap.end()) - return data_ptr(); // No, we're done - - data_ptr cachedData = mit->second.lock(); - if (!cachedData) - { // in map, but expired. Sorry, we don't have it - mMap.erase(mit); - return cachedData; - } - - // Put it back in the cache - mCache.insert(cache_pair(key, cache_entry(time(NULL), cachedData))); - return cachedData; + mCache.erase(cit); + return data_ptr(); } template From 50fce0f079d0d78090b9a54a85e5eca5aee64033 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Thu, 31 Jan 2013 00:28:23 -0800 Subject: [PATCH 06/21] Cosmetic improvements. --- src/cpp/ripple/TaggedCache.h | 61 ++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/cpp/ripple/TaggedCache.h b/src/cpp/ripple/TaggedCache.h index 2a0e6a719b..f8cb5d4992 100644 --- a/src/cpp/ripple/TaggedCache.h +++ b/src/cpp/ripple/TaggedCache.h @@ -41,6 +41,10 @@ protected: weak_data_ptr weak_ptr; cache_entry(time_t l, const data_ptr& d) : last_use(l), ptr(d), weak_ptr(d) { ; } + bool isCached() { return !!ptr; } + bool isExpired() { return weak_ptr.expired(); } + data_ptr lock() { return weak_ptr.lock(); } + void touch() { last_use = time(NULL); } }; typedef std::pair cache_pair; @@ -187,18 +191,19 @@ template bool TaggedCache::touch cache_iterator cit = mCache.find(key); if (cit == mCache.end()) // Don't have the object return false; + cache_entry& entry = cit->second; - if (cit->second.ptr) - { // Have the object in cache - cit->second.last_use = time(NULL); + if (entry.isCached()) + { + entry.touch(); return true; } - cit->second.ptr = cit->second.weak_ptr.lock(); - if (cit->second.ptr) + entry.ptr = entry.lock(); + if (entry.isCached()) { // We just put the object back in cache ++mCacheCount; - cit->second.last_use = time(NULL); + entry.touch(); return true; } @@ -214,16 +219,17 @@ template bool TaggedCache::del(c cache_iterator cit = mCache.find(key); if (cit == mCache.end()) return false; + cache_entry& entry = cit->second; bool ret = false; - if (cit->second.ptr) + if (entry.isCached()) { --mCacheCount; - cit->second.reset(); + entry.ptr.reset(); ret = true; } - if (!valid || cit->second.weak_ptr.expired()) + if (!valid || entry.isExpired()) mCache.erase(cit); return true; } @@ -241,40 +247,40 @@ bool TaggedCache::canonicalize(const key_type& key, boost::shared ++mCacheCount; return false; } + cache_entry& entry = cit->second; + entry.touch(); - cit->second.last_use = time(NULL); - - if (cit->second.ptr) + if (entry.isCached()) { if (replace) { - cit->second.ptr = data; - cit->second.weak_ptr = data; + entry.ptr = data; + entry.weak_ptr = data; } else - data = cit->second.ptr; + data = entry.ptr; return true; } - data_ptr cachedData = cit->second.weak_ptr.lock(); + data_ptr cachedData = entry.lock(); if (cachedData) { if (replace) { - cit->second.ptr = data; - cit->second.weak_ptr = data; + entry.ptr = data; + entry.weak_ptr = data; } else { - cit->second.ptr = cachedData; + entry.ptr = cachedData; data = cachedData; } ++mCacheCount; return true; } - cit->second.ptr = data; - cit->second.weak_ptr = data; + entry.ptr = data; + entry.weak_ptr = data; ++mCacheCount; return false; @@ -288,16 +294,17 @@ boost::shared_ptr TaggedCache::fetch(const key_type& key) cache_iterator cit = mCache.find(key); if (cit == mCache.end()) return data_ptr(); - cit->second.last_use = time(NULL); + cache_entry& entry = cit->second; + entry.touch(); - if (cit->second.ptr) - return cit->second.ptr; + if (entry.isCached()) + return entry.ptr; - cit->second.ptr = cit->second.weak_ptr.lock(); - if (cit->second.ptr) + entry.ptr = entry.lock(); + if (entry.isCached()) { ++mCacheCount; - return cit->second.ptr; + return entry.ptr; } mCache.erase(cit); return data_ptr(); From 6d1a3955b40f0b14234f05aa322f070f6051d3a6 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Thu, 31 Jan 2013 00:43:43 -0800 Subject: [PATCH 07/21] Some asserts to try to catch a bug where a ledger in the history gets changed. --- src/cpp/ripple/LedgerHistory.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cpp/ripple/LedgerHistory.cpp b/src/cpp/ripple/LedgerHistory.cpp index 39aab0a04c..c0b6037a98 100644 --- a/src/cpp/ripple/LedgerHistory.cpp +++ b/src/cpp/ripple/LedgerHistory.cpp @@ -30,7 +30,7 @@ void LedgerHistory::addLedger(Ledger::pointer ledger) void LedgerHistory::addAcceptedLedger(Ledger::pointer ledger, bool fromConsensus) { - assert(ledger && ledger->isAccepted()); + assert(ledger && ledger->isAccepted() && ledger->isImmutable()); uint256 h(ledger->getHash()); boost::recursive_mutex::scoped_lock sl(mLedgersByHash.peekMutex()); mLedgersByHash.canonicalize(h, ledger, true); @@ -70,6 +70,7 @@ Ledger::pointer LedgerHistory::getLedgerBySeq(uint32 index) assert(ret->getLedgerSeq() == index); sl.lock(); + assert(ret->isImmutable()); mLedgersByHash.canonicalize(ret->getHash(), ret); mLedgersByIndex[ret->getLedgerSeq()] = ret->getHash(); return (ret->getLedgerSeq() == index) ? ret : Ledger::pointer(); @@ -80,6 +81,7 @@ Ledger::pointer LedgerHistory::getLedgerByHash(const uint256& hash) Ledger::pointer ret = mLedgersByHash.fetch(hash); if (ret) { + assert(ret->isImmutable()); assert(ret->getHash() == hash); return ret; } @@ -87,6 +89,7 @@ Ledger::pointer LedgerHistory::getLedgerByHash(const uint256& hash) ret = Ledger::loadByHash(hash); if (!ret) return ret; + assert(ret->isImmutable()); assert(ret->getHash() == hash); mLedgersByHash.canonicalize(ret->getHash(), ret); assert(ret->getHash() == hash); From 7d8df3dddf76bb467eb9c3f226e1920f70b07bfa Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Thu, 31 Jan 2013 15:40:41 -0800 Subject: [PATCH 08/21] Save validations in a form they can actually be used. --- src/cpp/ripple/DBInit.cpp | 11 ++++++----- src/cpp/ripple/Ledger.cpp | 2 +- src/cpp/ripple/ValidationCollection.cpp | 13 +++++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/cpp/ripple/DBInit.cpp b/src/cpp/ripple/DBInit.cpp index b16ba0f41b..bdc6c29b5e 100644 --- a/src/cpp/ripple/DBInit.cpp +++ b/src/cpp/ripple/DBInit.cpp @@ -55,15 +55,16 @@ const char *LedgerDBInit[] = { );", "CREATE INDEX SeqLedger ON Ledgers(LedgerSeq);", - "CREATE TABLE LedgerValidations ( \ + "CREATE TABLE Validations ( \ LedgerHash CHARACTER(64), \ NodePubKey CHARACTER(56), \ - Flags BIGINT UNSIGNED, \ SignTime BIGINT UNSIGNED, \ - Signature BLOB \ + RawData BLOB \ );", - "CREATE INDEX ValidationByHash ON \ - LedgerValidations(LedgerHash);", + "CREATE INDEX ValidationsByHash ON \ + Validations(LedgerHash);", + "CREATE INDEX ValidationsByTime ON \ + Validations(SignTime);", "END TRANSACTION;" }; diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index 3f2124de3d..406a5f4e56 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -547,7 +547,7 @@ uint256 Ledger::getHashByIndex(uint32 ledgerIndex) { uint256 ret; - std::string sql="SELECT LedgerHash FROM Ledgers WHERE LedgerSeq='"; + std::string sql="SELECT LedgerHash FROM Ledgers INDEXED BY SeqLedger WHERE LedgerSeq='"; sql.append(boost::lexical_cast(ledgerIndex)); sql.append("';"); diff --git a/src/cpp/ripple/ValidationCollection.cpp b/src/cpp/ripple/ValidationCollection.cpp index 5d34e59ec6..84dbc8f140 100644 --- a/src/cpp/ripple/ValidationCollection.cpp +++ b/src/cpp/ripple/ValidationCollection.cpp @@ -300,8 +300,8 @@ void ValidationCollection::condWrite() void ValidationCollection::doWrite() { LoadEvent::autoptr event(theApp->getJobQueue().getLoadEventAP(jtDISK)); - static boost::format insVal("INSERT INTO LedgerValidations " - "(LedgerHash,NodePubKey,Flags,SignTime,Signature) VALUES ('%s','%s','%u','%u',%s);"); + static boost::format insVal("INSERT INTO Validations " + "(LedgerHash,NodePubKey,SignTime,RawData) VALUES ('%s','%s','%u',%s);"); boost::mutex::scoped_lock sl(mValidationLock); assert(mWriting); @@ -314,11 +314,16 @@ void ValidationCollection::doWrite() Database *db = theApp->getLedgerDB()->getDB(); ScopedLock dbl(theApp->getLedgerDB()->getDBLock()); + Serializer s(1024); db->executeSQL("BEGIN TRANSACTION;"); BOOST_FOREACH(const SerializedValidation::pointer& it, vector) + { + s.erase(); + it->add(s); db->executeSQL(boost::str(insVal % it->getLedgerHash().GetHex() - % it->getSignerPublic().humanNodePublic() % it->getFlags() % it->getSignTime() - % sqlEscape(it->getSignature()))); + % it->getSignerPublic().humanNodePublic() % it->getSignTime() + % sqlEscape(s.peekData()))); + } db->executeSQL("END TRANSACTION;"); } sl.lock(); From 3d3304ff1b8b95e22877ad77f8d6d6641ce455de Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Thu, 31 Jan 2013 16:10:27 -0800 Subject: [PATCH 09/21] Assign friendly names to trusted nodes. --- rippled-example.cfg | 3 ++- src/cpp/ripple/Peer.cpp | 6 +++++- src/cpp/ripple/Peer.h | 1 + src/cpp/ripple/UniqueNodeList.cpp | 34 ++++++++++++++++++++----------- src/cpp/ripple/UniqueNodeList.h | 3 ++- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/rippled-example.cfg b/rippled-example.cfg index 6eb7172bfb..f682966e7d 100644 --- a/rippled-example.cfg +++ b/rippled-example.cfg @@ -219,7 +219,8 @@ # [cluster_nodes]: # To extend full trust to other nodes, place their node public keys here. # Generally, you should only do this for nodes under common administration. -# Node public keys start with an 'n'. +# Node public keys start with an 'n'. To give a node a name for identification +# place a space after the public key and then the name. # # [ledger_history]: # The number of past ledgers to acquire on server startup and the minimum to diff --git a/src/cpp/ripple/Peer.cpp b/src/cpp/ripple/Peer.cpp index 7b686f3afc..460f2108cf 100644 --- a/src/cpp/ripple/Peer.cpp +++ b/src/cpp/ripple/Peer.cpp @@ -686,7 +686,7 @@ void Peer::recvHello(ripple::TMHello& packet) << "Peer speaks version " << (packet.protoversion() >> 16) << "." << (packet.protoversion() & 0xFF); mHello = packet; - if (theApp->getUNL().nodeInCluster(mNodePublic)) + if (theApp->getUNL().nodeInCluster(mNodePublic, mNodeName)) { mCluster = true; mLoad.setPrivileged(); @@ -1785,7 +1785,11 @@ Json::Value Peer::getJson() if (mInbound) ret["inbound"] = true; if (mCluster) + { ret["cluster"] = true; + if (!mNodeName.empty()) + ret["name"] = mNodeName; + } if (mHello.has_fullversion()) ret["version"] = mHello.fullversion(); diff --git a/src/cpp/ripple/Peer.h b/src/cpp/ripple/Peer.h index 2d3fb95277..f21e96b30d 100644 --- a/src/cpp/ripple/Peer.h +++ b/src/cpp/ripple/Peer.h @@ -40,6 +40,7 @@ private: bool mActive; bool mCluster; // Node in our cluster RippleAddress mNodePublic; // Node public key of peer. + std::string mNodeName; ipPort mIpPort; ipPort mIpPortConnect; uint256 mCookieHash; diff --git a/src/cpp/ripple/UniqueNodeList.cpp b/src/cpp/ripple/UniqueNodeList.cpp index c0f4d8016f..9c3487c7fb 100644 --- a/src/cpp/ripple/UniqueNodeList.cpp +++ b/src/cpp/ripple/UniqueNodeList.cpp @@ -64,15 +64,6 @@ void UniqueNodeList::start() // Load information about when we last updated. bool UniqueNodeList::miscLoad() { - BOOST_FOREACH(const std::string& node, theConfig.CLUSTER_NODES) - { - RippleAddress a = RippleAddress::createNodePublic(node); - if (a.isValid()) - sClusterNodes.insert(a); - else - cLog(lsWARNING) << "Entry in cluster list invalid: '" << node << "'"; - } - boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); Database *db=theApp->getWalletDB()->getDB(); @@ -105,11 +96,20 @@ bool UniqueNodeList::miscSave() void UniqueNodeList::trustedLoad() { - BOOST_FOREACH(const std::string& node, theConfig.CLUSTER_NODES) + BOOST_FOREACH(const std::string& c, theConfig.CLUSTER_NODES) { + std::string node, name; + size_t s = c.find(' '); + if (s != std::string::npos) + { + name = c.substr(s+1); + node = c.substr(0, s); + } + else + node = c; RippleAddress a = RippleAddress::createNodePublic(node); if (a.isValid()) - sClusterNodes.insert(a); + sClusterNodes.insert(std::make_pair(a, name)); else cLog(lsWARNING) << "Entry in cluster list invalid: '" << node << "'"; } @@ -1699,7 +1699,17 @@ bool UniqueNodeList::nodeInUNL(const RippleAddress& naNodePublic) bool UniqueNodeList::nodeInCluster(const RippleAddress& naNodePublic) { boost::recursive_mutex::scoped_lock sl(mUNLLock); - return sClusterNodes.count(naNodePublic) != 0; + return sClusterNodes.end() != sClusterNodes.find(naNodePublic); +} + +bool UniqueNodeList::nodeInCluster(const RippleAddress& naNodePublic, std::string& name) +{ + boost::recursive_mutex::scoped_lock sl(mUNLLock); + std::map::iterator it = sClusterNodes.find(naNodePublic); + if (it == sClusterNodes.end()) + return false; + name = it->second; + return true; } // vim:ts=4 diff --git a/src/cpp/ripple/UniqueNodeList.h b/src/cpp/ripple/UniqueNodeList.h index 9cf511d84c..ee0770f791 100644 --- a/src/cpp/ripple/UniqueNodeList.h +++ b/src/cpp/ripple/UniqueNodeList.h @@ -88,7 +88,7 @@ private: std::vector viReferrals; } scoreNode; - std::set sClusterNodes; + std::map sClusterNodes; typedef boost::unordered_map strIndex; typedef std::pair ipPort; @@ -155,6 +155,7 @@ public: bool nodeInUNL(const RippleAddress& naNodePublic); bool nodeInCluster(const RippleAddress& naNodePublic); + bool nodeInCluster(const RippleAddress& naNodePublic, std::string& name); void nodeBootstrap(); bool nodeLoad(boost::filesystem::path pConfig); From bbc9ec19313ffdc1ae4ecaed900a38dca9c2ae2a Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 1 Feb 2013 11:52:45 -0800 Subject: [PATCH 10/21] Use regexes to parse cluster entries. --- src/cpp/ripple/UniqueNodeList.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/cpp/ripple/UniqueNodeList.cpp b/src/cpp/ripple/UniqueNodeList.cpp index 9c3487c7fb..ff5622cb2c 100644 --- a/src/cpp/ripple/UniqueNodeList.cpp +++ b/src/cpp/ripple/UniqueNodeList.cpp @@ -96,22 +96,18 @@ bool UniqueNodeList::miscSave() void UniqueNodeList::trustedLoad() { + boost::regex rNode("\\`\\s*(\\S+)[\\s]*(.*)\\'"); BOOST_FOREACH(const std::string& c, theConfig.CLUSTER_NODES) { - std::string node, name; - size_t s = c.find(' '); - if (s != std::string::npos) + boost::smatch match; + if (boost::regex_match(c, match, rNode)) { - name = c.substr(s+1); - node = c.substr(0, s); + RippleAddress a = RippleAddress::createNodePublic(match[1]); + if (a.isValid()) + sClusterNodes.insert(std::make_pair(a, (match.size() > 1) ? match[2] : std::string(""))); } else - node = c; - RippleAddress a = RippleAddress::createNodePublic(node); - if (a.isValid()) - sClusterNodes.insert(std::make_pair(a, name)); - else - cLog(lsWARNING) << "Entry in cluster list invalid: '" << node << "'"; + cLog(lsWARNING) << "Entry in cluster list invalid: '" << c << "'"; } Database* db=theApp->getWalletDB()->getDB(); From bfbb7633ec54018a6cbe0fce43dc25b747390db7 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 1 Feb 2013 13:28:27 -0800 Subject: [PATCH 11/21] JS: Make transaction subscription robust. --- src/js/remote.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/js/remote.js b/src/js/remote.js index 1b1f675912..5832cced38 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -205,7 +205,7 @@ var Remote = function (opts, trace) { this._testnet = undefined; this._transaction_subs = 0; this.online_target = false; - this.online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' + this._online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' this.state = 'offline'; // 'online', 'offline' this.retry_timer = undefined; this.retry = undefined; @@ -245,7 +245,7 @@ var Remote = function (opts, trace) { this.on('newListener', function (type, listener) { if ('transaction' === type) { - if (!self._transaction_subs) + if (!self._transaction_subs && 'open' === self._online_state) { self.request_subscribe([ 'transactions' ]) .request(); @@ -260,7 +260,7 @@ var Remote = function (opts, trace) { { self._transaction_subs -= 1; - if (!self._transaction_subs) + if (!self._transaction_subs && 'open' === self._online_state) { self.request_unsubscribe([ 'transactions' ]) .request(); @@ -327,12 +327,12 @@ Remote.prototype._set_state = function (state) { switch (state) { case 'online': - this.online_state = 'open'; + this._online_state = 'open'; this.emit('connected'); break; case 'offline': - this.online_state = 'closed'; + this._online_state = 'closed'; this.emit('disconnected'); break; } @@ -353,7 +353,7 @@ Remote.prototype.connect = function (online) { this.online_target = target; // If we were in a stable state, go dynamic. - switch (this.online_state) { + switch (this._online_state) { case 'open': if (!target) this._connect_stop(); break; @@ -390,9 +390,9 @@ Remote.prototype._connect_retry = function () { // Do not continue trying to connect. this._set_state('offline'); } - else if ('connecting' !== this.online_state) { + else if ('connecting' !== this._online_state) { // New to connecting state. - this.online_state = 'connecting'; + this._online_state = 'connecting'; this.retry = 0; this._set_state('offline'); // Report newly offline. @@ -915,7 +915,12 @@ Remote.prototype.submit = function (transaction) { Remote.prototype._server_subscribe = function () { var self = this; - this.request_subscribe([ 'ledger', 'server' ]) + var feeds = [ 'ledger', 'server' ]; + + if (this._transaction_subs) + feeds.push('transactions'); + + this.request_subscribe(feeds) .on('success', function (message) { self._stand_alone = !!message.stand_alone; self._testnet = !!message.testnet; From 37cc88ccf246954a7a081653a96c7d4aa669858e Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 1 Feb 2013 01:41:36 +0100 Subject: [PATCH 12/21] Move custom SJCL extensions from client over to Ripple library. --- grunt.js | 5 +- src/js/README.md | 3 + src/js/sjcl-custom/sjcl-extramath.js | 61 ++++++++ src/js/sjcl-custom/sjcl-ripemd160.js | 207 +++++++++++++++++++++++++++ src/js/sjcl-custom/sjcl-secp256k1.js | 72 ++++++++++ 5 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 src/js/README.md create mode 100755 src/js/sjcl-custom/sjcl-extramath.js create mode 100755 src/js/sjcl-custom/sjcl-ripemd160.js create mode 100755 src/js/sjcl-custom/sjcl-secp256k1.js diff --git a/grunt.js b/grunt.js index 152cef3511..fdb94be2b0 100644 --- a/grunt.js +++ b/grunt.js @@ -32,7 +32,10 @@ module.exports = function(grunt) { "src/js/sjcl/core/convenience.js", "src/js/sjcl/core/bn.js", "src/js/sjcl/core/ecc.js", - "src/js/sjcl/core/srp.js" + "src/js/sjcl/core/srp.js", + "src/js/sjcl-custom/sjcl-secp256k1.js", + "src/js/sjcl-custom/sjcl-ripemd160.js", + "src/js/sjcl-custom/sjcl-extramath.js" ], dest: 'build/sjcl.js' } diff --git a/src/js/README.md b/src/js/README.md new file mode 100644 index 0000000000..7b357e08bd --- /dev/null +++ b/src/js/README.md @@ -0,0 +1,3 @@ +# Ripple JavaScript library + +This library lets you connect to a ripple server via websockets. diff --git a/src/js/sjcl-custom/sjcl-extramath.js b/src/js/sjcl-custom/sjcl-extramath.js new file mode 100755 index 0000000000..4dd5f326a3 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-extramath.js @@ -0,0 +1,61 @@ +sjcl.bn.ZERO = new sjcl.bn(0); + +/** [ this / that , this % that ] */ +sjcl.bn.prototype.divRem = function (that) { + if (typeof(that) !== "object") { that = new this._class(that); } + var thisa = this.abs(), thata = that.abs(), quot = new this._class(0), + ci = 0; + if (!thisa.greaterEquals(thata)) { + this.initWith(0); + return this; + } else if (thisa.equals(thata)) { + this.initWith(sign); + return this; + } + + for (; thisa.greaterEquals(thata); ci++) { + thata.doubleM(); + } + for (; ci > 0; ci--) { + quot.doubleM(); + thata.halveM(); + if (thisa.greaterEquals(thata)) { + quot.addM(1); + thisa.subM(that).normalize(); + } + } + return [quot, thisa]; +}; + +/** this /= that (rounded to nearest int) */ +sjcl.bn.prototype.divRound = function (that) { + var dr = this.divRem(that), quot = dr[0], rem = dr[1]; + + if (rem.doubleM().greaterEquals(that)) { + quot.addM(1); + } + + return quot; +}; + +/** this /= that (rounded down) */ +sjcl.bn.prototype.div = function (that) { + var dr = this.divRem(that); + return dr[0]; +}; + +sjcl.bn.prototype.sign = function () { + return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1; + }; + +/** -this */ +sjcl.bn.prototype.neg = function () { + return sjcl.bn.ZERO.sub(this); +}; + +/** |this| */ +sjcl.bn.prototype.abs = function () { + if (this.sign() === -1) { + return this.neg(); + } else return this; +}; diff --git a/src/js/sjcl-custom/sjcl-ripemd160.js b/src/js/sjcl-custom/sjcl-ripemd160.js new file mode 100755 index 0000000000..4ee7b9246d --- /dev/null +++ b/src/js/sjcl-custom/sjcl-ripemd160.js @@ -0,0 +1,207 @@ +/** @fileOverview Javascript RIPEMD-160 implementation. + * + * @author Artem S Vybornov + */ +(function() { + +/** + * Context for a RIPEMD-160 operation in progress. + * @constructor + * @class RIPEMD, 160 bits. + */ +sjcl.hash.ripemd160 = function (hash) { + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ +sjcl.hash.ripemd160.hash = function (data) { + return (new sjcl.hash.ripemd160()).update(data).finalize(); +}; + +sjcl.hash.ripemd160.prototype = { + /** + * Reset the hash state. + * @return this + */ + reset: function () { + this._h = _h0.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Reset the hash state. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if ( typeof data === "string" ) + data = sjcl.codec.utf8String.toBits(data); + + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = 512+ol & -512; i <= nl; i+= 512) { + var words = b.splice(0,16); + for ( var w = 0; w < 16; ++w ) + words[w] = _cvt(words[w]); + + _block.call( this, words ); + } + + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ + finalize: function () { + var b = sjcl.bitArray.concat( this._buffer, [ sjcl.bitArray.partial(1,1) ] ), + l = ( this._length + 1 ) % 512, + z = ( l > 448 ? 512 : 448 ) - l % 448, + zp = z % 32; + + if ( zp > 0 ) + b = sjcl.bitArray.concat( b, [ sjcl.bitArray.partial(zp,0) ] ) + for ( ; z >= 32; z -= 32 ) + b.push(0); + + b.push( _cvt( this._length | 0 ) ); + b.push( _cvt( Math.floor(this._length / 0x100000000) ) ); + + while ( b.length ) { + var words = b.splice(0,16); + for ( var w = 0; w < 16; ++w ) + words[w] = _cvt(words[w]); + + _block.call( this, words ); + } + + var h = this._h; + this.reset(); + + for ( var w = 0; w < 5; ++w ) + h[w] = _cvt(h[w]); + + return h; + } +}; + +var _h0 = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + +var _k1 = [ 0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e ]; +var _k2 = [ 0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000 ]; +for ( var i = 4; i >= 0; --i ) { + for ( var j = 1; j < 16; ++j ) { + _k1.splice(i,0,_k1[i]); + _k2.splice(i,0,_k2[i]); + } +} + +var _r1 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 ]; +var _r2 = [ 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 ]; + +var _s1 = [ 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]; +var _s2 = [ 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]; + +function _f0(x,y,z) { + return x ^ y ^ z; +}; + +function _f1(x,y,z) { + return (x & y) | (~x & z); +}; + +function _f2(x,y,z) { + return (x | ~y) ^ z; +}; + +function _f3(x,y,z) { + return (x & z) | (y & ~z); +}; + +function _f4(x,y,z) { + return x ^ (y | ~z); +}; + +function _rol(n,l) { + return (n << l) | (n >>> (32-l)); +} + +function _cvt(n) { + return ( (n & 0xff << 0) << 24 ) + | ( (n & 0xff << 8) << 8 ) + | ( (n & 0xff << 16) >>> 8 ) + | ( (n & 0xff << 24) >>> 24 ); +} + +function _block(X) { + var A1 = this._h[0], B1 = this._h[1], C1 = this._h[2], D1 = this._h[3], E1 = this._h[4], + A2 = this._h[0], B2 = this._h[1], C2 = this._h[2], D2 = this._h[3], E2 = this._h[4]; + + var j = 0, T; + + for ( ; j < 16; ++j ) { + T = _rol( A1 + _f0(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f4(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 32; ++j ) { + T = _rol( A1 + _f1(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f3(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 48; ++j ) { + T = _rol( A1 + _f2(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f2(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 64; ++j ) { + T = _rol( A1 + _f3(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f1(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 80; ++j ) { + T = _rol( A1 + _f4(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f0(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + + T = this._h[1] + C1 + D2; + this._h[1] = this._h[2] + D1 + E2; + this._h[2] = this._h[3] + E1 + A2; + this._h[3] = this._h[4] + A1 + B2; + this._h[4] = this._h[0] + B1 + C2; + this._h[0] = T; +} + +})(); diff --git a/src/js/sjcl-custom/sjcl-secp256k1.js b/src/js/sjcl-custom/sjcl-secp256k1.js new file mode 100755 index 0000000000..4d34db7f17 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-secp256k1.js @@ -0,0 +1,72 @@ +// ----- for secp256k1 ------ + +// Overwrite NIST-P256 with secp256k1 so we're on even footing +sjcl.ecc.curves.c256 = new sjcl.ecc.curve( + sjcl.bn.pseudoMersennePrime(256, [[0,-1],[4,-1],[6,-1],[7,-1],[8,-1],[9,-1],[32,-1]]), + "0x14551231950b75fc4402da1722fc9baee", + 0, + 7, + "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" +); + +// Replace point addition and doubling algorithms +// NIST-P256 is a=-3, we need algorithms for a=0 +sjcl.ecc.pointJac.prototype.add = function(T) { + var S = this; + if (S.curve !== T.curve) { + throw("sjcl.ecc.add(): Points must be on the same curve to add them!"); + } + + if (S.isIdentity) { + return T.toJac(); + } else if (T.isIdentity) { + return S; + } + + var z1z1 = S.z.square(); + var h = T.x.mul(z1z1).subM(S.x); + var s2 = T.y.mul(S.z).mul(z1z1); + + if (h.equals(0)) { + if (S.y.equals(T.y.mul(z1z1.mul(S.z)))) { + // same point + return S.doubl(); + } else { + // inverses + return new sjcl.ecc.pointJac(S.curve); + } + } + + var hh = h.square(); + var i = hh.copy().doubleM().doubleM(); + var j = h.mul(i); + var r = s2.sub(S.y).doubleM(); + var v = S.x.mul(i); + + var x = r.square().subM(j).subM(v.copy().doubleM()); + var y = r.mul(v.sub(x)).subM(S.y.mul(j).doubleM()); + var z = S.z.add(h).square().subM(z1z1).subM(hh); + + return new sjcl.ecc.pointJac(this.curve,x,y,z); +}; + +sjcl.ecc.pointJac.prototype.doubl = function () { + if (this.isIdentity) { return this; } + + var a = this.x.square(); + var b = this.y.square(); + var c = b.square(); + var d = this.x.add(b).square().subM(a).subM(c).doubleM(); + var e = a.mul(3); + var f = e.square(); + var x = f.sub(d.copy().doubleM()); + var y = e.mul(d.sub(x)).subM(c.doubleM().doubleM().doubleM()); + var z = this.y.mul(this.z).doubleM(); + return new sjcl.ecc.pointJac(this.curve, x, y, z); +}; + +sjcl.ecc.point.prototype.toBytesCompressed = function () { + var header = this.y.mod(2).toString() == "0x0" ? 0x02 : 0x03; + return [header].concat(sjcl.codec.bytes.fromBits(this.x.toBits())) +}; From 24dac24d2937a109322b3ddb39e12bbdd7c1153b Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 1 Feb 2013 15:13:09 +0100 Subject: [PATCH 13/21] Refactor JavaScript library. - Separate classes in separate modules (files) - Constants should be associated with a class - Replace exports.config scheme with a config singleton - Refactor base58 functions as a static class --- src/js/amount.js | 442 +---------------------------------------- src/js/base.js | 136 +++++++++++++ src/js/config.js | 3 + src/js/currency.js | 81 ++++++++ src/js/remote.js | 8 +- src/js/seed.js | 89 +++++++++ src/js/uint160.js | 156 +++++++++++++++ test/amount-test.js | 9 +- test/jsonrpc-test.js | 4 +- test/monitor-test.js | 4 +- test/offer-test.js | 4 +- test/path-test.js | 4 +- test/remote-test.js | 4 +- test/send-test.js | 4 +- test/testutils.js | 4 +- test/websocket-test.js | 3 +- 16 files changed, 500 insertions(+), 455 deletions(-) create mode 100644 src/js/base.js create mode 100644 src/js/config.js create mode 100644 src/js/currency.js create mode 100644 src/js/seed.js create mode 100644 src/js/uint160.js diff --git a/src/js/amount.js b/src/js/amount.js index b24ff0397b..7614e48721 100644 --- a/src/js/amount.js +++ b/src/js/amount.js @@ -3,27 +3,18 @@ var sjcl = require('../../build/sjcl'); var bn = sjcl.bn; -var utils = require('./utils.js'); -var jsbn = require('./jsbn.js'); +var utils = require('./utils'); +var jsbn = require('./jsbn'); -var BigInteger = jsbn.BigInteger; -var nbi = jsbn.nbi; +var BigInteger = jsbn.BigInteger; -var alphabets = { - 'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", - 'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz", - 'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" -}; +var UInt160 = require('./uint160').UInt160, + Seed = require('./seed').Seed, + Currency = require('./currency').Currency; var consts = exports.consts = { - 'address_xns' : "rrrrrrrrrrrrrrrrrrrrrhoLvTp", - 'address_one' : "rrrrrrrrrrrrrrrrrrrrBZbvji", 'currency_xns' : 0, 'currency_one' : 1, - 'uint160_xns' : utils.hexToString("0000000000000000000000000000000000000000"), - 'uint160_one' : utils.hexToString("0000000000000000000000000000000000000001"), - 'hex_xns' : "0000000000000000000000000000000000000000", - 'hex_one' : "0000000000000000000000000000000000000001", 'xns_precision' : 6, // BigInteger values prefixed with bi_. @@ -42,421 +33,8 @@ var consts = exports.consts = { 'cMinOffset' : -96, 'cMaxOffset' : 80, - - 'VER_NONE' : 1, - 'VER_NODE_PUBLIC' : 28, - 'VER_NODE_PRIVATE' : 32, - 'VER_ACCOUNT_ID' : 0, - 'VER_ACCOUNT_PUBLIC' : 35, - 'VER_ACCOUNT_PRIVATE' : 34, - 'VER_FAMILY_GENERATOR' : 41, - 'VER_FAMILY_SEED' : 33, }; -// --> input: big-endian array of bytes. -// <-- string at least as long as input. -var encode_base = function (input, alphabet) { - var alphabet = alphabets[alphabet || 'ripple']; - var bi_base = new BigInteger(String(alphabet.length)); - var bi_q = nbi(); - var bi_r = nbi(); - var bi_value = new BigInteger(input); - var buffer = []; - - while (bi_value.compareTo(BigInteger.ZERO) > 0) - { - bi_value.divRemTo(bi_base, bi_q, bi_r); - bi_q.copyTo(bi_value); - - buffer.push(alphabet[bi_r.intValue()]); - } - - var i; - - for (i = 0; i != input.length && !input[i]; i += 1) { - buffer.push(alphabet[0]); - } - - return buffer.reverse().join(""); -}; - -// --> input: String -// <-- array of bytes or undefined. -var decode_base = function (input, alphabet) { - var alphabet = alphabets[alphabet || 'ripple']; - var bi_base = new BigInteger(String(alphabet.length)); - var bi_value = nbi(); - var i; - - for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1) - ; - - for (; i != input.length; i += 1) { - var v = alphabet.indexOf(input[i]); - - if (v < 0) - return undefined; - - var r = nbi(); - - r.fromInt(v); - - bi_value = bi_value.multiply(bi_base).add(r); - } - - // toByteArray: - // - Returns leading zeros! - // - Returns signed bytes! - var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var extra = 0; - - while (extra != bytes.length && !bytes[extra]) - extra += 1; - - if (extra) - bytes = bytes.slice(extra); - - var zeros = 0; - - while (zeros !== input.length && input[zeros] === alphabet[0]) - zeros += 1; - - return [].concat(utils.arraySet(zeros, 0), bytes); -}; - -var sha256 = function (bytes) { - return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); -}; - -var sha256hash = function (bytes) { - return sha256(sha256(bytes)); -}; - -// --> input: Array -// <-- String -var encode_base_check = function (version, input, alphabet) { - var buffer = [].concat(version, input); - var check = sha256(sha256(buffer)).slice(0, 4); - - return encode_base([].concat(buffer, check), alphabet); -} - -// --> input : String -// <-- NaN || BigInteger -var decode_base_check = function (version, input, alphabet) { - var buffer = decode_base(input, alphabet); - - if (!buffer || buffer[0] !== version || buffer.length < 5) - return NaN; - - var computed = sha256hash(buffer.slice(0, -4)).slice(0, 4); - var checksum = buffer.slice(-4); - var i; - - for (i = 0; i != 4; i += 1) - if (computed[i] !== checksum[i]) - return NaN; - - return new BigInteger(buffer.slice(1, -4)); -} - -// -// Seed support -// - -var Seed = function () { - // Internal form: NaN or BigInteger - this._value = NaN; -}; - -Seed.json_rewrite = function (j) { - return Seed.from_json(j).to_json(); -}; - -// Return a new Seed from j. -Seed.from_json = function (j) { - return 'string' === typeof j - ? (new Seed()).parse_json(j) - : j.clone(); -}; - -Seed.is_valid = function (j) { - return Seed.from_json(j).is_valid(); -}; - -Seed.prototype.clone = function () { - return this.copyTo(new Seed()); -}; - -// Returns copy. -Seed.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -Seed.prototype.equals = function (d) { - return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); -}; - -Seed.prototype.is_valid = function () { - return this._value instanceof BigInteger; -}; - -// value = NaN on error. -// One day this will support rfc1751 too. -Seed.prototype.parse_json = function (j) { - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "s") { - this._value = decode_base_check(consts.VER_FAMILY_SEED, j); - } - else if (16 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 128); - } - else { - this._value = NaN; - } - - return this; -}; - -// Convert from internal form. -Seed.prototype.to_json = function () { - if (!(this._value instanceof BigInteger)) - return NaN; - - var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var target = 20; - - // XXX Make sure only trim off leading zeros. - var array = bytes.length < target - ? bytes.length - ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) - : utils.arraySet(target, 0) - : bytes.slice(target - bytes.length); - var output = encode_base_check(consts.VER_FAMILY_SEED, array); - - return output; -}; - -// -// UInt160 support -// - -var UInt160 = function () { - // Internal form: NaN or BigInteger - this._value = NaN; -}; - -UInt160.json_rewrite = function (j) { - return UInt160.from_json(j).to_json(); -}; - -// Return a new UInt160 from j. -UInt160.from_generic = function (j) { - return 'string' === typeof j - ? (new UInt160()).parse_generic(j) - : j.clone(); -}; - -// Return a new UInt160 from j. -UInt160.from_json = function (j) { - if ('string' === typeof j) { - return (new UInt160()).parse_json(j); - } else if (j instanceof UInt160) { - return j.clone(); - } else { - return new UInt160(); - } -}; - -UInt160.is_valid = function (j) { - return UInt160.from_json(j).is_valid(); -}; - -UInt160.prototype.clone = function () { - return this.copyTo(new UInt160()); -}; - -// Returns copy. -UInt160.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -UInt160.prototype.equals = function (d) { - return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); -}; - -UInt160.prototype.is_valid = function () { - return this._value instanceof BigInteger; -}; - -// value = NaN on error. -UInt160.prototype.parse_generic = function (j) { - // Canonicalize and validate - if (exports.config.accounts && j in exports.config.accounts) - j = exports.config.accounts[j].account; - - switch (j) { - case undefined: - case "0": - case consts.address_xns: - case consts.uint160_xns: - case consts.hex_xns: - this._value = nbi(); - break; - - case "1": - case consts.address_one: - case consts.uint160_one: - case consts.hex_one: - this._value = new BigInteger([1]); - - break; - - default: - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "r") { - this._value = decode_base_check(consts.VER_ACCOUNT_ID, j); - } - else if (20 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 256); - } - else if (40 === j.length) { - // XXX Check char set! - this._value = new BigInteger(j, 16); - } - else { - this._value = NaN; - } - } - - return this; -}; - -// value = NaN on error. -UInt160.prototype.parse_json = function (j) { - // Canonicalize and validate - if (exports.config.accounts && j in exports.config.accounts) - j = exports.config.accounts[j].account; - - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "r") { - this._value = decode_base_check(consts.VER_ACCOUNT_ID, j); - } - else { - this._value = NaN; - } - - return this; -}; - -// Convert from internal form. -// XXX Json form should allow 0 and 1, C++ doesn't currently allow it. -UInt160.prototype.to_json = function () { - if (!(this._value instanceof BigInteger)) - return NaN; - - var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var target = 20; - - // XXX Make sure only trim off leading zeros. - var array = bytes.length < target - ? bytes.length - ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) - : utils.arraySet(target, 0) - : bytes.slice(target - bytes.length); - var output = encode_base_check(consts.VER_ACCOUNT_ID, array); - - return output; -}; - -// -// Currency support -// - -// XXX Internal form should be UInt160. -var Currency = function () { - // Internal form: 0 = XRP. 3 letter-code. - // XXX Internal should be 0 or hex with three letter annotation when valid. - - // Json form: - // '', 'XRP', '0': 0 - // 3-letter code: ... - // XXX Should support hex, C++ doesn't currently allow it. - - this._value = NaN; -} - -// Given "USD" return the json. -Currency.json_rewrite = function (j) { - return Currency.from_json(j).to_json(); -}; - -Currency.from_json = function (j) { - return 'string' === typeof j - ? (new Currency()).parse_json(j) - : j.clone(); -}; - -Currency.is_valid = function (j) { - return currency.from_json(j).is_valid(); -}; - -Currency.prototype.clone = function() { - return this.copyTo(new Currency()); -}; - -// Returns copy. -Currency.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -Currency.prototype.equals = function (d) { - return ('string' !== typeof this._value && isNaN(this._value)) - || ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value; -}; - -// this._value = NaN on error. -Currency.prototype.parse_json = function (j) { - if ("" === j || "0" === j || "XRP" === j) { - this._value = 0; - } - else if ('string' != typeof j || 3 !== j.length) { - this._value = NaN; - } - else { - this._value = j; - } - - return this; -}; - -Currency.prototype.is_native = function () { - return !isNaN(this._value) && !this._value; -}; - -Currency.prototype.is_valid = function () { - return !isNaN(this._value); -}; - -Currency.prototype.to_json = function () { - return this._value ? this._value : "XRP"; -}; - -Currency.prototype.to_human = function () { - return this._value ? this._value : "XRP"; -}; // // Amount class in the style of Java's BigInteger class @@ -505,9 +83,9 @@ Amount.is_valid_full = function (j) { Amount.NaN = function () { var result = new Amount(); - + result._value = NaN; - + return result; }; @@ -1305,10 +883,10 @@ Amount.prototype.not_equals_why = function (d) { }; exports.Amount = Amount; + +// DEPRECATED: Include the corresponding files instead. exports.Currency = Currency; exports.Seed = Seed; exports.UInt160 = UInt160; -exports.config = {}; - // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/base.js b/src/js/base.js new file mode 100644 index 0000000000..47ef551ed0 --- /dev/null +++ b/src/js/base.js @@ -0,0 +1,136 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var Base = {}; + +var alphabets = Base.alphabets = { + 'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", + 'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz", + 'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +}; + +extend(Base, { + 'VER_NONE' : 1, + 'VER_NODE_PUBLIC' : 28, + 'VER_NODE_PRIVATE' : 32, + 'VER_ACCOUNT_ID' : 0, + 'VER_ACCOUNT_PUBLIC' : 35, + 'VER_ACCOUNT_PRIVATE' : 34, + 'VER_FAMILY_GENERATOR' : 41, + 'VER_FAMILY_SEED' : 33 +}); + +var sha256 = function (bytes) { + return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); +}; + +var sha256hash = function (bytes) { + return sha256(sha256(bytes)); +}; + +// --> input: big-endian array of bytes. +// <-- string at least as long as input. +Base.encode = function (input, alpha) { + var alphabet = alphabets[alpha || 'ripple']; + var bi_base = new BigInteger(String(alphabet.length)); + var bi_q = nbi(); + var bi_r = nbi(); + var bi_value = new BigInteger(input); + var buffer = []; + + while (bi_value.compareTo(BigInteger.ZERO) > 0) + { + bi_value.divRemTo(bi_base, bi_q, bi_r); + bi_q.copyTo(bi_value); + + buffer.push(alphabet[bi_r.intValue()]); + } + + var i; + + for (i = 0; i != input.length && !input[i]; i += 1) { + buffer.push(alphabet[0]); + } + + return buffer.reverse().join(""); +}; + +// --> input: String +// <-- array of bytes or undefined. +Base.decode = function (input, alpha) { + var alphabet = alphabets[alpha || 'ripple']; + var bi_base = new BigInteger(String(alphabet.length)); + var bi_value = nbi(); + var i; + + for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1) + ; + + for (; i != input.length; i += 1) { + var v = alphabet.indexOf(input[i]); + + if (v < 0) + return undefined; + + var r = nbi(); + + r.fromInt(v); + + bi_value = bi_value.multiply(bi_base).add(r); + } + + // toByteArray: + // - Returns leading zeros! + // - Returns signed bytes! + var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; }); + var extra = 0; + + while (extra != bytes.length && !bytes[extra]) + extra += 1; + + if (extra) + bytes = bytes.slice(extra); + + var zeros = 0; + + while (zeros !== input.length && input[zeros] === alphabet[0]) + zeros += 1; + + return [].concat(utils.arraySet(zeros, 0), bytes); +}; + +// --> input: Array +// <-- String +Base.encode_check = function (version, input, alphabet) { + var buffer = [].concat(version, input); + var check = sha256(sha256(buffer)).slice(0, 4); + + return Base.encode([].concat(buffer, check), alphabet); +} + +// --> input : String +// <-- NaN || BigInteger +Base.decode_check = function (version, input, alphabet) { + var buffer = Base.decode(input, alphabet); + + if (!buffer || buffer[0] !== version || buffer.length < 5) + return NaN; + + var computed = sha256hash(buffer.slice(0, -4)).slice(0, 4); + var checksum = buffer.slice(-4); + var i; + + for (i = 0; i != 4; i += 1) + if (computed[i] !== checksum[i]) + return NaN; + + return new BigInteger(buffer.slice(1, -4)); +} + +exports.Base = Base; diff --git a/src/js/config.js b/src/js/config.js new file mode 100644 index 0000000000..372b7d261e --- /dev/null +++ b/src/js/config.js @@ -0,0 +1,3 @@ +// This object serves as a singleton to store config options + +module.exports = {}; diff --git a/src/js/currency.js b/src/js/currency.js new file mode 100644 index 0000000000..14e902159d --- /dev/null +++ b/src/js/currency.js @@ -0,0 +1,81 @@ + +// +// Currency support +// + +// XXX Internal form should be UInt160. +var Currency = function () { + // Internal form: 0 = XRP. 3 letter-code. + // XXX Internal should be 0 or hex with three letter annotation when valid. + + // Json form: + // '', 'XRP', '0': 0 + // 3-letter code: ... + // XXX Should support hex, C++ doesn't currently allow it. + + this._value = NaN; +} + +// Given "USD" return the json. +Currency.json_rewrite = function (j) { + return Currency.from_json(j).to_json(); +}; + +Currency.from_json = function (j) { + return 'string' === typeof j + ? (new Currency()).parse_json(j) + : j.clone(); +}; + +Currency.is_valid = function (j) { + return Currency.from_json(j).is_valid(); +}; + +Currency.prototype.clone = function() { + return this.copyTo(new Currency()); +}; + +// Returns copy. +Currency.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +Currency.prototype.equals = function (d) { + return ('string' !== typeof this._value && isNaN(this._value)) + || ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value; +}; + +// this._value = NaN on error. +Currency.prototype.parse_json = function (j) { + if ("" === j || "0" === j || "XRP" === j) { + this._value = 0; + } + else if ('string' != typeof j || 3 !== j.length) { + this._value = NaN; + } + else { + this._value = j; + } + + return this; +}; + +Currency.prototype.is_native = function () { + return !isNaN(this._value) && !this._value; +}; + +Currency.prototype.is_valid = function () { + return !isNaN(this._value); +}; + +Currency.prototype.to_json = function () { + return this._value ? this._value : "XRP"; +}; + +Currency.prototype.to_human = function () { + return this._value ? this._value : "XRP"; +}; + +exports.Currency = Currency; diff --git a/src/js/remote.js b/src/js/remote.js index 5832cced38..f34ed32878 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -22,6 +22,7 @@ var UInt160 = require('./amount').UInt160; var Transaction = require('./transaction').Transaction; var utils = require('./utils'); +var config = require('./config'); // Request events emitted: // 'success' : Request successful. @@ -272,12 +273,12 @@ var Remote = function (opts, trace) { Remote.prototype = new EventEmitter; Remote.from_config = function (obj, trace) { - var serverConfig = 'string' === typeof obj ? exports.config.servers[obj] : obj; + var serverConfig = 'string' === typeof obj ? config.servers[obj] : obj; var remote = new Remote(serverConfig, trace); - for (var account in exports.config.accounts) { - var accountInfo = exports.config.accounts[account]; + for (var account in config.accounts) { + var accountInfo = config.accounts[account]; if ("object" === typeof accountInfo) { if (accountInfo.secret) { // Index by nickname ... @@ -1191,7 +1192,6 @@ Remote.prototype.transaction = function () { return new Transaction(this); }; -exports.config = {}; exports.Remote = Remote; // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/seed.js b/src/js/seed.js new file mode 100644 index 0000000000..b26f2e2c67 --- /dev/null +++ b/src/js/seed.js @@ -0,0 +1,89 @@ +// +// Seed support +// + +var utils = require('./utils'); +var jsbn = require('./jsbn'); + +var BigInteger = jsbn.BigInteger; + +var Base = require('./base').Base; + +var Seed = function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}; + +Seed.json_rewrite = function (j) { + return Seed.from_json(j).to_json(); +}; + +// Return a new Seed from j. +Seed.from_json = function (j) { + return 'string' === typeof j + ? (new Seed()).parse_json(j) + : j.clone(); +}; + +Seed.is_valid = function (j) { + return Seed.from_json(j).is_valid(); +}; + +Seed.prototype.clone = function () { + return this.copyTo(new Seed()); +}; + +// Returns copy. +Seed.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +Seed.prototype.equals = function (d) { + return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); +}; + +Seed.prototype.is_valid = function () { + return this._value instanceof BigInteger; +}; + +// value = NaN on error. +// One day this will support rfc1751 too. +Seed.prototype.parse_json = function (j) { + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "s") { + this._value = Base.decode_check(Base.VER_FAMILY_SEED, j); + } + else if (16 === j.length) { + this._value = new BigInteger(utils.stringToArray(j), 128); + } + else { + this._value = NaN; + } + + return this; +}; + +// Convert from internal form. +Seed.prototype.to_json = function () { + if (!(this._value instanceof BigInteger)) + return NaN; + + var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; }); + var target = 20; + + // XXX Make sure only trim off leading zeros. + var array = bytes.length < target + ? bytes.length + ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) + : utils.arraySet(target, 0) + : bytes.slice(target - bytes.length); + var output = Base.encode_check(Base.VER_FAMILY_SEED, array); + + return output; +}; + +exports.Seed = Seed; diff --git a/src/js/uint160.js b/src/js/uint160.js new file mode 100644 index 0000000000..befe2f101c --- /dev/null +++ b/src/js/uint160.js @@ -0,0 +1,156 @@ + +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var Base = require('./base').Base; + +// +// UInt160 support +// + +var UInt160 = function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}; + +UInt160.ZERO = utils.hexToString("0000000000000000000000000000000000000000"); +UInt160.ONE = utils.hexToString("0000000000000000000000000000000000000001"); +var ADDRESS_ZERO = UInt160.ADDRESS_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp"; +var ADDRESS_ONE = UInt160.ADDRESS_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji"; +var HEX_ZERO = UInt160.HEX_ZERO = "0000000000000000000000000000000000000000"; +var HEX_ONE = UInt160.HEX_ONE = "0000000000000000000000000000000000000001"; + +UInt160.json_rewrite = function (j) { + return UInt160.from_json(j).to_json(); +}; + +// Return a new UInt160 from j. +UInt160.from_generic = function (j) { + return 'string' === typeof j + ? (new UInt160()).parse_generic(j) + : j.clone(); +}; + +// Return a new UInt160 from j. +UInt160.from_json = function (j) { + if ('string' === typeof j) { + return (new UInt160()).parse_json(j); + } else if (j instanceof UInt160) { + return j.clone(); + } else { + return new UInt160(); + } +}; + +UInt160.is_valid = function (j) { + return UInt160.from_json(j).is_valid(); +}; + +UInt160.prototype.clone = function () { + return this.copyTo(new UInt160()); +}; + +// Returns copy. +UInt160.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +UInt160.prototype.equals = function (d) { + return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); +}; + +UInt160.prototype.is_valid = function () { + return this._value instanceof BigInteger; +}; + +// value = NaN on error. +UInt160.prototype.parse_generic = function (j) { + // Canonicalize and validate + if (config.accounts && j in config.accounts) + j = config.accounts[j].account; + + switch (j) { + case undefined: + case "0": + case UInt160.ZERO: + case ADDRESS_ZERO: + case HEX_ZERO: + this._value = nbi(); + break; + + case "1": + case UInt160.ONE: + case ADDRESS_ONE: + case HEX_ONE: + this._value = new BigInteger([1]); + + break; + + default: + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "r") { + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + } + else if (20 === j.length) { + this._value = new BigInteger(utils.stringToArray(j), 256); + } + else if (40 === j.length) { + // XXX Check char set! + this._value = new BigInteger(j, 16); + } + else { + this._value = NaN; + } + } + + return this; +}; + +// value = NaN on error. +UInt160.prototype.parse_json = function (j) { + // Canonicalize and validate + if (config.accounts && j in config.accounts) + j = config.accounts[j].account; + + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "r") { + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + } + else { + this._value = NaN; + } + + return this; +}; + +// Convert from internal form. +// XXX Json form should allow 0 and 1, C++ doesn't currently allow it. +UInt160.prototype.to_json = function () { + if (!(this._value instanceof BigInteger)) + return NaN; + + var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); + var target = 20; + + // XXX Make sure only trim off leading zeros. + var array = bytes.length < target + ? bytes.length + ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) + : utils.arraySet(target, 0) + : bytes.slice(target - bytes.length); + var output = Base.encode_check(Base.VER_ACCOUNT_ID, array); + + return output; +}; + +exports.UInt160 = UInt160; diff --git a/test/amount-test.js b/test/amount-test.js index 71279cbeed..e5bec8e00d 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -8,7 +8,8 @@ var amount = require("../src/js/amount.js"); var Amount = require("../src/js/amount.js").Amount; var UInt160 = require("../src/js/amount.js").UInt160; -require("../src/js/amount.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); var config = require('./config.js'); @@ -20,16 +21,16 @@ buster.testCase("Amount", { buster.assert.equals(nbi(), UInt160.from_generic("0")._value); }, "Parse 0 export" : function () { - buster.assert.equals(amount.consts.address_xns, UInt160.from_generic("0").to_json()); + buster.assert.equals(UInt160.ADDRESS_ZERO, UInt160.from_generic("0").to_json()); }, "Parse 1" : function () { buster.assert.equals(new BigInteger([1]), UInt160.from_generic("1")._value); }, "Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export" : function () { - buster.assert.equals(amount.consts.address_xns, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); + buster.assert.equals(UInt160.ADDRESS_ZERO, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); }, "Parse rrrrrrrrrrrrrrrrrrrrBZbvji export" : function () { - buster.assert.equals(amount.consts.address_one, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json()); + buster.assert.equals(UInt160.ADDRESS_ONE, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json()); }, "Parse mtgox export" : function () { buster.assert.equals(config.accounts["mtgox"].account, UInt160.from_json("mtgox").to_json()); diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js index ffad70ba60..c1569ba5d5 100644 --- a/test/jsonrpc-test.js +++ b/test/jsonrpc-test.js @@ -12,8 +12,8 @@ var testutils = require("./testutils.js"); var config = require("./config.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; diff --git a/test/monitor-test.js b/test/monitor-test.js index daef55a658..861f4c15d6 100644 --- a/test/monitor-test.js +++ b/test/monitor-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/offer-test.js b/test/offer-test.js index de87c7d103..07122d1d8f 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -9,8 +9,8 @@ var Server = require("./server").Server; var testutils = require("./testutils"); -require("../src/js/amount").config = require("./config"); -require("../src/js/remote").config = require("./config"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/path-test.js b/test/path-test.js index 9cc0d7f82f..dbc72ef89c 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/remote-test.js b/test/remote-test.js index 75ade8be34..e54afde222 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -6,8 +6,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; // XXX Not implemented. diff --git a/test/send-test.js b/test/send-test.js index d2fe9c7f32..524841e862 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; diff --git a/test/testutils.js b/test/testutils.js index deddc3009f..3ef6d6354c 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -4,8 +4,8 @@ var Amount = require("../src/js/amount.js").Amount; var Remote = require("../src/js/remote.js").Remote; var Server = require("./server.js").Server; -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); var config = require("./config.js"); diff --git a/test/websocket-test.js b/test/websocket-test.js index 02d7e59f9b..5f15135cc4 100644 --- a/test/websocket-test.js +++ b/test/websocket-test.js @@ -4,7 +4,8 @@ var Server = require("./server.js").Server; var Remote = require("../src/js/remote.js").Remote; var config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; From eeb9598b12c629220aebd684a318d0431bfb9531 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 1 Feb 2013 22:35:50 +0100 Subject: [PATCH 14/21] Add debugging parameter to sign and submit RPC calls. --- src/cpp/ripple/RPCHandler.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index e6a245aad6..65bb7996b9 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -296,6 +296,11 @@ Json::Value RPCHandler::transactionSign(Json::Value jvRequest, bool bSubmit) return jvResult; } + if (jvRequest.isMember("debug_signing")) { + jvResult["tx_unsigned"] = strHex(stpTrans->getSerializer().peekData()); + jvResult["tx_signing_hash"] = stpTrans->getSigningHash().ToString(); + } + // FIXME: For performance, transactions should not be signed in this code path. stpTrans->sign(naAccountPrivate); From 54f4edf5ef50fc02d956690f9acc20dea6eb7495 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 1 Feb 2013 22:38:37 +0100 Subject: [PATCH 15/21] Create abstract UInt class for UInt160, UInt256, ... --- src/js/uint.js | 221 ++++++++++++++++++++++++++++++++++++++++++++++ src/js/uint160.js | 119 +++---------------------- src/js/uint256.js | 37 ++++++++ 3 files changed, 271 insertions(+), 106 deletions(-) create mode 100644 src/js/uint.js create mode 100644 src/js/uint256.js diff --git a/src/js/uint.js b/src/js/uint.js new file mode 100644 index 0000000000..b989b7e847 --- /dev/null +++ b/src/js/uint.js @@ -0,0 +1,221 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var Base = require('./base').Base; + +// +// Abstract UInt class +// +// Base class for UInt??? classes +// + +var UInt = function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}; + +UInt.json_rewrite = function (j) { + return this.from_json(j).to_json(); +}; + +// Return a new UInt from j. +UInt.from_generic = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_generic(j); + } +}; + +// Return a new UInt from j. +UInt.from_hex = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_hex(j); + } +}; + +// Return a new UInt from j. +UInt.from_json = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_json(j); + } +}; + +// Return a new UInt from j. +UInt.from_bits = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_bits(j); + } +}; + +// Return a new UInt from j. +UInt.from_bn = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_bn(j); + } +}; + +UInt.is_valid = function (j) { + return this.from_json(j).is_valid(); +}; + +UInt.prototype.clone = function () { + return this.copyTo(new this.constructor()); +}; + +// Returns copy. +UInt.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +UInt.prototype.equals = function (d) { + return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); +}; + +UInt.prototype.is_valid = function () { + return this._value instanceof BigInteger; +}; + +// value = NaN on error. +UInt.prototype.parse_generic = function (j) { + // Canonicalize and validate + if (config.accounts && j in config.accounts) + j = config.accounts[j].account; + + switch (j) { + case undefined: + case "0": + case this.constructor.STR_ZERO: + case this.constructor.ADDRESS_ZERO: + case this.constructor.HEX_ZERO: + this._value = nbi(); + break; + + case "1": + case this.constructor.STR_ONE: + case this.constructor.ADDRESS_ONE: + case this.constructor.HEX_ONE: + this._value = new BigInteger([1]); + + break; + + default: + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "r") { + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + } + else if (this.constructor.width === j.length) { + this._value = new BigInteger(utils.stringToArray(j), 256); + } + else if ((this.constructor.width*2) === j.length) { + // XXX Check char set! + this._value = new BigInteger(j, 16); + } + else { + this._value = NaN; + } + } + + return this; +}; + +UInt.prototype.parse_hex = function (j) { + if ('string' === typeof j && + j.length === (this.constructor.width * 2)) { + this._value = new BigInteger(j, 16); + } else { + this._value = NaN; + } + + return this; +}; + +UInt.prototype.parse_bits = function (j) { + if (sjcl.bitArray.bitLength(j) !== this.constructor.width * 8) { + this._value = NaN; + } else { + var bytes = sjcl.codec.bytes.fromBits(j); + this._value = new BigInteger(bytes, 256); + } + + return this; +}; + +UInt.prototype.parse_json = UInt.prototype.parse_hex; + +UInt.prototype.parse_bn = function (j) { + if (j instanceof sjcl.bn && + j.bitLength() <= this.constructor.width * 8) { + var bytes = sjcl.codec.bytes.fromBits(j.toBits()); + this._value = new BigInteger(bytes, 256); + } else { + this._value = NaN; + } + + return this; +}; + +// Convert from internal form. +UInt.prototype.to_bytes = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this._value.toByteArray(); + bytes = bytes.map(function (b) { return (b+256) % 256; }); + var target = this.constructor.width; + + // XXX Make sure only trim off leading zeros. + bytes = bytes.slice(-target); + while (bytes.length < target) bytes.unshift(0); + + return bytes; +}; + +UInt.prototype.to_hex = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this.to_bytes(); + + return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(bytes)).toUpperCase(); +}; + +UInt.prototype.to_json = UInt.prototype.to_hex; + +UInt.prototype.to_bits = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this.to_bytes(); + + return sjcl.codec.bytes.toBits(bytes); +}; + +UInt.prototype.to_bn = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bits = this.to_bits(); + + return sjcl.bn.fromBits(bits); +}; + +exports.UInt = UInt; diff --git a/src/js/uint160.js b/src/js/uint160.js index befe2f101c..b9165e34c3 100644 --- a/src/js/uint160.js +++ b/src/js/uint160.js @@ -1,118 +1,35 @@ +var sjcl = require('../../build/sjcl'); var utils = require('./utils'); var config = require('./config'); var jsbn = require('./jsbn'); +var extend = require('extend'); var BigInteger = jsbn.BigInteger; var nbi = jsbn.nbi; -var Base = require('./base').Base; +var UInt = require('./uint').UInt, + Base = require('./base').Base; // // UInt160 support // -var UInt160 = function () { +var UInt160 = extend(function () { // Internal form: NaN or BigInteger this._value = NaN; -}; +}, UInt); + +UInt160.width = 20; +UInt160.prototype = extend({}, UInt.prototype); +UInt160.prototype.constructor = UInt160; -UInt160.ZERO = utils.hexToString("0000000000000000000000000000000000000000"); -UInt160.ONE = utils.hexToString("0000000000000000000000000000000000000001"); var ADDRESS_ZERO = UInt160.ADDRESS_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp"; var ADDRESS_ONE = UInt160.ADDRESS_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji"; var HEX_ZERO = UInt160.HEX_ZERO = "0000000000000000000000000000000000000000"; var HEX_ONE = UInt160.HEX_ONE = "0000000000000000000000000000000000000001"; - -UInt160.json_rewrite = function (j) { - return UInt160.from_json(j).to_json(); -}; - -// Return a new UInt160 from j. -UInt160.from_generic = function (j) { - return 'string' === typeof j - ? (new UInt160()).parse_generic(j) - : j.clone(); -}; - -// Return a new UInt160 from j. -UInt160.from_json = function (j) { - if ('string' === typeof j) { - return (new UInt160()).parse_json(j); - } else if (j instanceof UInt160) { - return j.clone(); - } else { - return new UInt160(); - } -}; - -UInt160.is_valid = function (j) { - return UInt160.from_json(j).is_valid(); -}; - -UInt160.prototype.clone = function () { - return this.copyTo(new UInt160()); -}; - -// Returns copy. -UInt160.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -UInt160.prototype.equals = function (d) { - return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); -}; - -UInt160.prototype.is_valid = function () { - return this._value instanceof BigInteger; -}; - -// value = NaN on error. -UInt160.prototype.parse_generic = function (j) { - // Canonicalize and validate - if (config.accounts && j in config.accounts) - j = config.accounts[j].account; - - switch (j) { - case undefined: - case "0": - case UInt160.ZERO: - case ADDRESS_ZERO: - case HEX_ZERO: - this._value = nbi(); - break; - - case "1": - case UInt160.ONE: - case ADDRESS_ONE: - case HEX_ONE: - this._value = new BigInteger([1]); - - break; - - default: - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "r") { - this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); - } - else if (20 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 256); - } - else if (40 === j.length) { - // XXX Check char set! - this._value = new BigInteger(j, 16); - } - else { - this._value = NaN; - } - } - - return this; -}; +var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO); +var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE); // value = NaN on error. UInt160.prototype.parse_json = function (j) { @@ -133,22 +50,12 @@ UInt160.prototype.parse_json = function (j) { return this; }; -// Convert from internal form. // XXX Json form should allow 0 and 1, C++ doesn't currently allow it. UInt160.prototype.to_json = function () { if (!(this._value instanceof BigInteger)) return NaN; - var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var target = 20; - - // XXX Make sure only trim off leading zeros. - var array = bytes.length < target - ? bytes.length - ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) - : utils.arraySet(target, 0) - : bytes.slice(target - bytes.length); - var output = Base.encode_check(Base.VER_ACCOUNT_ID, array); + var output = Base.encode_check(Base.VER_ACCOUNT_ID, this.to_bytes()); return output; }; diff --git a/src/js/uint256.js b/src/js/uint256.js new file mode 100644 index 0000000000..ab5b6f70b6 --- /dev/null +++ b/src/js/uint256.js @@ -0,0 +1,37 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var UInt = require('./uint').UInt, + Base = require('./base').Base; + +// +// UInt256 support +// + +var UInt256 = extend(function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}, UInt); + +UInt256.width = 32; +UInt256.prototype = extend({}, UInt.prototype); +UInt256.prototype.constructor = UInt256; + +// XXX Generate these constants (or remove them) +var ADDRESS_ZERO = UInt256.ADDRESS_ZERO = "XXX"; +var ADDRESS_ONE = UInt256.ADDRESS_ONE = "XXX"; +var HEX_ZERO = UInt256.HEX_ZERO = "00000000000000000000000000000000" + + "00000000000000000000000000000000"; +var HEX_ONE = UInt256.HEX_ONE = "00000000000000000000000000000000" + + "00000000000000000000000000000001"; +var STR_ZERO = UInt256.STR_ZERO = utils.hexToString(HEX_ZERO); +var STR_ONE = UInt256.STR_ONE = utils.hexToString(HEX_ONE); + +exports.UInt256 = UInt256; From 6820c6823d39fbadf9b8c8c105652fe572447bfc Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 1 Feb 2013 22:43:01 +0100 Subject: [PATCH 16/21] Add transaction signing basics. Most fields aren't supported yet and there aren't any nice external APIs, but my test script successfully serializes and signs an XRP Payment, so this seems like a good time to make a commit. --- grunt.js | 3 +- src/js/remote.js | 16 ++- src/js/seed.js | 48 ++++++++- src/js/serializedobject.js | 144 +++++++++++++++++++++++++++ src/js/serializedtypes.js | 124 +++++++++++++++++++++-- src/js/serializer.js | 44 -------- src/js/sjcl-custom/sjcl-ecdsa-der.js | 28 ++++++ src/js/transaction.js | 61 +++++++++--- src/js/utils.js | 6 +- 9 files changed, 401 insertions(+), 73 deletions(-) create mode 100644 src/js/serializedobject.js delete mode 100644 src/js/serializer.js create mode 100644 src/js/sjcl-custom/sjcl-ecdsa-der.js diff --git a/grunt.js b/grunt.js index fdb94be2b0..dab1bcef57 100644 --- a/grunt.js +++ b/grunt.js @@ -35,7 +35,8 @@ module.exports = function(grunt) { "src/js/sjcl/core/srp.js", "src/js/sjcl-custom/sjcl-secp256k1.js", "src/js/sjcl-custom/sjcl-ripemd160.js", - "src/js/sjcl-custom/sjcl-extramath.js" + "src/js/sjcl-custom/sjcl-extramath.js", + "src/js/sjcl-custom/sjcl-ecdsa-der.js" ], dest: 'build/sjcl.js' } diff --git a/src/js/remote.js b/src/js/remote.js index f34ed32878..23c2296e8a 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -851,6 +851,17 @@ Remote.prototype.request_wallet_accounts = function (seed) { return request; }; +Remote.prototype.request_sign = function (secret, tx_json) { + utils.assert(this.trusted); // Don't send secrets. + + var request = new Request(this, 'sign'); + + request.message.secret = secret; + request.message.tx_json = tx_json; + + return request; +}; + // Submit a transaction. Remote.prototype.submit = function (transaction) { var self = this; @@ -1134,7 +1145,9 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, request.message.source_account = UInt160.json_rewrite(src_account); request.message.destination_account = UInt160.json_rewrite(dst_account); request.message.destination_amount = Amount.json_rewrite(dst_amount); - request.message.source_currencies = source_currencies.map(function (ci) { + + if (source_currencies) { + request.message.source_currencies = source_currencies.map(function (ci) { var ci_new = {}; if ('issuer' in ci) @@ -1145,6 +1158,7 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, return ci_new; }); + } return request; }; diff --git a/src/js/seed.js b/src/js/seed.js index b26f2e2c67..ee519566cf 100644 --- a/src/js/seed.js +++ b/src/js/seed.js @@ -2,12 +2,14 @@ // Seed support // +var sjcl = require('../../build/sjcl'); var utils = require('./utils'); var jsbn = require('./jsbn'); var BigInteger = jsbn.BigInteger; -var Base = require('./base').Base; +var Base = require('./base').Base, + UInt256 = require('./uint256').UInt256; var Seed = function () { // Internal form: NaN or BigInteger @@ -20,9 +22,9 @@ Seed.json_rewrite = function (j) { // Return a new Seed from j. Seed.from_json = function (j) { - return 'string' === typeof j - ? (new Seed()).parse_json(j) - : j.clone(); + return (j instanceof Seed) + ? j.clone() + : (new Seed()).parse_json(j); }; Seed.is_valid = function (j) { @@ -86,4 +88,42 @@ Seed.prototype.to_json = function () { return output; }; +function append_int(a, i) { + return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff); +} + +function firstHalfOfSHA512(bytes) { + return sjcl.bitArray.bitSlice( + sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)), + 0, 256 + ); +} + +function SHA256_RIPEMD160(bits) { + return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits)); +} + +Seed.prototype.generate_private = function (account_id) { + // XXX If account_id is given, should loop over keys until we find the right one + + var seq = 0; + + var private_gen, public_gen, i = 0; + do { + private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i))); + i++; + } while (!sjcl.ecc.curves.c256.r.greaterEquals(private_gen)); + + public_gen = sjcl.ecc.curves.c256.G.mult(private_gen); + + var sec; + i = 0; + do { + sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i))); + i++; + } while (!sjcl.ecc.curves.c256.r.greaterEquals(sec)); + + return UInt256.from_bn(sec); +}; + exports.Seed = Seed; diff --git a/src/js/serializedobject.js b/src/js/serializedobject.js new file mode 100644 index 0000000000..e66a2001ac --- /dev/null +++ b/src/js/serializedobject.js @@ -0,0 +1,144 @@ +var binformat = require('./binformat'), + sjcl = require('../../build/sjcl'), + extend = require('extend'), + stypes = require('./serializedtypes'); + +var UInt256 = require('./uint256').UInt256; + +var SerializedObject = function () { + this.buffer = []; + this.pointer = 0; +}; + +SerializedObject.from_json = function (obj) { + var typedef; + var so = new SerializedObject(); + + // Create a copy of the object so we don't modify it + obj = extend({}, obj); + + if ("number" === typeof obj.TransactionType) { + obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType); + + if (!obj.TransactionType) { + throw new Error("Transaction type ID is invalid."); + } + } + + if ("string" === typeof obj.TransactionType) { + typedef = binformat.tx[obj.TransactionType].slice(); + + obj.TransactionType = typedef.shift(); + } else if ("undefined" !== typeof obj.LedgerEntryType) { + // XXX: TODO + throw new Error("Ledger entry binary format not yet implemented."); + } else throw new Error("Object to be serialized must contain either " + + "TransactionType or LedgerEntryType."); + + so.serialize(typedef, obj); + + return so; +}; + +SerializedObject.prototype.append = function (bytes) { + this.buffer = this.buffer.concat(bytes); + this.pointer += bytes.length; +}; + +SerializedObject.prototype.to_bits = function () +{ + return sjcl.codec.bytes.toBits(this.buffer); +}; + +SerializedObject.prototype.to_hex = function () { + return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase(); +}; + +SerializedObject.prototype.serialize = function (typedef, obj) +{ + // Ensure canonical order + typedef = SerializedObject._sort_typedef(typedef.slice()); + + // Serialize fields + for (var i = 0, l = typedef.length; i < l; i++) { + var spec = typedef[i]; + this.serialize_field(spec, obj); + } +}; + +SerializedObject.prototype.signing_hash = function (prefix) +{ + var sign_buffer = new SerializedObject(); + stypes.Int32.serialize(sign_buffer, prefix); + sign_buffer.append(this.buffer); + return sign_buffer.hash_sha512_half(); +}; + +SerializedObject.prototype.hash_sha512_half = function () +{ + var bits = sjcl.codec.bytes.toBits(this.buffer), + hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256); + + return UInt256.from_hex(sjcl.codec.hex.fromBits(hash)); +}; + +SerializedObject.prototype.serialize_field = function (spec, obj) +{ + spec = spec.slice(); + + var name = spec.shift(), + presence = spec.shift(), + field_id = spec.shift(), + Type = spec.shift(); + + if ("undefined" !== typeof obj[name]) { + console.log(name, Type.id, field_id); + this.append(SerializedObject.get_field_header(Type.id, field_id)); + + try { + Type.serialize(this, obj[name]); + } catch (e) { + // Add field name to message and rethrow + e.message = "Error serializing '"+name+"': "+e.message; + throw e; + } + } else if (presence === binformat.REQUIRED) { + throw new Error('Missing required field '+name); + } +}; + +SerializedObject.get_field_header = function (type_id, field_id) +{ + var buffer = [0]; + if (type_id > 0xf) buffer.push(type_id & 0xff); + else buffer[0] += (type_id & 0xf) << 4; + + if (field_id > 0xf) buffer.push(field_id & 0xff); + else buffer[0] += field_id & 0xf; + + return buffer; +}; + +function sort_field_compare(a, b) { + // Sort by type id first, then by field id + return a[3].id !== b[3].id ? + a[3].id - b[3].id : + a[2] - b[2]; +}; +SerializedObject._sort_typedef = function (typedef) { + return typedef.sort(sort_field_compare); +}; + +SerializedObject.lookup_type_tx = function (id) { + for (var i in binformat.tx) { + if (!binformat.tx.hasOwnProperty(i)) continue; + + if (binformat.tx[i][0] === id) { + return i; + } + } + + return null; +}; + +exports.SerializedObject = SerializedObject; diff --git a/src/js/serializedtypes.js b/src/js/serializedtypes.js index 11acd294f2..2c61b64b36 100644 --- a/src/js/serializedtypes.js +++ b/src/js/serializedtypes.js @@ -1,10 +1,55 @@ -var SerializedType = function () { +/** + * Type definitions for binary format. + * + * This file should not be included directly. Instead, find the format you're + * trying to parse or serialize in binformat.js and pass that to + * SerializedObject.parse() or SerializedObject.serialize(). + */ +var extend = require('extend'), + utils = require('./utils'), + sjcl = require('../../build/sjcl'); + +var amount = require('./amount'), + UInt160 = amount.UInt160, + Amount = amount.Amount; + +// Shortcuts +var hex = sjcl.codec.hex, + bytes = sjcl.codec.bytes; + +var SerializedType = function (methods) { + extend(this, methods); +}; + +SerializedType.prototype.serialize_hex = function (so, hexData) { + var byteData = bytes.fromBits(hex.toBits(hexData)); + this.serialize_varint(so, byteData.length); + so.append(byteData); +}; + +SerializedType.prototype.serialize_varint = function (so, val) { + if (val < 0) { + throw new Error("Variable integers are unsigned."); + } + if (val <= 192) { + so.append([val]); + } else if (val <= 12,480) { + val -= 193; + so.append([193 + (val >>> 8), val & 0xff]); + } else if (val <= 918744) { + val -= 12481; + so.append([ + 241 + (val >>> 16), + val >>> 8 & 0xff, + val & 0xff + ]); + } else throw new Error("Variable integer overflow."); }; exports.Int8 = new SerializedType({ serialize: function (so, val) { - return so.append([val & 0xff]); + so.append([val & 0xff]); }, parse: function (so) { return so.read(1)[0]; @@ -13,7 +58,10 @@ exports.Int8 = new SerializedType({ exports.Int16 = new SerializedType({ serialize: function (so, val) { - // XXX + so.append([ + val >>> 8 & 0xff, + val & 0xff + ]); }, parse: function (so) { // XXX @@ -22,7 +70,12 @@ exports.Int16 = new SerializedType({ exports.Int32 = new SerializedType({ serialize: function (so, val) { - // XXX + so.append([ + val >>> 24 & 0xff, + val >>> 16 & 0xff, + val >>> 8 & 0xff, + val & 0xff + ]); }, parse: function (so) { // XXX @@ -67,7 +120,62 @@ exports.Hash160 = new SerializedType({ exports.Amount = new SerializedType({ serialize: function (so, val) { - // XXX + var amount = Amount.from_json(val); + if (!amount.is_valid()) { + throw new Error("Not a valid Amount object."); + } + + // Amount (64-bit integer) + if (amount.is_native()) { + var valueHex = amount._value.toString(16); + + // Enforce correct length (64 bits) + if (valueHex.length > 16) { + throw new Error('Value out of bounds'); + } + while (valueHex.length < 16) { + valueHex = "0" + valueHex; + } + + var valueBytes = bytes.fromBits(hex.toBits(valueHex)); + // Clear most significant two bits - these bits should already be 0 if + // Amount enforces the range correctly, but we'll clear them anyway just + // so this code can make certain guarantees about the encoded value. + valueBytes[0] &= 0x3f; + if (!amount.is_negative()) valueBytes[0] |= 0x40; + + so.append(valueBytes); + } else { + // XXX + throw new Error("Non-native amounts not implemented!"); + } + + if (!amount.is_native()) { + // Currency (160-bit hash) + var currency = amount.currency().to_json(); + if ("string" === typeof currency && currency.length === 3) { + var currencyCode = currency.toUpperCase(), + currencyData = utils.arraySet(20, 0); + + if (!/^[A-Z]{3}$/.test(currencyCode)) { + throw new Error('Invalid currency code'); + } + + currencyData[12] = currencyCode.charCodeAt(0) & 0xff; + currencyData[13] = currencyCode.charCodeAt(1) & 0xff; + currencyData[14] = currencyCode.charCodeAt(2) & 0xff; + + var currencyBits = bytes.toBits(currencyData), + currencyHash = sjcl.hash.ripemd160.hash(currencyBits); + + so.append(bytes.fromBits(currencyHash)); + } else { + throw new Error('Tried to serialize invalid/unimplemented currency type.'); + } + + // Issuer (160-bit hash) + // XXX + } }, parse: function (so) { // XXX @@ -76,7 +184,8 @@ exports.Amount = new SerializedType({ exports.VariableLength = new SerializedType({ serialize: function (so, val) { - // XXX + if ("string" === typeof val) this.serialize_hex(so, val); + else throw new Error("Unknown datatype."); }, parse: function (so) { // XXX @@ -85,7 +194,8 @@ exports.VariableLength = new SerializedType({ exports.Account = new SerializedType({ serialize: function (so, val) { - // XXX + var account = UInt160.from_json(val); + this.serialize_hex(so, account.to_hex()); }, parse: function (so) { // XXX diff --git a/src/js/serializer.js b/src/js/serializer.js deleted file mode 100644 index 6a75e84ef7..0000000000 --- a/src/js/serializer.js +++ /dev/null @@ -1,44 +0,0 @@ -// - -var serializer = {}; - -serializer.addUInt16 = function(value) { - switch (typeof value) { - case 'string': - addUInt16(value.charCodeAt(0)); - break; - - case 'integer': - for (i = 16/8; i; i -=1) { - raw.push(value & 255); - value >>= 8; - } - break; - - default: - throw 'UNEXPECTED_TYPE'; - } -}; - -serializer.addUInt160 = function(value) { - switch (typeof value) { - case 'array': - raw.concat(value); - break; - - case 'integer': - for (i = 160/8; i; i -=1) { - raw.push(value & 255); - value >>= 8; - } - break; - - default: - throw 'UNEXPECTED_TYPE'; - } -}; - -serializer.getSHA512Half = function() { -}; - -// vim:sw=2:sts=2:ts=8:et diff --git a/src/js/sjcl-custom/sjcl-ecdsa-der.js b/src/js/sjcl-custom/sjcl-ecdsa-der.js new file mode 100644 index 0000000000..befddbf084 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-ecdsa-der.js @@ -0,0 +1,28 @@ +sjcl.ecc.ecdsa.secretKey.prototype.signDER = function(hash, paranoia) { + return this.encodeDER(this.sign(hash, paranoia)); +}; + +sjcl.ecc.ecdsa.secretKey.prototype.encodeDER = function(rs) { + var w = sjcl.bitArray, + R = this._curve.r, + l = R.bitLength(), + r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)).toBits(), + s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)).toBits(); + + var rb = sjcl.codec.bytes.fromBits(r), + sb = sjcl.codec.bytes.fromBits(s); + + var buffer = [].concat( + 0x30, + 4 + rb.length + sb.length, + 0x02, + rb.length, + rb, + 0x02, + sb.length, + sb + ); + + return sjcl.codec.bytes.toBits(buffer); +}; + diff --git a/src/js/transaction.js b/src/js/transaction.js index 3fedb3dca6..a2ad368a38 100644 --- a/src/js/transaction.js +++ b/src/js/transaction.js @@ -43,10 +43,16 @@ // - may or may not forward. // -var Amount = require('./amount').Amount; -var Currency = require('./amount').Currency; -var UInt160 = require('./amount').UInt160; -var EventEmitter = require('events').EventEmitter; +var sjcl = require('../../build/sjcl'); + +var Amount = require('./amount').Amount; +var Currency = require('./amount').Currency; +var UInt160 = require('./amount').UInt160; +var Seed = require('./seed').Seed; +var EventEmitter = require('events').EventEmitter; +var SerializedObject = require('./serializedobject').SerializedObject; + +var config = require('./config'); var SUBMIT_MISSING = 4; // Report missing. var SUBMIT_LOST = 8; // Give up tracking. @@ -112,6 +118,11 @@ Transaction.flags = { }, }; +Transaction.formats = require('./binformat').tx; + +Transaction.HASH_SIGN = 0x53545800; +Transaction.HASH_SIGN_TESTNET = 0x73747800; + Transaction.prototype.consts = { 'telLOCAL_ERROR' : -399, 'temMALFORMED' : -299, @@ -156,6 +167,30 @@ Transaction.prototype.set_state = function (state) { } }; +Transaction.prototype.serialize = function () { + return SerializedObject.from_json(this.tx_json); +}; + +Transaction.prototype.signing_hash = function () { + var prefix = config.testnet + ? Transaction.HASH_SIGN_TESTNET + : Transaction.HASH_SIGN; + + return SerializedObject.from_json(this.tx_json).signing_hash(prefix); +}; + +Transaction.prototype.sign = function () { + var seed = Seed.from_json(this._secret), + priv = seed.generate_private(this.tx_json.Account), + hash = this.signing_hash(); + + var key = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], priv.to_bn()), + sig = key.signDER(hash.to_bits(), 0), + hex = sjcl.codec.hex.fromBits(sig).toUpperCase(); + + this.tx_json.TxnSignature = hex; +}; + // Submit a transaction to the network. // XXX Don't allow a submit without knowing ledger_index. // XXX Have a network canSubmit(), post events for following. @@ -355,21 +390,21 @@ Transaction.prototype.transfer_rate = function (rate) { // --> flags: undefined, _flag_, or [ _flags_ ] Transaction.prototype.set_flags = function (flags) { if (flags) { - var transaction_flags = Transaction.flags[this.tx_json.TransactionType]; + var transaction_flags = Transaction.flags[this.tx_json.TransactionType]; if (undefined == this.tx_json.Flags) // We plan to not define this field on new Transaction. this.tx_json.Flags = 0; - var flag_set = 'object' === typeof flags ? flags : [ flags ]; + var flag_set = 'object' === typeof flags ? flags : [ flags ]; - for (index in flag_set) { - var flag = flag_set[index]; + for (var index in flag_set) { + if (!flag_set.hasOwnProperty(index)) continue; - if (flag in transaction_flags) - { - this.tx_json.Flags += transaction_flags[flag]; - } - else { + var flag = flag_set[index]; + + if (flag in transaction_flags) { + this.tx_json.Flags += transaction_flags[flag]; + } else { // XXX Immediately report an error or mark it. } } diff --git a/src/js/utils.js b/src/js/utils.js index d337763a67..d1779faf9a 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -27,11 +27,11 @@ var trace = function(comment, func) { }; var arraySet = function (count, value) { - var a = new Array(count); - var i; + var i, a = new Array(count); - for (i = 0; i != count; i += 1) + for (i = 0; i < count; i++) { a[i] = value; + } return a; }; From 6f619835e26538a4d7f51a69cb897a0cd7f5dc02 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 1 Feb 2013 15:36:07 -0800 Subject: [PATCH 17/21] Regular expressions are very Zen. It always matches something, even if that something is nothing. Nothing is a form of something. --- src/cpp/ripple/UniqueNodeList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpp/ripple/UniqueNodeList.cpp b/src/cpp/ripple/UniqueNodeList.cpp index ff5622cb2c..fdef96ef65 100644 --- a/src/cpp/ripple/UniqueNodeList.cpp +++ b/src/cpp/ripple/UniqueNodeList.cpp @@ -104,7 +104,7 @@ void UniqueNodeList::trustedLoad() { RippleAddress a = RippleAddress::createNodePublic(match[1]); if (a.isValid()) - sClusterNodes.insert(std::make_pair(a, (match.size() > 1) ? match[2] : std::string(""))); + sClusterNodes.insert(std::make_pair(a, match[2])); } else cLog(lsWARNING) << "Entry in cluster list invalid: '" << c << "'"; From 9781c10736b4dfba39b706b161b9430d1343fe73 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 1 Feb 2013 16:11:32 -0800 Subject: [PATCH 18/21] Fix some cases where ledger flags are incorrectly set. --- src/cpp/ripple/Ledger.cpp | 36 ++++++++++++++++++++++++-------- src/cpp/ripple/Ledger.h | 2 +- src/cpp/ripple/LedgerAcquire.cpp | 5 ++--- src/cpp/ripple/LedgerHistory.cpp | 2 +- src/cpp/ripple/LedgerMaster.cpp | 6 ++++++ src/cpp/ripple/LedgerMaster.h | 1 + src/cpp/ripple/NetworkOPs.cpp | 5 +++++ src/cpp/ripple/NetworkOPs.h | 1 + 8 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index 406a5f4e56..0cb2f0b2a4 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -113,14 +113,31 @@ Ledger::Ledger(const std::string& rawLedger, bool hasPrefix) : zeroFees(); } +void Ledger::setImmutable() +{ + if (!mImmutable) + { + updateHash(); + mImmutable = true; + if (mTransactionMap) + mTransactionMap->setImmutable(); + if (mAccountStateMap) + mAccountStateMap->setImmutable(); + } +} + void Ledger::updateHash() { if (!mImmutable) { - if (mTransactionMap) mTransHash = mTransactionMap->getHash(); - else mTransHash.zero(); - if (mAccountStateMap) mAccountHash = mAccountStateMap->getHash(); - else mAccountHash.zero(); + if (mTransactionMap) + mTransHash = mTransactionMap->getHash(); + else + mTransHash.zero(); + if (mAccountStateMap) + mAccountHash = mAccountStateMap->getHash(); + else + mAccountHash.zero(); } Serializer s(118); @@ -170,18 +187,17 @@ void Ledger::setAccepted(uint32 closeTime, int closeResolution, bool correctClos mCloseTime = correctCloseTime ? (closeTime - (closeTime % closeResolution)) : closeTime; mCloseResolution = closeResolution; mCloseFlags = correctCloseTime ? 0 : sLCF_NoConsensusTime; - updateHash(); mAccepted = true; - mImmutable = true; + setImmutable(); } void Ledger::setAccepted() { // used when we acquired the ledger // FIXME assert(mClosed && (mCloseTime != 0) && (mCloseResolution != 0)); - mCloseTime -= mCloseTime % mCloseResolution; - updateHash(); + if ((mCloseFlags & sLCF_NoConsensusTime) == 0) + mCloseTime -= mCloseTime % mCloseResolution; mAccepted = true; - mImmutable = true; + setImmutable(); } AccountState::pointer Ledger::getAccountState(const RippleAddress& accountID) @@ -527,6 +543,8 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) Ledger::pointer ret = boost::make_shared(prevHash, transHash, accountHash, totCoins, closingTime, prevClosingTime, closeFlags, closeResolution, ledgerSeq); ret->setClosed(); + if (theApp->getOPs().haveLedger(ledgerSeq)) + ret->setAccepted(); if (ret->getHash() != ledgerHash) { if (sLog(lsERROR)) diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index 7b11adc26d..71ede9357f 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -124,7 +124,7 @@ public: void setClosed() { mClosed = true; } void setAccepted(uint32 closeTime, int closeResolution, bool correctCloseTime); void setAccepted(); - void setImmutable() { updateHash(); mImmutable = true; } + void setImmutable(); bool isClosed() { return mClosed; } bool isAccepted() { return mAccepted; } bool isImmutable() { return mImmutable; } diff --git a/src/cpp/ripple/LedgerAcquire.cpp b/src/cpp/ripple/LedgerAcquire.cpp index a17cc6ef68..75a2e42436 100644 --- a/src/cpp/ripple/LedgerAcquire.cpp +++ b/src/cpp/ripple/LedgerAcquire.cpp @@ -160,6 +160,7 @@ bool LedgerAcquire::tryLocal() { cLog(lsDEBUG) << "Had everything locally"; mComplete = true; + mLedger->setClosed(); } return mComplete; @@ -244,11 +245,9 @@ void LedgerAcquire::done() if (isComplete() && !isFailed() && mLedger) { + mLedger->setClosed(); if (mAccept) - { - mLedger->setClosed(); mLedger->setAccepted(); - } theApp->getLedgerMaster().storeLedger(mLedger); } else diff --git a/src/cpp/ripple/LedgerHistory.cpp b/src/cpp/ripple/LedgerHistory.cpp index c0b6037a98..420b442558 100644 --- a/src/cpp/ripple/LedgerHistory.cpp +++ b/src/cpp/ripple/LedgerHistory.cpp @@ -82,7 +82,7 @@ Ledger::pointer LedgerHistory::getLedgerByHash(const uint256& hash) if (ret) { assert(ret->isImmutable()); - assert(ret->getHash() == hash); + assert(ret->getHash() == hash); // FIXME: We seem to be getting these return ret; } diff --git a/src/cpp/ripple/LedgerMaster.cpp b/src/cpp/ripple/LedgerMaster.cpp index 553a4e1473..63be1e8f9d 100644 --- a/src/cpp/ripple/LedgerMaster.cpp +++ b/src/cpp/ripple/LedgerMaster.cpp @@ -133,6 +133,12 @@ bool LedgerMaster::haveLedgerRange(uint32 from, uint32 to) return (prevMissing == RangeSet::RangeSetAbsent) || (prevMissing < from); } +bool LedgerMaster::haveLedger(uint32 seq) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + return mCompleteLedgers.hasValue(seq); +} + void LedgerMaster::asyncAccept(Ledger::pointer ledger) { uint32 seq = ledger->getLedgerSeq(); diff --git a/src/cpp/ripple/LedgerMaster.h b/src/cpp/ripple/LedgerMaster.h index dfcf8ef2e8..e3cb37d98a 100644 --- a/src/cpp/ripple/LedgerMaster.h +++ b/src/cpp/ripple/LedgerMaster.h @@ -126,6 +126,7 @@ public: void fixMismatch(Ledger::ref ledger); bool haveLedgerRange(uint32 from, uint32 to); + bool haveLedger(uint32 seq); void resumeAcquiring(); diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index 3fbf0768b2..4816d3622a 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -113,6 +113,11 @@ bool NetworkOPs::haveLedgerRange(uint32 from, uint32 to) return mLedgerMaster->haveLedgerRange(from, to); } +bool NetworkOPs::haveLedger(uint32 seq) +{ + return mLedgerMaster->haveLedger(seq); +} + bool NetworkOPs::addWantedHash(const uint256& h) { boost::recursive_mutex::scoped_lock sl(mWantedHashLock); diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index 0979671fb7..d4bf0bee81 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -150,6 +150,7 @@ public: // Do we have this inclusive range of ledgers in our database bool haveLedgerRange(uint32 from, uint32 to); + bool haveLedger(uint32 seq); SerializedValidation::ref getLastValidation() { return mLastValidation; } void setLastValidation(SerializedValidation::ref v) { mLastValidation = v; } From 402f7539d4559642740d9b05a3d0f99c82940e24 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 1 Feb 2013 16:47:08 -0800 Subject: [PATCH 19/21] Rounding, w/ unit test. --- src/cpp/ripple/Amount.cpp | 45 ++++++++++++++++++++++++++++---- src/cpp/ripple/SerializedTypes.h | 3 +++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/cpp/ripple/Amount.cpp b/src/cpp/ripple/Amount.cpp index 0fa87c3bc1..8272f5fadf 100644 --- a/src/cpp/ripple/Amount.cpp +++ b/src/cpp/ripple/Amount.cpp @@ -1215,6 +1215,40 @@ std::string STAmount::getFullText() const } } +STAmount STAmount::getRound() const +{ + if (mIsNative) + return *this; + + uint64 valueDigits = mValue % 1000000000ull; + if (valueDigits == 1) + return STAmount(mCurrency, mIssuer, mValue - 1, mOffset, mIsNegative); + else if (valueDigits == 999999999ull) + return STAmount(mCurrency, mIssuer, mValue + 1, mOffset, mIsNegative); + + return *this; +} + +void STAmount::roundSelf() +{ + if (mIsNative) + return; + + uint64 valueDigits = mValue % 1000000000ull; + if (valueDigits == 1) + { + mValue -= 1; + if (mValue < cMinValue) + canonicalize(); + } + else if (valueDigits == 999999999ull) + { + mValue += 1; + if (mValue > cMaxValue) + canonicalize(); + } +} + #if 0 std::string STAmount::getExtendedText() const { @@ -1461,7 +1495,7 @@ BOOST_AUTO_TEST_CASE( CustomCurrency_test ) BOOST_TEST_MESSAGE("Amount CC Complete"); } -static void roundTest(int n, int d, int m) +static bool roundTest(int n, int d, int m) { // check STAmount rounding STAmount num(CURRENCY_ONE, ACCOUNT_ONE, n); STAmount den(CURRENCY_ONE, ACCOUNT_ONE, d); @@ -1470,18 +1504,19 @@ static void roundTest(int n, int d, int m) STAmount res = STAmount::multiply(quot, mul, CURRENCY_ONE, ACCOUNT_ONE); if (res.isNative()) BOOST_FAIL("Product is native"); - - cLog(lsDEBUG) << n << " / " << d << " = " << quot.getText(); + res.roundSelf(); STAmount cmp(CURRENCY_ONE, ACCOUNT_ONE, (n * m) / d); if (cmp.isNative()) BOOST_FAIL("Comparison amount is native"); if (res == cmp) - return; + return true; cmp.throwComparable(res); - cLog(lsINFO) << "(" << num.getText() << "/" << den.getText() << ") X " << mul.getText() << " = " + cLog(lsWARNING) << "(" << num.getText() << "/" << den.getText() << ") X " << mul.getText() << " = " << res.getText() << " not " << cmp.getText(); + BOOST_FAIL("Round fail"); + return false; } static void mulTest(int a, int b) diff --git a/src/cpp/ripple/SerializedTypes.h b/src/cpp/ripple/SerializedTypes.h index c6bb32affd..fd0c2fc6fd 100644 --- a/src/cpp/ripple/SerializedTypes.h +++ b/src/cpp/ripple/SerializedTypes.h @@ -387,6 +387,9 @@ public: static bool issuerFromString(uint160& uDstIssuer, const std::string& sIssuer); Json::Value getJson(int) const; + + STAmount getRound() const; + void roundSelf(); }; extern STAmount saZero; From 1012833991ae711b19a6794845e68803ddffa280 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 1 Feb 2013 18:09:21 -0800 Subject: [PATCH 20/21] Additional override. --- src/cpp/ripple/AutoSocket.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/cpp/ripple/AutoSocket.h b/src/cpp/ripple/AutoSocket.h index 1a3b6db7b6..0b5dc21554 100644 --- a/src/cpp/ripple/AutoSocket.h +++ b/src/cpp/ripple/AutoSocket.h @@ -141,6 +141,16 @@ public: } + template + void async_write(basic_streambuf& buffers, Handler handler) + { + if (isSecure()) + boost::asio::async_write(*mSocket, buffers, handler); + else + boost::asio::async_write(PlainSocket(), buffers, handler); + } + + template void async_read(const Buf& buffers, Condition cond, Handler handler) { From 7e6e1645134953916dd54f5585f4615805c6dc74 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 1 Feb 2013 18:10:58 -0800 Subject: [PATCH 21/21] Missing scope. --- src/cpp/ripple/AutoSocket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpp/ripple/AutoSocket.h b/src/cpp/ripple/AutoSocket.h index 0b5dc21554..628127c374 100644 --- a/src/cpp/ripple/AutoSocket.h +++ b/src/cpp/ripple/AutoSocket.h @@ -142,7 +142,7 @@ public: template - void async_write(basic_streambuf& buffers, Handler handler) + void async_write(boost::asio::basic_streambuf& buffers, Handler handler) { if (isSecure()) boost::asio::async_write(*mSocket, buffers, handler);