-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add experimental ChainObjects serializer & encoder (#242)
- Loading branch information
Showing
11 changed files
with
1,091 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const ObjectTags = require('./ChainObjectTags') | ||
|
||
class ChainObject { | ||
constructor(name, fields) { | ||
this.name = name | ||
this.vsn = fields.version || fields.header?.version || 1n | ||
|
||
Object.assign(this, fields) | ||
} | ||
|
||
get tag() { | ||
return ObjectTags[this.name.toUpperCase()] | ||
} | ||
} | ||
|
||
module.exports = ChainObject |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
const ChainObject = require('./ChainObject') | ||
const Templates = require('./ChainObjectTemplates') | ||
|
||
class ChainObjectEncoder { | ||
/** | ||
* @param {FieldsEncoder} fieldsEncoder | ||
*/ | ||
constructor(fieldsEncoder) { | ||
this.fieldsEncoder = fieldsEncoder | ||
} | ||
|
||
encode(object) { | ||
const {name, vsn} = object | ||
const template = Templates[name.toUpperCase()][vsn] | ||
|
||
if (template === undefined) { | ||
throw new Error(`Unsupported template version "${vsn}" for object type "${name}"`) | ||
} | ||
|
||
const encoded = this.fieldsEncoder.encodeFields(object, template) | ||
|
||
return new Uint8Array(encoded.flatMap(e => [...e])) | ||
} | ||
|
||
// Decoding a fixed size object | ||
// alternative compared to fields based decoding above | ||
// works based on object field sizes used to split on fields | ||
decode(name, data) { | ||
// version field is 4 bytes and should be always first | ||
// note that it may exists other objects with different version size? | ||
const {vsn} = this.fieldsEncoder.decodeFields([data.slice(0, 4)], {vsn: 'uint_32'}) | ||
|
||
const template = Templates[name.toUpperCase()][vsn] | ||
const binaryFields = this.fieldsEncoder.splitFields(data, template) | ||
const fields = this.fieldsEncoder.decodeFields(binaryFields, template) | ||
|
||
return new ChainObject(name, fields) | ||
} | ||
} | ||
|
||
module.exports = ChainObjectEncoder |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
const RLP = require('rlp') | ||
const ChainObject = require('./ChainObject') | ||
const ObjectTags = require('./ChainObjectTags') | ||
const Templates = require('./ChainObjectTemplates') | ||
|
||
class ChainObjectSerializer { | ||
/** | ||
* @param {FieldsEncoder} fieldsEncoder | ||
*/ | ||
constructor(fieldsEncoder) { | ||
this.fieldsEncoder = fieldsEncoder | ||
} | ||
|
||
serialize(chainObject) { | ||
const {name, tag, vsn} = chainObject | ||
|
||
const template = Templates[name.toUpperCase()][vsn] | ||
if (template === undefined) { | ||
throw new Error(`Unsupported template version "${vsn}" for object type "${name}"`) | ||
} | ||
|
||
const serializedFields = this.#serializeFields(tag, vsn, template, chainObject) | ||
|
||
return new Uint8Array(serializedFields) | ||
} | ||
|
||
deserialize(data) { | ||
const {tag, vsn, rest} = this.#deserializeHeader(data) | ||
const type = Object.keys(ObjectTags).find(key => ObjectTags[key] === Number(tag)) | ||
|
||
if (type === undefined) { | ||
throw new Error(`Unsupported object tag: ${tag}`) | ||
} | ||
|
||
if (!Templates.hasOwnProperty(type)) { | ||
return new ChainObject(type.toLowerCase(), {}) | ||
} | ||
|
||
if (!Templates[type].hasOwnProperty(vsn)) { | ||
throw new Error(`Unsupported template version "${vsn}" for object type "${type}"`) | ||
} | ||
|
||
const template = Templates[type][vsn] | ||
const fields = this.fieldsEncoder.decodeFields(rest, template) | ||
|
||
return new ChainObject(type.toLowerCase(), {vsn, ...fields}) | ||
} | ||
|
||
#deserializeHeader(data) { | ||
const objData = RLP.decode(data) | ||
const template = {tag: 'int', vsn: 'int'} | ||
|
||
const {tag, vsn} = this.fieldsEncoder.decodeFields(objData, template) | ||
const rest = objData.slice(2) | ||
|
||
return {tag, vsn, rest} | ||
} | ||
|
||
#serializeFields(tag, vsn, template, fields) { | ||
const allFields = {tag, vsn, ...fields} | ||
const fullTemplate = {tag: 'int', vsn: 'int', ...template} | ||
const data = this.fieldsEncoder.encodeFields(allFields, fullTemplate) | ||
|
||
return [...RLP.encode(data)] | ||
} | ||
|
||
#deserializeFields(template, data) { | ||
const objData = RLP.decode(data) | ||
const fullTemplate = {tag: 'int', vsn: 'int', ...template} | ||
const {_tag, _vsn, ...fields} = this.fieldsEncoder.decodeFields(objData, fullTemplate) | ||
|
||
return fields | ||
} | ||
} | ||
|
||
module.exports = ChainObjectSerializer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
const ChainObjectTags = Object.freeze({ | ||
ACCOUNT: 10, | ||
SIGNED_TX: 11, | ||
SPEND_TX: 12, | ||
ORACLE: 20, | ||
ORACLE_QUERY: 21, | ||
ORACLE_REGISTER_TX: 22, | ||
ORACLE_QUERY_TX: 23, | ||
ORACLE_RESPONSE_TX: 24, | ||
ORACLE_EXTEND_TX: 25, | ||
NAME: 30, | ||
NAME_COMMITMENT: 31, | ||
NAME_CLAIM_TX: 32, | ||
NAME_PRECLAIM_TX: 33, | ||
NAME_UPDATE_TX: 34, | ||
NAME_REVOKE_TX: 35, | ||
NAME_TRANSFER_TX: 36, | ||
NAME_AUCTION: 37, | ||
CONTRACT: 40, | ||
CONTRACT_CALL: 41, | ||
CONTRACT_CREATE_TX: 42, | ||
CONTRACT_CALL_TX: 43, | ||
CHANNEL_CREATE_TX: 50, | ||
CHANNEL_SET_DELEGATES_TX: 501, | ||
CHANNEL_DEPOSIT_TX: 51, | ||
CHANNEL_WITHDRAW_TX: 52, | ||
CHANNEL_FORCE_PROGRESS_TX: 521, | ||
CHANNEL_CLOSE_MUTUAL_TX: 53, | ||
CHANNEL_CLOSE_SOLO_TX: 54, | ||
CHANNEL_SLASH_TX: 55, | ||
CHANNEL_SETTLE_TX: 56, | ||
CHANNEL_OFFCHAIN_TX: 57, | ||
CHANNEL_OFFCHAIN_UPDATE_TRANSFER: 570, | ||
CHANNEL_OFFCHAIN_UPDATE_DEPOSIT: 571, | ||
CHANNEL_OFFCHAIN_UPDATE_WITHDRAW: 572, | ||
CHANNEL_OFFCHAIN_UPDATE_CREATE_CONTRACT: 573, | ||
CHANNEL_OFFCHAIN_UPDATE_CALL_CONTRACT: 574, | ||
CHANNEL_OFFCHAIN_UPDATE_META: 576, | ||
CHANNEL_CLIENT_RECONNECT_TX: 575, | ||
CHANNEL: 58, | ||
CHANNEL_SNAPSHOT_SOLO_TX: 59, | ||
TREES_POI: 60, | ||
TREES_DB: 61, | ||
STATE_TREES: 62, | ||
MTREE: 63, | ||
MTREE_VALUE: 64, | ||
CONTRACTS_MTREE: 621, | ||
CALLS_MTREE: 622, | ||
CHANNELS_MTREE: 623, | ||
NAMESERVICE_MTREE: 624, | ||
ORACLES_MTREE: 625, | ||
ACCOUNTS_MTREE: 626, | ||
COMPILER_SOPHIA: 70, | ||
GA_ATTACH_TX: 80, | ||
GA_META_TX: 81, | ||
PAYING_FOR_TX: 82, | ||
GA_META_TX_AUTH_DATA: 810, | ||
KEY_BLOCK: 100, | ||
MICRO_BLOCK_BODY: 101, | ||
LIGHT_MICRO_BLOCK: 102, | ||
POF: 200, | ||
}) | ||
|
||
module.exports = ChainObjectTags |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
const ChainObjectTemplates = Object.freeze({ | ||
ACCOUNT: { | ||
1: { | ||
nonce: 'int', | ||
balance: 'int', | ||
} | ||
}, | ||
SIGNED_TX: { | ||
1: { | ||
signatures: ['signature'], | ||
transaction: 'chain_object', | ||
} | ||
}, | ||
SPEND_TX: { | ||
1: { | ||
sender: 'id', | ||
recipient: 'id', | ||
amount: 'int', | ||
fee: 'int', | ||
ttl: 'int', | ||
nonce: 'int', | ||
payload: 'bytearray', | ||
} | ||
}, | ||
LIGHT_MICRO_BLOCK: { | ||
5: { | ||
header: { | ||
type: 'object', | ||
template: { | ||
version: 'uint_32', // protocol version? | ||
flags: 'uint_32', | ||
height: 'uint_64', | ||
prevHash: 'micro_block_hash', | ||
prevKeyHash: 'key_block_hash', | ||
stateHash: 'block_state_hash', | ||
txsHash: 'block_tx_hash', | ||
time: 'uint_64', | ||
// fraudHash: 'block_pof_hash', | ||
signature: 'signature', | ||
} | ||
}, | ||
txHashes: ['tx_hash'], | ||
pof: 'binary', | ||
} | ||
}, | ||
MICRO_BLOCK_BODY: { | ||
5: { | ||
txs: ['chain_object'], | ||
pof: 'binary' | ||
} | ||
}, | ||
MICRO_BLOCK: { | ||
5: { | ||
header: { | ||
type: 'object', | ||
template: { | ||
version: 'uint_32', | ||
flags: 'uint_32', | ||
height: 'uint_64', | ||
prevHash: 'micro_block_hash', | ||
prevKeyHash: 'key_block_hash', | ||
stateHash: 'block_state_hash', | ||
txsHash: 'block_tx_hash', | ||
time: 'uint_64', | ||
// fraudHash: 'block_pof_hash', | ||
signature: 'signature', | ||
} | ||
}, | ||
body: 'chain_object' | ||
} | ||
}, | ||
KEY_BLOCK: { | ||
1: { | ||
version: 'uint_32', | ||
flags: 'uint_32', | ||
height: 'uint_64', | ||
prevHash: 'micro_block_hash', | ||
prevKeyHash: 'key_block_hash', | ||
stateHash: 'block_state_hash', | ||
miner: 'account_pubkey', | ||
beneficiary: 'account_pubkey', | ||
target: 'uint_32', | ||
pow: 'pow', | ||
nonce: 'uint_64', | ||
time: 'uint_64', | ||
}, | ||
5: { | ||
version: 'uint_32', | ||
flags: 'uint_32', | ||
height: 'uint_64', | ||
prevHash: 'micro_block_hash', | ||
prevKeyHash: 'key_block_hash', | ||
stateHash: 'block_state_hash', | ||
miner: 'account_pubkey', | ||
beneficiary: 'account_pubkey', | ||
target: 'uint_32', | ||
pow: 'pow', | ||
nonce: 'uint_64', | ||
time: 'uint_64', | ||
// binary, but currently interpreted as int/node version | ||
info: 'uint_32' | ||
} | ||
}, | ||
COMPILER_SOPHIA: { | ||
3: { | ||
sourceHash: 'hex', | ||
typeInfo: [{ | ||
hash: 'binary', | ||
name: 'binary', | ||
payable: 'bool', | ||
argType: 'binary', | ||
returnType: 'binary' | ||
}], | ||
byteCode: 'binary', | ||
compilerVersion: 'string', | ||
payable: 'bool' | ||
} | ||
} | ||
}) | ||
|
||
module.exports = ChainObjectTemplates |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
const ApiEncoder = require('../ApiEncoder') | ||
const ChainObjectSerializer = require('./ChainObjectSerializer') | ||
const ChainObjectEncoder = require('./ChainObjectEncoder') | ||
const PrimitivesEncoder = require('../PrimitivesEncoder') | ||
const IdEncoder = require('../IdEncoder') | ||
const FieldsEncoder = require('./FieldsEncoder') | ||
|
||
class FieldEncoder { | ||
constructor(additionalEncoders, additionalDecoders) { | ||
this.primEncoder = new PrimitivesEncoder() | ||
this.apiEncoder = new ApiEncoder() | ||
this.idEncoder = new IdEncoder(this.apiEncoder) | ||
|
||
const fieldsEncoder = new FieldsEncoder(this) | ||
this.chainObjectEncoder = new ChainObjectEncoder(fieldsEncoder) | ||
this.chainObjectSerializer = new ChainObjectSerializer(fieldsEncoder) | ||
|
||
this.decoders = { | ||
key_block_hash: (value) => this.apiEncoder.encode('key_block_hash', value), | ||
micro_block_hash: (value) => this.apiEncoder.encode('micro_block_hash', value), | ||
block_pof_hash: (value) => this.apiEncoder.encode('block_pof_hash', value), | ||
block_tx_hash: (value) => this.apiEncoder.encode('block_tx_hash', value), | ||
block_state_hash: (value) => this.apiEncoder.encode('block_state_hash', value), | ||
signature: (value) => this.apiEncoder.encode('signature', value), | ||
peer_pubkey: (value) => this.apiEncoder.encode('peer_pubkey', value), | ||
account_pubkey: (value) => this.apiEncoder.encode('account_pubkey', value), | ||
tx_hash: (value) => this.apiEncoder.encode('tx_hash', value), | ||
bytearray: (value) => this.apiEncoder.encode('bytearray', value), | ||
id: (value) => this.idEncoder.encode(value), | ||
key_block: (value, _params) => this.chainObjectEncoder.decode('key_block', value), | ||
micro_block: (value, _params) => this.chainObjectEncoder.decode('micro_block', value), | ||
light_micro_block: (value, _params) => this.chainObjectEncoder.decode('light_micro_block', value), | ||
chain_object: (value) => this.chainObjectSerializer.deserialize(value), | ||
...additionalDecoders | ||
} | ||
|
||
this.encoders = { | ||
key_block_hash: (value) => this.apiEncoder.decode(value), | ||
micro_block_hash: (value) => this.apiEncoder.decode(value), | ||
block_pof_hash: (value) => this.apiEncoder.decode(value), | ||
block_tx_hash: (value) => this.apiEncoder.decode(value), | ||
block_state_hash: (value) => this.apiEncoder.decode(value), | ||
signature: (value) => this.apiEncoder.decode(value), | ||
peer_pubkey: (value) => this.apiEncoder.decode(value), | ||
account_pubkey: (value) => this.apiEncoder.decode(value), | ||
tx_hash: (value) => this.apiEncoder.decode(value), | ||
bytearray: (value) => this.apiEncoder.decode(value), | ||
id: (value) => this.idEncoder.decode(value), | ||
key_block: (value, _params) => this.chainObjectEncoder.encode(value), | ||
micro_block: (value, _params) => this.chainObjectEncoder.encode(value), | ||
light_micro_block: (value, _params) => this.chainObjectEncoder.encode(value), | ||
chain_object: (value) => this.chainObjectSerializer.serialize(value), | ||
...additionalEncoders | ||
} | ||
} | ||
|
||
encode(type, value, params) { | ||
return this.encoders.hasOwnProperty(type) ? this.encoders[type](value, params) : value | ||
} | ||
|
||
decode(type, value, params) { | ||
return this.decoders.hasOwnProperty(type) ? this.decoders[type](value, params) : value | ||
} | ||
} | ||
|
||
module.exports = FieldEncoder |
Oops, something went wrong.