parsec-node
Version:
https://parseclabs.readthedocs.io/en/latest/
199 lines (184 loc) • 4.88 kB
JavaScript
/* eslint-disable no-await-in-loop */
const createABCIServer = require('abci');
const decodeTx = require('./tx-encoding.js').decode;
const jsondiffpatch = require('jsondiffpatch');
const getRoot = require('./get-root.js');
const { stringify } = require('deterministic-json');
async function runTx(
txMiddleware,
store,
tx,
chainInfo,
allowMutation = false
) {
let stateMutated = false;
// TODO: mutate store then use merk.rollback instead of cloning state
const newChainInfo = jsondiffpatch.clone(chainInfo);
const newState = jsondiffpatch.clone(store);
const proxy = {
get: (target, name) => {
if (typeof target[name] === 'object' && target[name] !== null) {
return new Proxy(target[name], proxy);
}
return target[name];
},
set: (target, name, newValue) => {
const oldValue = target[name];
target[name] = newValue;
if (newValue !== oldValue) {
stateMutated = true;
}
return true;
},
};
const hookedState = new Proxy(newState, proxy);
const hookedChainInfo = new Proxy(chainInfo, proxy);
// run middleware stack
try {
for (const txHandler of txMiddleware) {
await txHandler(hookedState, tx, hookedChainInfo, !allowMutation);
}
} catch (e) {
return [false, e.toString()];
}
if (allowMutation) {
Object.assign(chainInfo, newChainInfo);
Object.assign(store, newState);
} else {
// merk.rollback(store)
}
return [
stateMutated,
stateMutated ? '' : 'transaction must mutate state to be valid',
];
}
function updateAndDiffValidators(validators, newValidators) {
const diffs = [];
for (const key in newValidators) {
if (validators[key] === undefined) {
validators[key] = newValidators[key];
diffs.push(validators[key]);
} else if (
typeof newValidators[key] === 'number' &&
validators[key].power !== newValidators[key]
) {
validators[key].power = newValidators[key];
diffs.push(validators[key]);
}
}
return diffs;
}
class AbciApp {}
module.exports = function configureABCIServer({
txMiddleware,
blockMiddleware,
store,
// initialAppHash,
initChainMiddleware,
periodMiddleware,
}) {
const chainInfo = {
height: 1,
validators: {},
};
const lastValidatorState = {};
const abciApp = new AbciApp();
abciApp.checkTx = async req => {
const rawTx = req.tx;
try {
const tx = decodeTx(rawTx);
const [isValid, log] = await runTx(
txMiddleware,
store,
tx,
chainInfo,
false
);
const code = isValid ? 0 : 2;
return { code, log };
} catch (e) {
return { code: 2, log: 'Invalid tx encoding for checkTx' };
}
};
abciApp.deliverTx = async req => {
const rawTx = req.tx;
try {
const tx = decodeTx(rawTx);
const [isValid, log] = await runTx(
txMiddleware,
store,
tx,
chainInfo,
true
);
if (isValid) {
return { code: 0 };
}
return { code: 2, log };
} catch (e) {
return { code: 2, log: 'Invalid tx encoding for deliverTx' };
}
};
abciApp.commit = async () => {
chainInfo.height += 1;
for (const blockHandler of blockMiddleware) {
await blockHandler(store, chainInfo);
}
const appHash = await getRoot(store);
return { data: appHash };
};
abciApp.initChain = async ({ validators }) => {
validators.forEach(tmValidator => {
const address = tmValidator.address.toString('base64');
chainInfo.validators[address] = {
address,
pubKey: {
type: tmValidator.pubKey.type,
data: tmValidator.pubKey.data.toString('base64'),
},
power: Number(tmValidator.power),
};
});
Object.assign(lastValidatorState, chainInfo.validators);
initChainMiddleware.forEach(handler => {
handler(chainInfo);
});
return {};
};
abciApp.endBlock = () => {
return {
validatorUpdates: updateAndDiffValidators(
lastValidatorState,
chainInfo.validators
),
};
};
abciApp.checkBridge = async ({ height }) => {
const rsp = {};
for (let i = 0; i < periodMiddleware.length; i += 1) {
await periodMiddleware[i](rsp, chainInfo, height);
}
return { status: rsp.status };
};
abciApp.query = () => {
try {
return {
value: Buffer.from(stringify(store)),
height: chainInfo.height - 1,
proof: '',
key: '',
index: 0,
code: 0,
log: '',
};
} catch (e) {
return { code: 2, log: `invalid query: ${e.message}` };
}
};
abciApp.info = async () => {
const rootHash = await getRoot(store);
return { lastBlockAppHash: rootHash };
};
const abciServer = createABCIServer(abciApp);
return abciServer;
};