Skip to content

Commit

Permalink
Decouple ledger.js and serialization code
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Clark committed Oct 13, 2015
1 parent a2406ac commit 9a5d05f
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 148 deletions.
4 changes: 2 additions & 2 deletions src/api/offline/ledgerhash.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function computeTransactionHash(ledger) {
return renameMeta;
});
const ledgerObject = common.core.Ledger.from_json({transactions: txs});
const transactionHash = ledgerObject.calc_tx_hash().to_hex();
const transactionHash = ledgerObject.calc_tx_hash();
if (ledger.transactionHash !== undefined
&& ledger.transactionHash !== transactionHash) {
throw new common.errors.ValidationError('transactionHash in header'
Expand All @@ -55,7 +55,7 @@ function computeStateHash(ledger) {
}
const state = JSON.parse(ledger.rawState);
const ledgerObject = common.core.Ledger.from_json({accountState: state});
const stateHash = ledgerObject.calc_account_hash().to_hex();
const stateHash = ledgerObject.calc_account_hash();
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
throw new common.errors.ValidationError('stateHash in header'
+ ' does not match computed hash of state');
Expand Down
194 changes: 100 additions & 94 deletions src/core/ledger.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
'use strict';
const sha512half = require('./utils').sha512half;
const BigNumber = require('bignumber.js');
const Transaction = require('./transaction').Transaction;
const hashprefixes = require('./hashprefixes');
const SHAMap = require('./shamap').SHAMap;
const SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
const SerializedObject = require('./serializedobject').SerializedObject;
const stypes = require('./serializedtypes');
const UInt160 = require('./uint160').UInt160;
const Currency = require('./currency').Currency;
const {decodeAddress} = require('ripple-address-codec');
const binary = require('ripple-binary-codec');

function Ledger() {
this.ledger_json = {};
Expand All @@ -20,21 +19,57 @@ Ledger.from_json = function(v) {

Ledger.space = require('./ledgerspaces');

function hash(hex) {
return sha512half(new Buffer(hex, 'hex'));
}

function hashTransaction(txBlobHex) {
const prefix = hashprefixes.HASH_TX_ID.toString(16).toUpperCase();
return hash(prefix + txBlobHex);
}

function padLeftZero(string, length) {
return Array(length - string.length + 1).join('0') + string;
}

function intToHex(integer, byteLength) {
return padLeftZero(Number(integer).toString(16), byteLength * 2);
}

function bytesToHex(bytes) {
return (new Buffer(bytes)).toString('hex');
}

function bigintToHex(integerString, byteLength) {
const hex = (new BigNumber(integerString)).toString(16);
return padLeftZero(hex, byteLength * 2);
}

function addressToHex(address) {
return (new Buffer(decodeAddress(address))).toString('hex');
}

function currencyToHex(currency) {
if (currency.length === 3) {
const bytes = new Array(20 + 1).join('0').split('').map(parseFloat);
bytes[12] = currency.charCodeAt(0) & 0xff;
bytes[13] = currency.charCodeAt(1) & 0xff;
bytes[14] = currency.charCodeAt(2) & 0xff;
return bytesToHex(bytes);
}
return currency;
}

/**
* Generate the key for an AccountRoot entry.
*
* @param {String|UInt160} accountArg - Ripple Account
* @return {UInt256}
*/
Ledger.calcAccountRootEntryHash =
Ledger.prototype.calcAccountRootEntryHash = function(accountArg) {
const account = UInt160.from_json(accountArg);
const index = new SerializedObject();

index.append([0, Ledger.space.account.charCodeAt(0)]);
index.append(account.to_bytes());

return index.hash();
Ledger.prototype.calcAccountRootEntryHash = function(address) {
const prefix = '00' + intToHex(Ledger.space.account.charCodeAt(0), 1);
return hash(prefix + addressToHex(address));
};

/**
Expand All @@ -46,15 +81,9 @@ Ledger.prototype.calcAccountRootEntryHash = function(accountArg) {
* @return {UInt256}
*/
Ledger.calcOfferEntryHash =
Ledger.prototype.calcOfferEntryHash = function(accountArg, sequence) {
const account = UInt160.from_json(accountArg);
const index = new SerializedObject();

index.append([0, Ledger.space.offer.charCodeAt(0)]);
index.append(account.to_bytes());
stypes.Int32.serialize(index, sequence);

return index.hash();
Ledger.prototype.calcOfferEntryHash = function(address, sequence) {
const prefix = '00' + intToHex(Ledger.space.offer.charCodeAt(0), 1);
return hash(prefix + addressToHex(address) + intToHex(sequence, 4));
};

/**
Expand All @@ -69,49 +98,47 @@ Ledger.prototype.calcOfferEntryHash = function(accountArg, sequence) {
*/
Ledger.calcRippleStateEntryHash =
Ledger.prototype.calcRippleStateEntryHash = function(
_account1, _account2, _currency) {
const currency = Currency.from_json(_currency);
const account1 = UInt160.from_json(_account1);
const account2 = UInt160.from_json(_account2);

if (!account1.is_valid()) {
throw new Error('Invalid first account');
}
if (!account2.is_valid()) {
throw new Error('Invalid second account');
}
if (!currency.is_valid()) {
throw new Error('Invalid currency');
}

const swap = account1.greater_than(account2);
const lowAccount = swap ? account2 : account1;
const highAccount = swap ? account1 : account2;
const index = new SerializedObject();

index.append([0, Ledger.space.rippleState.charCodeAt(0)]);
index.append(lowAccount.to_bytes());
index.append(highAccount.to_bytes());
index.append(currency.to_bytes());

return index.hash();
address1, address2, currency) {
const address1Hex = addressToHex(address1);
const address2Hex = addressToHex(address2);

const swap = (new BigNumber(address1Hex, 16)).greaterThan(
new BigNumber(address2Hex, 16));
const lowAddressHex = swap ? address2Hex : address1Hex;
const highAddressHex = swap ? address1Hex : address2Hex;

const prefix = '00' + intToHex(Ledger.space.rippleState.charCodeAt(0), 1);
return hash(prefix + lowAddressHex + highAddressHex +
currencyToHex(currency));
};

Ledger.prototype.parse_json = function(v) {
this.ledger_json = v;
};

function addLengthPrefix(hex) {
const length = hex.length / 2;
if (length <= 192) {
return bytesToHex([length]) + hex;
} else if (length <= 12480) {
const x = length - 193;
return bytesToHex([193 + (x >>> 8), x & 0xff]) + hex;
} else if (length <= 918744) {
const x = length - 12481;
return bytesToHex([241 + (x >>> 16), x >>> 8 & 0xff, x & 0xff]) + hex;
}
throw new Error('Variable integer overflow.');
}

Ledger.prototype.calc_tx_hash = function() {
const tx_map = new SHAMap();

this.ledger_json.transactions.forEach(function(tx_json) {
const tx = Transaction.from_json(tx_json);
const meta = SerializedObject.from_json(tx_json.metaData);

const data = new SerializedObject();
stypes.VariableLength.serialize(data, tx.serialize());
stypes.VariableLength.serialize(data, meta.to_hex());
tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
const txBlobHex = binary.encode(tx_json);
const metaHex = binary.encode(tx_json.metaData);
const txHash = hashTransaction(txBlobHex);
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex);
tx_map.add_item(txHash, data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
});

return tx_map.hash();
Expand All @@ -127,54 +154,33 @@ Ledger.prototype.calc_tx_hash = function() {
*
* @return {UInt256} - hash of shamap
*/
Ledger.prototype.calc_account_hash = function(options) {
Ledger.prototype.calc_account_hash = function() {
const account_map = new SHAMap();
let erred;

this.ledger_json.accountState.forEach(function(le) {
let data = SerializedObject.from_json(le);

let json;
if (options && options.sanity_test) {
try {
json = data.to_json();
data = SerializedObject.from_json(json);
} catch (e) {
console.log('account state item: ', le);
console.log('to_json() ', json);
console.log('exception: ', e);
erred = true;
}
}

account_map.add_item(le.index, data, SHAMapTreeNode.TYPE_ACCOUNT_STATE);
});

if (erred) {
throw new Error('There were errors with sanity_test'); // all logged above
}
this.ledger_json.accountState.forEach(function(ledgerEntry) {
const data = binary.encode(ledgerEntry);
account_map.add_item(ledgerEntry.index, data,
SHAMapTreeNode.TYPE_ACCOUNT_STATE);
});

return account_map.hash();
};

// see rippled Ledger::updateHash()
Ledger.calculateLedgerHash =
Ledger.prototype.calculateLedgerHash = function(ledgerHeader) {
const so = new SerializedObject();
const prefix = 0x4C575200;
const totalCoins = (new BigNumber(ledgerHeader.total_coins)).toString(16);

stypes.Int32.serialize(so, Number(ledgerHeader.ledger_index));
stypes.Int64.serialize(so, totalCoins);
stypes.Hash256.serialize(so, ledgerHeader.parent_hash);
stypes.Hash256.serialize(so, ledgerHeader.transaction_hash);
stypes.Hash256.serialize(so, ledgerHeader.account_hash);
stypes.Int32.serialize(so, ledgerHeader.parent_close_time);
stypes.Int32.serialize(so, ledgerHeader.close_time);
stypes.Int8.serialize(so, ledgerHeader.close_time_resolution);
stypes.Int8.serialize(so, ledgerHeader.close_flags);

return so.hash(prefix).to_hex();
const prefix = '4C575200';
return hash(prefix +
intToHex(ledgerHeader.ledger_index, 4) +
bigintToHex(ledgerHeader.total_coins, 8) +
ledgerHeader.parent_hash +
ledgerHeader.transaction_hash +
ledgerHeader.account_hash +
intToHex(ledgerHeader.parent_close_time, 4) +
intToHex(ledgerHeader.close_time, 4) +
intToHex(ledgerHeader.close_time_resolution, 1) +
intToHex(ledgerHeader.close_flags, 1)
);
};

exports.Ledger = Ledger;

0 comments on commit 9a5d05f

Please sign in to comment.