mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 12:15:51 +00:00
Add SHAMaps, metadata serialization and transaction hash calculation.
This commit is contained in:
14
scripts/verify_ledger_json.js
Normal file
14
scripts/verify_ledger_json.js
Normal file
@@ -0,0 +1,14 @@
|
||||
var fs = require('fs');
|
||||
var Ledger = require('../src/js/ripple/ledger').Ledger;
|
||||
|
||||
if (process.argc < 1) {
|
||||
console.error("Usage: scripts/verify_ledger_json path/to/ledger.json");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var json = fs.readFileSync(process.argv[2], 'utf-8');
|
||||
var ledger = Ledger.from_json(JSON.parse(json));
|
||||
|
||||
console.log("Calculated transaction hash: "+ledger.calc_tx_hash().to_hex())
|
||||
console.log("Transaction hash in header: "+ledger.ledger_json.transaction_hash);
|
||||
|
||||
@@ -1,90 +1,114 @@
|
||||
var ST = require("./serializedtypes");
|
||||
|
||||
var REQUIRED = exports.REQUIRED = 0,
|
||||
OPTIONAL = exports.OPTIONAL = 1,
|
||||
DEFAULT = exports.DEFAULT = 2;
|
||||
|
||||
ST.Int16.id = 1;
|
||||
ST.Int32.id = 2;
|
||||
ST.Int64.id = 3;
|
||||
ST.Hash128.id = 4;
|
||||
ST.Hash256.id = 5;
|
||||
ST.Amount.id = 6;
|
||||
ST.VariableLength.id = 7;
|
||||
ST.Account.id = 8;
|
||||
ST.Object.id = 14;
|
||||
ST.Array.id = 15;
|
||||
ST.Int8.id = 16;
|
||||
ST.Hash160.id = 17;
|
||||
ST.PathSet.id = 18;
|
||||
ST.Vector256.id = 19;
|
||||
|
||||
var base = [
|
||||
[ 'TransactionType' , REQUIRED, 2, ST.Int16 ],
|
||||
[ 'Flags' , OPTIONAL, 2, ST.Int32 ],
|
||||
[ 'SourceTag' , OPTIONAL, 3, ST.Int32 ],
|
||||
[ 'Account' , REQUIRED, 1, ST.Account ],
|
||||
[ 'Sequence' , REQUIRED, 4, ST.Int32 ],
|
||||
[ 'Fee' , REQUIRED, 8, ST.Amount ],
|
||||
[ 'OperationLimit' , OPTIONAL, 29, ST.Int32 ],
|
||||
[ 'SigningPubKey' , REQUIRED, 3, ST.VariableLength ],
|
||||
[ 'TxnSignature' , OPTIONAL, 4, ST.VariableLength ]
|
||||
[ 'TransactionType' , REQUIRED, 2, "Int16" ],
|
||||
[ 'Flags' , OPTIONAL, 2, "Int32" ],
|
||||
[ 'SourceTag' , OPTIONAL, 3, "Int32" ],
|
||||
[ 'Account' , REQUIRED, 1, "Account" ],
|
||||
[ 'Sequence' , REQUIRED, 4, "Int32" ],
|
||||
[ 'Fee' , REQUIRED, 8, "Amount" ],
|
||||
[ 'OperationLimit' , OPTIONAL, 29, "Int32" ],
|
||||
[ 'SigningPubKey' , REQUIRED, 3, "VariableLength" ],
|
||||
[ 'TxnSignature' , OPTIONAL, 4, "VariableLength" ]
|
||||
];
|
||||
|
||||
exports.tx = {
|
||||
AccountSet: [3].concat(base, [
|
||||
[ 'EmailHash' , OPTIONAL, 1, ST.Hash128 ],
|
||||
[ 'WalletLocator' , OPTIONAL, 7, ST.Hash256 ],
|
||||
[ 'WalletSize' , OPTIONAL, 12, ST.Int32 ],
|
||||
[ 'MessageKey' , OPTIONAL, 2, ST.VariableLength ],
|
||||
[ 'Domain' , OPTIONAL, 7, ST.VariableLength ],
|
||||
[ 'TransferRate' , OPTIONAL, 11, ST.Int32 ]
|
||||
[ 'EmailHash' , OPTIONAL, 1, "Hash128" ],
|
||||
[ 'WalletLocator' , OPTIONAL, 7, "Hash256" ],
|
||||
[ 'WalletSize' , OPTIONAL, 12, "Int32" ],
|
||||
[ 'MessageKey' , OPTIONAL, 2, "VariableLength" ],
|
||||
[ 'Domain' , OPTIONAL, 7, "VariableLength" ],
|
||||
[ 'TransferRate' , OPTIONAL, 11, "Int32" ]
|
||||
]),
|
||||
TrustSet: [20].concat(base, [
|
||||
[ 'LimitAmount' , OPTIONAL, 3, ST.Amount ],
|
||||
[ 'QualityIn' , OPTIONAL, 20, ST.Int32 ],
|
||||
[ 'QualityOut' , OPTIONAL, 21, ST.Int32 ]
|
||||
[ 'LimitAmount' , OPTIONAL, 3, "Amount" ],
|
||||
[ 'QualityIn' , OPTIONAL, 20, "Int32" ],
|
||||
[ 'QualityOut' , OPTIONAL, 21, "Int32" ]
|
||||
]),
|
||||
OfferCreate: [7].concat(base, [
|
||||
[ 'TakerPays' , REQUIRED, 4, ST.Amount ],
|
||||
[ 'TakerGets' , REQUIRED, 5, ST.Amount ],
|
||||
[ 'Expiration' , OPTIONAL, 10, ST.Int32 ]
|
||||
[ 'TakerPays' , REQUIRED, 4, "Amount" ],
|
||||
[ 'TakerGets' , REQUIRED, 5, "Amount" ],
|
||||
[ 'Expiration' , OPTIONAL, 10, "Int32" ]
|
||||
]),
|
||||
OfferCancel: [8].concat(base, [
|
||||
[ 'OfferSequence' , REQUIRED, 25, ST.Int32 ]
|
||||
[ 'OfferSequence' , REQUIRED, 25, "Int32" ]
|
||||
]),
|
||||
SetRegularKey: [5].concat(base, [
|
||||
[ 'RegularKey' , REQUIRED, 8, ST.Account ]
|
||||
[ 'RegularKey' , REQUIRED, 8, "Account" ]
|
||||
]),
|
||||
Payment: [0].concat(base, [
|
||||
[ 'Destination' , REQUIRED, 3, ST.Account ],
|
||||
[ 'Amount' , REQUIRED, 1, ST.Amount ],
|
||||
[ 'SendMax' , OPTIONAL, 9, ST.Amount ],
|
||||
[ 'Paths' , DEFAULT , 1, ST.PathSet ],
|
||||
[ 'InvoiceID' , OPTIONAL, 17, ST.Hash256 ],
|
||||
[ 'DestinationTag' , OPTIONAL, 14, ST.Int32 ]
|
||||
[ 'Destination' , REQUIRED, 3, "Account" ],
|
||||
[ 'Amount' , REQUIRED, 1, "Amount" ],
|
||||
[ 'SendMax' , OPTIONAL, 9, "Amount" ],
|
||||
[ 'Paths' , DEFAULT , 1, "PathSet" ],
|
||||
[ 'InvoiceID' , OPTIONAL, 17, "Hash256" ],
|
||||
[ 'DestinationTag' , OPTIONAL, 14, "Int32" ]
|
||||
]),
|
||||
Contract: [9].concat(base, [
|
||||
[ 'Expiration' , REQUIRED, 10, ST.Int32 ],
|
||||
[ 'BondAmount' , REQUIRED, 23, ST.Int32 ],
|
||||
[ 'StampEscrow' , REQUIRED, 22, ST.Int32 ],
|
||||
[ 'RippleEscrow' , REQUIRED, 17, ST.Amount ],
|
||||
[ 'CreateCode' , OPTIONAL, 11, ST.VariableLength ],
|
||||
[ 'FundCode' , OPTIONAL, 8, ST.VariableLength ],
|
||||
[ 'RemoveCode' , OPTIONAL, 9, ST.VariableLength ],
|
||||
[ 'ExpireCode' , OPTIONAL, 10, ST.VariableLength ]
|
||||
[ 'Expiration' , REQUIRED, 10, "Int32" ],
|
||||
[ 'BondAmount' , REQUIRED, 23, "Int32" ],
|
||||
[ 'StampEscrow' , REQUIRED, 22, "Int32" ],
|
||||
[ 'RippleEscrow' , REQUIRED, 17, "Amount" ],
|
||||
[ 'CreateCode' , OPTIONAL, 11, "VariableLength" ],
|
||||
[ 'FundCode' , OPTIONAL, 8, "VariableLength" ],
|
||||
[ 'RemoveCode' , OPTIONAL, 9, "VariableLength" ],
|
||||
[ 'ExpireCode' , OPTIONAL, 10, "VariableLength" ]
|
||||
]),
|
||||
RemoveContract: [10].concat(base, [
|
||||
[ 'Target' , REQUIRED, 7, ST.Account ]
|
||||
[ 'Target' , REQUIRED, 7, "Account" ]
|
||||
]),
|
||||
EnableFeature: [100].concat(base, [
|
||||
[ 'Feature' , REQUIRED, 19, ST.Hash256 ]
|
||||
[ 'Feature' , REQUIRED, 19, "Hash256" ]
|
||||
]),
|
||||
SetFee: [101].concat(base, [
|
||||
[ 'Features' , REQUIRED, 9, ST.Array ],
|
||||
[ 'BaseFee' , REQUIRED, 5, ST.Int64 ],
|
||||
[ 'ReferenceFeeUnits' , REQUIRED, 30, ST.Int32 ],
|
||||
[ 'ReserveBase' , REQUIRED, 31, ST.Int32 ],
|
||||
[ 'ReserveIncrement' , REQUIRED, 32, ST.Int32 ]
|
||||
[ 'Features' , REQUIRED, 9, "Array" ],
|
||||
[ 'BaseFee' , REQUIRED, 5, "Int64" ],
|
||||
[ 'ReferenceFeeUnits' , REQUIRED, 30, "Int32" ],
|
||||
[ 'ReserveBase' , REQUIRED, 31, "Int32" ],
|
||||
[ 'ReserveIncrement' , REQUIRED, 32, "Int32" ]
|
||||
])
|
||||
};
|
||||
|
||||
exports.ledger = {
|
||||
AccountRoot: [97],
|
||||
Contract: [99],
|
||||
DirectoryNode: [100],
|
||||
Features: [102],
|
||||
GeneratorMap: [103],
|
||||
LedgerHashes: [104],
|
||||
Nickname: [110],
|
||||
Offer: [111],
|
||||
RippleState: [114],
|
||||
FeeSettings: [115]
|
||||
};
|
||||
|
||||
exports.metadata = [
|
||||
[ 'TransactionIndex' , REQUIRED, 28, "Int32" ],
|
||||
[ 'TransactionResult' , REQUIRED, 3, "Int8" ],
|
||||
[ 'AffectedNodes' , REQUIRED, 8, "Array" ]
|
||||
];
|
||||
|
||||
exports.ter = {
|
||||
tesSUCCESS: 0,
|
||||
tecCLAIM: 100,
|
||||
tecPATH_PARTIAL: 101,
|
||||
tecUNFUNDED_ADD: 102,
|
||||
tecUNFUNDED_OFFER: 103,
|
||||
tecUNFUNDED_PAYMENT: 104,
|
||||
tecFAILED_PROCESSING: 105,
|
||||
tecDIR_FULL: 121,
|
||||
tecINSUF_RESERVE_LINE: 122,
|
||||
tecINSUF_RESERVE_OFFER: 123,
|
||||
tecNO_DST: 124,
|
||||
tecNO_DST_INSUF_XRP: 125,
|
||||
tecNO_LINE_INSUF_RESERVE: 126,
|
||||
tecNO_LINE_REDUNDANT: 127,
|
||||
tecPATH_DRY: 128,
|
||||
tecUNFUNDED: 129,
|
||||
tecMASTER_DISABLED: 130,
|
||||
tecNO_REGULAR_KEY: 131,
|
||||
tecOWNERS: 132
|
||||
};
|
||||
|
||||
23
src/js/ripple/hashprefixes.js
Normal file
23
src/js/ripple/hashprefixes.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Prefix for hashing functions.
|
||||
*
|
||||
* These prefixes are inserted before the source material used to
|
||||
* generate various hashes. This is done to put each hash in its own
|
||||
* "space." This way, two different types of objects with the
|
||||
* same binary data will produce different hashes.
|
||||
*
|
||||
* Each prefix is a 4-byte value with the last byte set to zero
|
||||
* and the first three bytes formed from the ASCII equivalent of
|
||||
* some arbitrary string. For example "TXN".
|
||||
*/
|
||||
|
||||
// transaction plus signature to give transaction ID
|
||||
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
// transaction plus metadata
|
||||
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
|
||||
// inner node in tree
|
||||
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
|
||||
// inner transaction to sign
|
||||
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
|
||||
// inner transaction to sign (TESTNET)
|
||||
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
|
||||
40
src/js/ripple/ledger.js
Normal file
40
src/js/ripple/ledger.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// Ledger
|
||||
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var SHAMap = require('./shamap').SHAMap;
|
||||
var SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
var stypes = require('./serializedtypes');
|
||||
|
||||
function Ledger()
|
||||
{
|
||||
this.ledger_json = {};
|
||||
}
|
||||
|
||||
Ledger.from_json = function (v) {
|
||||
var ledger = new Ledger();
|
||||
ledger.parse_json(v);
|
||||
return ledger;
|
||||
};
|
||||
|
||||
Ledger.prototype.parse_json = function (v) {
|
||||
this.ledger_json = v;
|
||||
};
|
||||
|
||||
Ledger.prototype.calc_tx_hash = function () {
|
||||
var tx_map = new SHAMap();
|
||||
|
||||
this.ledger_json.transactions.forEach(function (tx_json) {
|
||||
var tx = Transaction.from_json(tx_json);
|
||||
var meta = SerializedObject.from_json(tx_json.metaData);
|
||||
|
||||
var data = new SerializedObject();
|
||||
stypes.VariableLength.serialize(data, tx.serialize().to_hex());
|
||||
stypes.VariableLength.serialize(data, meta.to_hex());
|
||||
tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
|
||||
});
|
||||
|
||||
return tx_map.hash();
|
||||
};
|
||||
|
||||
exports.Ledger = Ledger;
|
||||
@@ -1,62 +1,29 @@
|
||||
var binformat = require('./binformat');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var extend = require('extend');
|
||||
var stypes = require('./serializedtypes');
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var assert = require('assert');
|
||||
var binformat = require('./binformat');
|
||||
var extend = require('extend');
|
||||
var stypes = require('./serializedtypes');
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var assert = require('assert');
|
||||
|
||||
var TRANSACTION_TYPES = {
|
||||
0: 'Payment',
|
||||
3: 'AccountSet',
|
||||
5: 'SetRegularKey',
|
||||
7: 'OfferCreate',
|
||||
8: 'OfferCancel',
|
||||
9: 'Contract',
|
||||
10: 'RemoveContract',
|
||||
20: 'TrustSet',
|
||||
100: 'EnableFeature',
|
||||
101: 'SetFee'
|
||||
};
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var LEDGER_ENTRY_TYPES = {
|
||||
97: 'AccountRoot',
|
||||
99: 'Contract',
|
||||
100: 'DirectoryNode',
|
||||
102: 'Features',
|
||||
103: 'GeneratorMap',
|
||||
104: 'LedgerHashes',
|
||||
110: 'Nickname',
|
||||
111: 'Offer',
|
||||
114: 'RippleState',
|
||||
115: 'FeeSettings'
|
||||
};
|
||||
|
||||
var TRANSACTION_RESULTS = {
|
||||
0 : 'tesSUCCESS',
|
||||
100: 'tecCLAIM',
|
||||
101: 'tecPATH_PARTIAL',
|
||||
102: 'tecUNFUNDED_ADD',
|
||||
103: 'tecUNFUNDED_OFFER',
|
||||
104: 'tecUNFUNDED_PAYMENT',
|
||||
105: 'tecFAILED_PROCESSING',
|
||||
121: 'tecDIR_FULL',
|
||||
122: 'tecINSUF_RESERVE_LINE',
|
||||
123: 'tecINSUF_RESERVE_OFFER',
|
||||
124: 'tecNO_DST',
|
||||
125: 'tecNO_DST_INSUF_XRP',
|
||||
126: 'tecNO_LINE_INSUF_RESERVE',
|
||||
127: 'tecNO_LINE_REDUNDANT',
|
||||
128: 'tecPATH_DRY',
|
||||
129: 'tecUNFUNDED', // Deprecated, old ambiguous unfunded.
|
||||
130: 'tecMASTER_DISABLED',
|
||||
131: 'tecNO_REGULAR_KEY',
|
||||
132: 'tecOWNERS'
|
||||
};
|
||||
|
||||
var TX_ID_MAP = { };
|
||||
var TRANSACTION_TYPES = { };
|
||||
|
||||
Object.keys(binformat.tx).forEach(function(key) {
|
||||
TX_ID_MAP[key[0]] = key;
|
||||
TRANSACTION_TYPES[binformat.tx[key][0]] = key;
|
||||
});
|
||||
|
||||
var LEDGER_ENTRY_TYPES = {};
|
||||
|
||||
Object.keys(binformat.ledger).forEach(function(key) {
|
||||
LEDGER_ENTRY_TYPES[binformat.ledger[key][0]] = key;
|
||||
});
|
||||
|
||||
var TRANSACTION_RESULTS = {};
|
||||
|
||||
Object.keys(binformat.ter).forEach(function(key) {
|
||||
TRANSACTION_RESULTS[binformat.ter[key]] = key;
|
||||
});
|
||||
|
||||
function SerializedObject(buf) {
|
||||
@@ -78,39 +45,42 @@ SerializedObject.from_json = function (obj) {
|
||||
var so = new SerializedObject;
|
||||
var typedef;
|
||||
|
||||
switch (typeof obj.TransactionType) {
|
||||
case 'number':
|
||||
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
if ("number" === typeof obj.TransactionType) {
|
||||
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
|
||||
if (!obj.TransactionType) {
|
||||
throw new Error('Transaction type ID is invalid.');
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
typedef = binformat.tx[obj.TransactionType];
|
||||
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('Transaction type is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.TransactionType = typedef.shift();
|
||||
break;
|
||||
default:
|
||||
if (typeof obj.LedgerEntryType !== 'undefined') {
|
||||
// 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.');
|
||||
if (!obj.TransactionType) {
|
||||
throw new Error('Transaction type ID is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if ("string" === typeof obj.TransactionType) {
|
||||
typedef = binformat.tx[obj.TransactionType];
|
||||
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('Transaction type is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.TransactionType = typedef.shift();
|
||||
} else if ("undefined" !== typeof obj.LedgerEntryType) {
|
||||
// XXX: TODO
|
||||
throw new Error('Ledger entry binary format not yet implemented.');
|
||||
} else if ("object" === typeof obj.AffectedNodes) {
|
||||
typedef = binformat.metadata;
|
||||
} else {
|
||||
throw new Error('Object to be serialized must contain either' +
|
||||
' TransactionType, LedgerEntryType or AffectedNodes.');
|
||||
}
|
||||
|
||||
so.serialize(typedef, obj);
|
||||
|
||||
return so;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.append = function (bytes) {
|
||||
if (bytes instanceof SerializedObject) {
|
||||
bytes = bytes.buffer;
|
||||
}
|
||||
this.buffer = this.buffer.concat(bytes);
|
||||
this.pointer += bytes.length;
|
||||
};
|
||||
@@ -174,13 +144,13 @@ SerializedObject.jsonify_structure = function(structure, field_name) {
|
||||
case 'number':
|
||||
switch (field_name) {
|
||||
case 'LedgerEntryType':
|
||||
output = LEDGER_ENTRY_TYPES[structure] || thing;
|
||||
output = LEDGER_ENTRY_TYPES[structure];
|
||||
break;
|
||||
case 'TransactionResult':
|
||||
output = TRANSACTION_RESULTS[structure] || thing;
|
||||
output = TRANSACTION_RESULTS[structure];
|
||||
break;
|
||||
case 'TransactionType':
|
||||
output = TRANSACTION_TYPES[structure] || thing;
|
||||
output = TRANSACTION_TYPES[structure];
|
||||
break;
|
||||
default:
|
||||
output = structure;
|
||||
@@ -190,6 +160,8 @@ SerializedObject.jsonify_structure = function(structure, field_name) {
|
||||
if (!structure) break; //null
|
||||
if (typeof structure.to_json === 'function') {
|
||||
output = structure.to_json();
|
||||
} else if (structure instanceof BigInteger) {
|
||||
output = structure.toString(16).toUpperCase();
|
||||
} else {
|
||||
output = new structure.constructor; //new Array or Object
|
||||
var keys = Object.keys(structure);
|
||||
@@ -207,13 +179,19 @@ SerializedObject.jsonify_structure = function(structure, field_name) {
|
||||
};
|
||||
|
||||
SerializedObject.prototype.serialize = function (typedef, obj) {
|
||||
// Serialize object without end marker
|
||||
stypes.Object.serialize(this, obj, true);
|
||||
|
||||
// ST: Old serialization
|
||||
/*
|
||||
// Ensure canonical order
|
||||
var typedef = SerializedObject.sort_typedef(typedef);
|
||||
typedef = SerializedObject.sort_typedef(typedef);
|
||||
|
||||
// Serialize fields
|
||||
for (var i=0, l=typedef.length; i<l; i++) {
|
||||
this.serialize_field(typedef[i], obj);
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
SerializedObject.prototype.hash = function (prefix) {
|
||||
@@ -236,12 +214,15 @@ SerializedObject.prototype.serialize_field = function (spec, obj) {
|
||||
var name = spec[0];
|
||||
var presence = spec[1];
|
||||
var field_id = spec[2];
|
||||
var Type = spec[3];
|
||||
var Type = stypes[spec[3]];
|
||||
|
||||
if (typeof obj[name] !== 'undefined') {
|
||||
this.append(SerializedObject.get_field_header(Type.id, field_id));
|
||||
// ST: Old serialization code
|
||||
//this.append(SerializedObject.get_field_header(Type.id, field_id));
|
||||
try {
|
||||
Type.serialize(this, obj[name]);
|
||||
// ST: Old serialization code
|
||||
//Type.serialize(this, obj[name]);
|
||||
stypes.serialize(this, name, obj[name]);
|
||||
} catch (e) {
|
||||
// Add field name to message and rethrow
|
||||
e.message = 'Error serializing "' + name + '": ' + e.message;
|
||||
@@ -275,15 +256,15 @@ SerializedObject.sort_typedef = function (typedef) {
|
||||
|
||||
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];
|
||||
return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2];
|
||||
};
|
||||
|
||||
return typedef.sort(sort_field_compare);
|
||||
};
|
||||
|
||||
SerializedObject.lookup_type_tx = function (id) {
|
||||
assert(typeof id === 'string');
|
||||
return TX_ID_MAP[id];
|
||||
assert(typeof id === 'number');
|
||||
return TRANSACTION_TYPES[id];
|
||||
};
|
||||
|
||||
exports.SerializedObject = SerializedObject;
|
||||
|
||||
@@ -6,25 +6,27 @@
|
||||
* SerializedObject.parse() or SerializedObject.serialize().
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
var assert = require('assert');
|
||||
var extend = require('extend');
|
||||
var binformat = require('./binformat');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
|
||||
var UInt128 = require('./uint128').UInt128;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var UInt128 = require('./uint128').UInt128;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
|
||||
var amount = require('./amount');
|
||||
var Amount = amount.Amount;
|
||||
var Currency = amount.Currency;
|
||||
var amount = require('./amount');
|
||||
var Amount = amount.Amount;
|
||||
var Currency = amount.Currency;
|
||||
|
||||
// Shortcuts
|
||||
var hex = sjcl.codec.hex;
|
||||
var bytes = sjcl.codec.bytes;
|
||||
var hex = sjcl.codec.hex;
|
||||
var bytes = sjcl.codec.bytes;
|
||||
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
|
||||
var SerializedType = function (methods) {
|
||||
extend(this, methods);
|
||||
};
|
||||
@@ -154,6 +156,8 @@ var STInt8 = exports.Int8 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STInt8.id = 16;
|
||||
|
||||
var STInt16 = exports.Int16 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 2);
|
||||
@@ -163,6 +167,8 @@ var STInt16 = exports.Int16 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STInt16.id = 1;
|
||||
|
||||
var STInt32 = exports.Int32 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 4)
|
||||
@@ -172,6 +178,8 @@ var STInt32 = exports.Int32 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STInt32.id = 2;
|
||||
|
||||
var STInt64 = exports.Int64 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var bigNumObject;
|
||||
@@ -215,6 +223,8 @@ var STInt64 = exports.Int64 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STInt64.id = 3;
|
||||
|
||||
var STHash128 = exports.Hash128 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt128.from_json(val);
|
||||
@@ -228,6 +238,8 @@ var STHash128 = exports.Hash128 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash128.id = 4;
|
||||
|
||||
var STHash256 = exports.Hash256 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt256.from_json(val);
|
||||
@@ -241,6 +253,8 @@ var STHash256 = exports.Hash256 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash256.id = 5;
|
||||
|
||||
var STHash160 = exports.Hash160 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt160.from_json(val);
|
||||
@@ -254,6 +268,8 @@ var STHash160 = exports.Hash160 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash160.id = 17;
|
||||
|
||||
// Internal
|
||||
var STCurrency = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
@@ -385,6 +401,8 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STAmount.id = 6;
|
||||
|
||||
var STVL = exports.VariableLength = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
if (typeof val === 'string') {
|
||||
@@ -399,6 +417,8 @@ var STVL = exports.VariableLength = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STVL.id = 7;
|
||||
|
||||
var STAccount = exports.Account = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var account = UInt160.from_json(val);
|
||||
@@ -425,6 +445,8 @@ var STAccount = exports.Account = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STAccount.id = 8;
|
||||
|
||||
var STPathSet = exports.PathSet = new SerializedType({
|
||||
typeBoundary: 0xff,
|
||||
typeEnd: 0x00,
|
||||
@@ -529,6 +551,8 @@ var STPathSet = exports.PathSet = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STPathSet.id = 18;
|
||||
|
||||
var STVector256 = exports.Vector256 = new SerializedType({
|
||||
serialize: function (so, val) { //Assume val is an array of STHash256 objects.
|
||||
var length_as_varint = SerializedType.serialize_varint(so, val.length);
|
||||
@@ -546,6 +570,8 @@ var STVector256 = exports.Vector256 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STVector256.id = 19;
|
||||
|
||||
exports.serialize = exports.serialize_whatever = serialize;
|
||||
|
||||
function serialize(so, field_name, value) {
|
||||
@@ -555,16 +581,24 @@ function serialize(so, field_name, value) {
|
||||
var field_coordinates = INVERSE_FIELDS_MAP[field_name];
|
||||
var type_bits = field_coordinates[0];
|
||||
var field_bits = field_coordinates[1];
|
||||
var tag_byte = (type_bits < 16 ? type_bits << 4 : 0) | (field_bits < 16 ? field_bits : 0)
|
||||
var tag_byte = (type_bits < 16 ? type_bits << 4 : 0) | (field_bits < 16 ? field_bits : 0);
|
||||
|
||||
STInt8.serialize(so, tag_byte)
|
||||
if (field_name === "LedgerEntryType" && "string" === typeof value) {
|
||||
value = binformat.ledger[value][0];
|
||||
}
|
||||
|
||||
if (field_name === "TransactionResult" && "string" === typeof value) {
|
||||
value = binformat.ter[value];
|
||||
}
|
||||
|
||||
STInt8.serialize(so, tag_byte);
|
||||
|
||||
if (type_bits >= 16) {
|
||||
STInt8.serialize(so, type_bits)
|
||||
STInt8.serialize(so, type_bits);
|
||||
}
|
||||
|
||||
if (field_bits >= 16) {
|
||||
STInt8.serialize(so, field_bits)
|
||||
STInt8.serialize(so, field_bits);
|
||||
}
|
||||
|
||||
var serialized_object_type = TYPES_MAP[type_bits];
|
||||
@@ -597,13 +631,39 @@ function parse(so) {
|
||||
return [ field_name, type.parse(so) ]; //key, value
|
||||
};
|
||||
|
||||
function sort_fields(keys) {
|
||||
function sort_field_compare(a, b) {
|
||||
var a_field_coordinates = INVERSE_FIELDS_MAP[a];
|
||||
var a_type_bits = a_field_coordinates[0];
|
||||
var a_field_bits = a_field_coordinates[1];
|
||||
var b_field_coordinates = INVERSE_FIELDS_MAP[b];
|
||||
var b_type_bits = b_field_coordinates[0];
|
||||
var b_field_bits = b_field_coordinates[1];
|
||||
|
||||
// Sort by type id first, then by field id
|
||||
return a_type_bits !== b_type_bits ? a_type_bits - b_type_bits : a_field_bits - b_field_bits;
|
||||
};
|
||||
|
||||
return keys.sort(sort_field_compare);
|
||||
}
|
||||
|
||||
var STObject = exports.Object = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
serialize: function (so, val, no_marker) {
|
||||
var keys = Object.keys(val);
|
||||
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
keys = keys.filter(function (key) {
|
||||
return key[0] !== key[0].toLowerCase();
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
serialize(so, keys[i], val[keys[i]]);
|
||||
}
|
||||
STInt8.serialize(so, 0xe1); //Object ending marker
|
||||
if (!no_marker) STInt8.serialize(so, 0xe1); //Object ending marker
|
||||
},
|
||||
|
||||
parse: function (so) {
|
||||
@@ -617,6 +677,8 @@ var STObject = exports.Object = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STObject.id = 14;
|
||||
|
||||
var STArray = exports.Array = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
for (var i=0, l=val.length; i<l; i++) {
|
||||
@@ -649,6 +711,8 @@ var STArray = exports.Array = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STArray.id = 15;
|
||||
|
||||
var TYPES_MAP = [
|
||||
void(0),
|
||||
|
||||
|
||||
174
src/js/ripple/shamap.js
Normal file
174
src/js/ripple/shamap.js
Normal file
@@ -0,0 +1,174 @@
|
||||
var util = require('util');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var stypes = require('./serializedtypes');
|
||||
var hashprefixes = require('./hashprefixes');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
|
||||
function SHAMap() {
|
||||
this.root = new SHAMapTreeNodeInner();
|
||||
};
|
||||
|
||||
SHAMap.prototype.add_item = function (tag, node, type) {
|
||||
var node = new SHAMapTreeNodeLeaf(tag, node, type);
|
||||
this.root.add_item(tag, node);
|
||||
};
|
||||
|
||||
SHAMap.prototype.hash = function () {
|
||||
return this.root.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class representing a node in a SHAMap tree.
|
||||
*
|
||||
* Can be either SHAMapTreeNodeInner or SHAMapTreeNodeLeaf.
|
||||
*/
|
||||
function SHAMapTreeNode() {
|
||||
|
||||
};
|
||||
|
||||
SHAMapTreeNode.TYPE_INNER = 1;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_NM = 2;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_MD = 3;
|
||||
SHAMapTreeNode.TYPE_ACCOUNT_STATE = 4;
|
||||
|
||||
SHAMapTreeNode.prototype.add_item = function (tag_segment, node) {
|
||||
throw new Error("Called unimplemented virtual method SHAMapTreeNode#add_item.");
|
||||
};
|
||||
|
||||
SHAMapTreeNode.prototype.hash = function () {
|
||||
throw new Error("Called unimplemented virtual method SHAMapTreeNode#hash.");
|
||||
};
|
||||
|
||||
/**
|
||||
* Inner (non-leaf) node in a SHAMap tree.
|
||||
*/
|
||||
function SHAMapTreeNodeInner() {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
this.leaves = {};
|
||||
|
||||
this.type = SHAMapTreeNode.INNER;
|
||||
|
||||
this.empty = true;
|
||||
}
|
||||
|
||||
util.inherits(SHAMapTreeNodeInner, SHAMapTreeNode);
|
||||
|
||||
SHAMapTreeNodeInner.prototype.add_item = function (tag_segment, node) {
|
||||
var current_node = this.get_node(tag_segment);
|
||||
|
||||
if (current_node) {
|
||||
// A node already exists in this slot
|
||||
|
||||
if (current_node instanceof SHAMapTreeNodeInner) {
|
||||
// There is an inner node, so we need to go deeper
|
||||
current_node.add_item(tag_segment.slice(1), node);
|
||||
} else if (current_node.get_segment() === tag_segment) {
|
||||
// Collision
|
||||
throw new Error("Tried to add a node to a SHAMap that was already in there.");
|
||||
} else {
|
||||
// Turn it into an inner node
|
||||
var new_inner_node = new SHAMapTreeNodeInner();
|
||||
|
||||
// Move the existing leaf node down one level
|
||||
current_node.set_segment(current_node.get_segment().slice(1));
|
||||
new_inner_node.set_node(current_node.get_segment()[0], current_node);
|
||||
|
||||
// Add the new node next to it
|
||||
node.set_segment(tag_segment.slice(1));
|
||||
new_inner_node.set_node(tag_segment[1], node);
|
||||
|
||||
// And place the newly created inner node in the slot
|
||||
this.set_node(tag_segment[0], new_inner_node);
|
||||
}
|
||||
} else {
|
||||
// Neat, we have a nice open spot for the new node
|
||||
node.set_segment(tag_segment);
|
||||
this.set_node(tag_segment[0], node);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Overwrite the node that is currently in a given slot.
|
||||
*/
|
||||
SHAMapTreeNodeInner.prototype.set_node = function (slot, node) {
|
||||
this.leaves[slot] = node;
|
||||
this.empty = false;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.get_node = function (slot) {
|
||||
return this.leaves[slot];
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.hash = function () {
|
||||
if (this.empty) {
|
||||
return UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
}
|
||||
|
||||
var hash_buffer = new SerializedObject();
|
||||
var buffer = [];
|
||||
for (var i = 0; i < 16; i++) {
|
||||
var leafHash = UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
var slot = i.toString(16).toUpperCase();
|
||||
if ("object" === typeof this.leaves[slot]) {
|
||||
leafHash = this.leaves[slot].hash();
|
||||
}
|
||||
hash_buffer.append(leafHash.to_bytes());
|
||||
}
|
||||
|
||||
var hash = hash_buffer.hash(hashprefixes.HASH_INNER_NODE);
|
||||
|
||||
return UInt256.from_bits(hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaf node in a SHAMap tree.
|
||||
*/
|
||||
function SHAMapTreeNodeLeaf(tag, node, type) {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
if ("string" === typeof tag) {
|
||||
tag = UInt256.from_hex(tag);
|
||||
} else if (tag instanceof UInt256) {
|
||||
// Type is already the right one
|
||||
} else {
|
||||
throw new Error("Tag is unexpected type.");
|
||||
}
|
||||
|
||||
this.tag = tag;
|
||||
this.tag_segment = null;
|
||||
|
||||
this.type = type;
|
||||
|
||||
this.node = node;
|
||||
}
|
||||
util.inherits(SHAMapTreeNodeLeaf, SHAMapTreeNode);
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.get_segment = function (segment) {
|
||||
return this.tag_segment;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.set_segment = function (segment) {
|
||||
this.tag_segment = segment;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.hash = function () {
|
||||
var buffer = new SerializedObject();
|
||||
switch (this.type) {
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_NM:
|
||||
return this.tag;
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_MD:
|
||||
buffer.append(this.node);
|
||||
buffer.append(this.tag.to_bytes());
|
||||
return buffer.hash(hashprefixes.HASH_TX_NODE);
|
||||
default:
|
||||
throw new Error("Tried to hash a SHAMap node of unknown type.");
|
||||
}
|
||||
};
|
||||
|
||||
exports.SHAMap = SHAMap;
|
||||
exports.SHAMapTreeNode = SHAMapTreeNode;
|
||||
exports.SHAMapTreeNodeInner = SHAMapTreeNodeInner;
|
||||
exports.SHAMapTreeNodeLeaf = SHAMapTreeNodeLeaf;
|
||||
@@ -54,6 +54,8 @@ var Seed = require('./seed').Seed;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
var RippleError = require('./rippleerror').RippleError;
|
||||
|
||||
var hashprefixes = require('./hashprefixes');
|
||||
|
||||
var config = require('./config');
|
||||
|
||||
// A class to implement transactions.
|
||||
@@ -120,13 +122,6 @@ Transaction.flags = {
|
||||
|
||||
Transaction.formats = require('./binformat').tx;
|
||||
|
||||
// transaction plus signature to give transaction ID
|
||||
Transaction.HASH_TXID = 0x54584E00; // 'TXN'
|
||||
// inner transaction to sign
|
||||
Transaction.HASH_SIGN = 0x53545800; // 'STX'
|
||||
// inner transaction to sign (TESTNET)
|
||||
Transaction.HASH_SIGN_TESTNET = 0x73747800; // 'stx'
|
||||
|
||||
Transaction.prototype.consts = {
|
||||
telLOCAL_ERROR : -399,
|
||||
temMALFORMED : -299,
|
||||
@@ -212,17 +207,17 @@ Transaction.prototype.serialize = function () {
|
||||
};
|
||||
|
||||
Transaction.prototype.signing_hash = function () {
|
||||
return this.hash(config.testnet ? 'HASH_SIGN_TESTNET' : 'HASH_SIGN');
|
||||
return this.hash(config.testnet ? 'HASH_TX_SIGN_TESTNET' : 'HASH_TX_SIGN');
|
||||
};
|
||||
|
||||
Transaction.prototype.hash = function (prefix, as_uint256) {
|
||||
if ("string" === typeof prefix) {
|
||||
if ("undefined" === typeof Transaction[prefix]) {
|
||||
if ("undefined" === typeof hashprefixes[prefix]) {
|
||||
throw new Error("Unknown hashing prefix requested.");
|
||||
}
|
||||
prefix = Transaction[prefix];
|
||||
prefix = hashprefixes[prefix];
|
||||
} else if (!prefix) {
|
||||
prefix = Transaction['HASH_TXID'];
|
||||
prefix = hashprefixes['HASH_TX_ID'];
|
||||
}
|
||||
var hash = SerializedObject.from_json(this.tx_json).hash(prefix);
|
||||
|
||||
|
||||
@@ -3,6 +3,57 @@ var assert = require('assert');
|
||||
var Transaction = utils.load_module('transaction').Transaction;
|
||||
|
||||
describe('Transaction', function() {
|
||||
it('Serialization', function() {
|
||||
var input_json = {
|
||||
Account : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
Amount : {
|
||||
currency : "LTC",
|
||||
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
value : "9.985"
|
||||
},
|
||||
Destination : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
Fee : "15",
|
||||
Flags : 0,
|
||||
Paths : [
|
||||
[
|
||||
{
|
||||
account : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
currency : "USD",
|
||||
issuer : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
type : 49,
|
||||
type_hex : "0000000000000031"
|
||||
},
|
||||
{
|
||||
currency : "LTC",
|
||||
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
type : 48,
|
||||
type_hex : "0000000000000030"
|
||||
},
|
||||
{
|
||||
account : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
currency : "LTC",
|
||||
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
type : 49,
|
||||
type_hex : "0000000000000031"
|
||||
}
|
||||
]
|
||||
],
|
||||
SendMax : {
|
||||
currency : "USD",
|
||||
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
value : "30.30993068"
|
||||
},
|
||||
Sequence : 415,
|
||||
SigningPubKey : "02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A",
|
||||
TransactionType : "Payment",
|
||||
TxnSignature : "304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E"
|
||||
};
|
||||
var expected_hex = "1200002200000000240000019F61D4A3794DFA1510000000000000000000000000004C54430000000000EF7ED76B77750D79EC92A59389952E0E8054407668400000000000000F69D4CAC4AC112283000000000000000000000000005553440000000000EF7ED76B77750D79EC92A59389952E0E80544076732102854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A7448304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E8114EF7ED76B77750D79EC92A59389952E0E805440768314EF7ED76B77750D79EC92A59389952E0E80544076011231DD39C650A96EDA48334E70CC4A85B8B2E8502CD30000000000000000000000005553440000000000DD39C650A96EDA48334E70CC4A85B8B2E8502CD3300000000000000000000000004C5443000000000047DA9E2E00ECF224A52329793F1BB20FB1B5EA643147DA9E2E00ECF224A52329793F1BB20FB1B5EA640000000000000000000000004C5443000000000047DA9E2E00ECF224A52329793F1BB20FB1B5EA6400";
|
||||
var transaction = Transaction.from_json(input_json);
|
||||
|
||||
assert.deepEqual(transaction.serialize().to_hex(), expected_hex);
|
||||
});
|
||||
|
||||
it('Hashing', function() {
|
||||
var input_json = {
|
||||
Account : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
|
||||
Reference in New Issue
Block a user