Skip to content

Commit

Permalink
Cache history and just resume where we left when restarting.
Browse files Browse the repository at this point in the history
  • Loading branch information
caedesvvv committed Nov 10, 2014
1 parent d2f1ae8 commit b553b97
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 91 deletions.
22 changes: 12 additions & 10 deletions js/backend/services/wallet.js
Expand Up @@ -187,7 +187,7 @@ function(IdentityKeyRing, Port, CurrencyFormatting, TransactionTasks, Bitcoin, B
/***************************************
/* History and address subscription
*/
function historyFetched(err, walletAddress, history) {
function historyFetched(err, walletAddress, history, fromHeight) {
if (err) {
core.servicesStatus.syncing -= 1;
core.servicesStatus.obelisk = 'error';
Expand All @@ -199,15 +199,16 @@ function(IdentityKeyRing, Port, CurrencyFormatting, TransactionTasks, Bitcoin, B
var identity = self.getCurrentIdentity();

// pass to the wallet to process outputs
identity.wallet.processHistory(walletAddress, history);
if (history.length) {
identity.wallet.processHistory(walletAddress, history);

// start filling history
identity.history.fillHistory(history);
// start filling history
identity.history.fillHistory(history);

if (TransactionTasks.processHistory(history, self.currentHeight)) {
// some task was updated
if (TransactionTasks.processHistory(history, self.currentHeight)) {
// some task was updated
}
}

// now subscribe the address for notifications
client.subscribe(walletAddress.address, function(err, res) {
core.servicesStatus.syncing -= 1;
Expand All @@ -230,15 +231,16 @@ function(IdentityKeyRing, Port, CurrencyFormatting, TransactionTasks, Bitcoin, B
var identity = self.getCurrentIdentity();

// Load history cache
if (walletAddress.history) {
/*if (walletAddress.history) {
identity.history.fillHistory(walletAddress.history);
}
}*/
if (!core.servicesStatus.syncing) {
core.servicesStatus.syncing = 0;
}
core.servicesStatus.syncing += 1;
var fromHeight = walletAddress.height+1;
// Now fetch history
client.fetch_history(walletAddress.address, 0 /*walletAddress.height*/, function(err, res) { historyFetched(err, walletAddress, res); });
client.fetch_history(walletAddress.address, fromHeight, function(err, res) { historyFetched(err, walletAddress, res, fromHeight); });
};

// Unsusbscribe an address from the backend
Expand Down
117 changes: 46 additions & 71 deletions js/model/history.js
@@ -1,6 +1,6 @@
'use strict';

define(['bitcoinjs-lib', 'util/btc'], function(Bitcoin, BtcUtils) {
define(['bitcoinjs-lib', 'util/btc', 'model/historyrow'], function(Bitcoin, BtcUtils, HistoryRow) {

/**
* User oriented history view.
Expand All @@ -11,6 +11,12 @@ define(['bitcoinjs-lib', 'util/btc'], function(Bitcoin, BtcUtils) {
function History(store, identity) {
this.history = [];
this.identity = identity;
var self = this;
Object.keys(identity.txdb.transactions).forEach(function(txId) {
if (identity.txdb.getImpact(txId)) {
self.history.push(new HistoryRow(txId, identity));
}
});
}

/**
Expand Down Expand Up @@ -45,6 +51,21 @@ History.prototype.addHistoryRow = function(newRow) {
return 0;
};

/**
* Add impact for input or output to an impact dictionary.
* @private
*/
History.prototype.addPocketImpact = function(impact, pocketId, outPocketType, amount) {
if (!impact.hasOwnProperty(pocketId)) {
impact[pocketId] = {ins: 0, outs: 0, total: 0, type: outPocketType};
}
if (amount > 0) {
impact[pocketId].outs += amount;
} else {
impact[pocketId].ins -= amount;
}
impact[pocketId].total += amount;
};

/**
* Build a history row from a transaction.
Expand All @@ -56,48 +77,35 @@ History.prototype.addHistoryRow = function(newRow) {
History.prototype.buildHistoryRow = function(transaction, height) {
var identity = this.identity,
btcWallet = identity.wallet.wallet,
inMine = 0,
outAddresses = [],
myInValue = 0,
myOutValue = 0,
inMine = 0,
txAddr = "",
txObj = Bitcoin.Transaction.fromHex(transaction);
var isStealth = false;
var txHash = txObj.getId();

var pocketImpact = {};
var addPocketImpact = function(pocketId, outPocketType, amount) {
if (!pocketImpact.hasOwnProperty(pocketId)) {
pocketImpact[pocketId] = {ins: 0, outs: 0, total: 0, type: outPocketType};
}
if (amount > 0) {
pocketImpact[pocketId].outs += amount;
} else {
pocketImpact[pocketId].ins -= amount;
}
pocketImpact[pocketId].total += amount;
};

var inAddress, inPocket, outPocket, internal;
var inAddress;

// Check inputs
for(var idx=0; idx<txObj.ins.length; idx++) {
var anIn = txObj.ins[idx];
var outIdx = Bitcoin.bufferutils.reverse(anIn.hash).toString('hex')+":"+anIn.index;
if (btcWallet.outputs[outIdx]) {
var output = btcWallet.outputs[outIdx];
var output = btcWallet.outputs[outIdx];
if (output) {
// save in pocket
var inWalletAddress = identity.wallet.getWalletAddress(output.address);
inPocket = identity.wallet.pockets.getAddressPocketId(inWalletAddress);
var inPocket = identity.wallet.pockets.getAddressPocketId(inWalletAddress);
var inPocketType = identity.wallet.pockets.getPocketType(inWalletAddress.type);
// counters
this.addPocketImpact(pocketImpact, inPocket, inPocketType, -output.value);
inMine += 1;
myInValue += output.value;
addPocketImpact(inPocket, inPocketType, -output.value);
} else {
try {
var address = BtcUtils.getInputAddress(anIn, this.identity.wallet.versions);
inAddress = address || inAddress;
if (!inAddress) {
var address = BtcUtils.getInputAddress(anIn, this.identity.wallet.versions);
inAddress = address || inAddress;
}
} catch (e) {
console.log("error decoding input", anIn);
}
Expand All @@ -108,70 +116,37 @@ History.prototype.buildHistoryRow = function(transaction, height) {
}
// Check outputs
for(var idx=0; idx<txObj.outs.length; idx++) {
var outAddress;
var outAddress = "";
var anOut = txObj.outs[idx];
try {
outAddress = Bitcoin.Address.fromOutputScript(anOut.script, Bitcoin.networks[this.identity.wallet.network]);
outAddress = Bitcoin.Address.fromOutputScript(anOut.script, Bitcoin.networks[this.identity.wallet.network]).toString();
} catch(e) {
outAddress = "";
}
var outWalletAddress = identity.wallet.getWalletAddress(outAddress.toString());
var outWalletAddress = identity.wallet.getWalletAddress(outAddress);
if (outWalletAddress) {
var output = btcWallet.outputs[txHash+":"+idx];
// TODO: mark also when input is mine and output not
if (output && output.stealth) {
isStealth = true;
}
if (outAddresses.indexOf(outWalletAddress) == -1) {
outAddresses.push(outWalletAddress);
}
myOutValue += anOut.value;
var _outPocket = identity.wallet.pockets.getAddressPocketId(outWalletAddress);
var outPocket = identity.wallet.pockets.getAddressPocketId(outWalletAddress);
var outPocketType = identity.wallet.pockets.getPocketType(outWalletAddress.type);
// check if this is change, seq[0] for oldstealth and old hd, seq[1] for new hd
var isChange = (['oldstealth', undefined].indexOf(outWalletAddress.type) > -1) ? (outWalletAddress.index[0]%2) : false;
isChange = isChange || ((outWalletAddress.type == 'hd') ? outWalletAddress.index[1] : false);
// save out pocket (don't set if it's change or same as input)
if (inPocket !== _outPocket && !isChange) {
outPocket = _outPocket;
}
addPocketImpact(_outPocket, outPocketType, anOut.value);
this.addPocketImpact(pocketImpact, outPocket, outPocketType, anOut.value);
} else {
if (inMine) {
txAddr = outAddress.toString();
txAddr = outAddress;
}
}
}
if (!txAddr) {
txAddr = this.getTransferLabel(pocketImpact);
internal = true;
}
// Create a row representing this change (if already referenced will
// be replaced)
var newRow = {hash: txHash, tx: txObj, inMine: inMine, outAddresses: outAddresses, myInValue: myInValue, myOutValue: myOutValue, height: height, address: txAddr, isStealth: isStealth, total: myOutValue-myInValue, outPocket: outPocket, inPocket: inPocket, impact: pocketImpact, label: this.identity.txdb.getLabel(txHash), internal: internal, bareid: BtcUtils.getBareTxId(txObj)};
return newRow;
};

History.prototype.getTransferLabel = function(pocketImpact) {
var identity = this.identity;
var keys = Object.keys(pocketImpact);
this.identity.txdb.setHeight(txHash, height);
this.identity.txdb.setImpact(txHash, pocketImpact);
this.identity.txdb.setOutAddresses(txHash, outAddresses);
this.identity.txdb.setAddress(txHash, txAddr);

// Input pockets
var inKeys = keys.filter(function(key) { return pocketImpact[key].ins!==0; } );
inKeys = inKeys.map(function(key) { return identity.wallet.pockets.getPocket(key, pocketImpact[key].type).name; });

// Output pockets (minus change pockets)
var outKeys = keys.filter(function(key) { return pocketImpact[key].outs!==0 && pocketImpact[key].ins===0; } );
outKeys = outKeys.map(function(key) { return identity.wallet.pockets.getPocket(key, pocketImpact[key].type).name; });

// Compose the final label
var label = inKeys.join(' ,');
if (outKeys.length) {
return label + " to " + outKeys.join(' ,');
} else {
return 'internal on ' + label;
}
}
// Start row
return new HistoryRow(txHash, this.identity, txObj)
};

/**
* Callback to fill missing input (depending on another transaction)
Expand Down Expand Up @@ -233,7 +208,7 @@ History.prototype.fillHistory = function(history) {
txdb = this.identity.txdb;
history.forEach(function(tx) {
var outTxHash = tx[0],
inTxHash = tx[4];
inTxHash = tx[4];
if (inTxHash) {
// fetch a row for the spend
txdb.fetchTransaction(inTxHash, function(_a, _b) {self.txFetched(_a, _b);}, tx[6]);
Expand Down
143 changes: 143 additions & 0 deletions js/model/historyrow.js
@@ -0,0 +1,143 @@
'use strict';

define(['bitcoinjs-lib', 'util/btc'], function(Bitcoin, BtcUtils) {

function HistoryRow(hash, identity, txObj) {
this.identity = identity;
this._tx = txObj;
this.hash = hash;
this.outAddresses = identity.txdb.getOutAddresses(hash).map(function(address) {
return identity.wallet.getWalletAddress(address);}).filter(function(address){return address !== undefined;
});
this.isStealth = this.outAddresses.some(function(walletAddress) { return walletAddress && walletAddress.type === 'stealth';});
}

HistoryRow.prototype.getTransferLabel = function() {
var identity = this.identity;
var pocketImpact = this.impact;
var keys = Object.keys(pocketImpact);

// Input pockets
var inKeys = keys.filter(function(key) { return pocketImpact[key].ins!==0; } );
inKeys = inKeys.map(function(key) { return identity.wallet.pockets.getPocket(key, pocketImpact[key].type).name; });

// Output pockets (minus change pockets)
var outKeys = keys.filter(function(key) { return pocketImpact[key].outs!==0 && pocketImpact[key].ins===0; } );
outKeys = outKeys.map(function(key) { return identity.wallet.pockets.getPocket(key, pocketImpact[key].type).name; });

// Compose the final label
var label = inKeys.join(' ,');
if (outKeys.length) {
return label + " to " + outKeys.join(' ,');
} else {
return 'internal on ' + label;
}
};

Object.defineProperty(HistoryRow.prototype, 'tx', {
get: function() {
if (!this._tx) {
var txBody = this.identity.txdb.getBody(this.hash);
this._tx = Bitcoin.Transaction.fromHex(txBody);
}
return this._tx;
}
});

Object.defineProperty(HistoryRow.prototype, 'inMine', {
get: function() { return this.myInValue>0; }
});

Object.defineProperty(HistoryRow.prototype, 'address', {
// TODO: Probably don't want this here any more!
get: function() {
var addressLabel = this.identity.txdb.getAddress(this.hash);
if (addressLabel) {
return addressLabel;
} else {
return this.getTransferLabel();
}
},
set: function(val) { this.identity.txdb.setAddress(this.hash, val); }
});

Object.defineProperty(HistoryRow.prototype, 'total', {
get: function() { return this.myOutValue-this.myInValue; }
});

Object.defineProperty(HistoryRow.prototype, 'outPocket', {
get: function() {
var impactkeys = Object.keys(this.impact);
for(var i=0; i<impactkeys.length; i++) {
if (this.impact[impactkeys[i]].outs && this.impact[impactkeys[i]].outs > this.impact[impactkeys[i]].ins) {
return impactkeys[i];
}
}
}
});

Object.defineProperty(HistoryRow.prototype, 'inPocket', {
get: function() {
var impactkeys = Object.keys(this.impact);
for(var i=0; i<impactkeys.length; i++) {
// TODO: should think about change in this comparison
if (this.impact[impactkeys[i]].ins && this.impact[impactkeys[i]].ins > this.impact[impactkeys[i]].outs) {
return impactkeys[i];
}
}
}
});

Object.defineProperty(HistoryRow.prototype, 'label', {
get: function() { return this.identity.txdb.getLabel(this.hash); }
});

Object.defineProperty(HistoryRow.prototype, 'impact', {
get: function() { return this.identity.txdb.getImpact(this.hash); }
});

Object.defineProperty(HistoryRow.prototype, 'internal', {
get: function() { return (this.myInValue === this.myOutValue); }
});

Object.defineProperty(HistoryRow.prototype, 'bareid', {
get: function() {
if (!this._bareid) {
this._bareid = BtcUtils.getBareTxId(this.tx);
}
return this._bareid;
}
});

Object.defineProperty(HistoryRow.prototype, 'myInValue', {
get: function() {
var res = 0;
var impactkeys = Object.keys(this.impact);
for(var i=0; i<impactkeys.length; i++) {
res += this.impact[impactkeys[i]].ins;
}
return res;

}
});

Object.defineProperty(HistoryRow.prototype, 'myOutValue', {
get: function() {
var res = 0;
var impactkeys = Object.keys(this.impact);
for(var i=0; i<impactkeys.length; i++) {
res += this.impact[impactkeys[i]].outs;
}
return res;

}
});

Object.defineProperty(HistoryRow.prototype, 'height', {
// TODO: getHeight not implemented yet...
get: function() { return this.identity.txdb.getHeight(this.hash); },
set: function(val) { return this.identity.txdb.setHeight(this.hash, val); }
});

return HistoryRow;
});

0 comments on commit b553b97

Please sign in to comment.