@neo-one/node-blockchain-esnext-cjs
Version:
NEO•ONE NEO blockchain implementation.
922 lines (800 loc) • 30.4 kB
text/typescript
import {
Block,
common,
ConsensusPayload,
crypto,
ECPoint,
Header,
Input,
InvocationResult,
InvocationTransaction,
Output,
OutputKey,
ScriptBuilder,
ScriptContainerType,
SerializableInvocationData,
Settings,
Transaction,
TransactionBase,
TransactionData,
UInt160,
utils,
Validator,
VerifyScriptOptions,
} from '@neo-one/client-core-esnext-cjs';
import { metrics, Monitor } from '@neo-one/monitor-esnext-cjs';
import { Blockchain as BlockchainType, NULL_ACTION, Storage, TriggerType, VM } from '@neo-one/node-core-esnext-cjs';
import { labels, utils as commonUtils } from '@neo-one/utils-esnext-cjs';
import { BN } from 'bn.js';
import PriorityQueue from 'js-priority-queue';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { toArray } from 'rxjs/operators';
import {
CoinClaimedError,
CoinUnspentError,
GenesisBlockNotRegisteredError,
InvalidClaimError,
ScriptVerifyError,
UnknownVerifyError,
WitnessVerifyError,
} from './errors';
import { getValidators } from './getValidators';
import { wrapExecuteScripts } from './wrapExecuteScripts';
import { WriteBatchBlockchain } from './WriteBatchBlockchain';
export interface CreateBlockchainOptions {
readonly settings: Settings;
readonly storage: Storage;
readonly vm: VM;
readonly monitor: Monitor;
}
export interface BlockchainOptions extends CreateBlockchainOptions {
readonly currentBlock: BlockchainType['currentBlock'] | undefined;
readonly previousBlock: BlockchainType['previousBlock'] | undefined;
readonly currentHeader: BlockchainType['currentHeader'] | undefined;
}
interface SpentCoin {
readonly output: Output;
readonly startHeight: number;
readonly endHeight: number;
readonly claimed: boolean;
}
interface Entry {
readonly monitor: Monitor;
readonly block: Block;
readonly resolve: () => void;
readonly reject: (error: Error) => void;
readonly unsafe: boolean;
}
const NAMESPACE = 'blockchain';
const NEO_BLOCKCHAIN_PERSIST_BLOCK_DURATION_SECONDS = metrics.createHistogram({
name: 'neo_blockchain_persist_block_duration_seconds',
});
const NEO_BLOCKCHAIN_PERSIST_BLOCK_FAILURES_TOTAL = metrics.createCounter({
name: 'neo_blockchain_persist_block_failures_total',
});
const NEO_BLOCKCHAIN_BLOCK_INDEX_GAUGE = metrics.createGauge({
name: 'neo_blockchain_block_index',
help: 'The current block index',
});
const NEO_BLOCKCHAIN_PERSISTING_BLOCK_INDEX_GAUGE = metrics.createGauge({
name: 'neo_blockchain_persisting_block_index',
help: 'The current in progress persist index',
});
const NEO_BLOCKCHAIN_PERSIST_BLOCK_LATENCY_SECONDS = metrics.createHistogram({
name: 'neo_blockchain_persist_block_latency_seconds',
help: 'The latency from block timestamp to persist',
buckets: [1, 2, 5, 7.5, 10, 12.5, 15, 17.5, 20],
});
export class Blockchain {
public static async create({ settings, storage, vm, monitor }: CreateBlockchainOptions): Promise<BlockchainType> {
const [currentBlock, currentHeader] = await Promise.all([
storage.block.tryGetLatest(),
storage.header.tryGetLatest(),
]);
let previousBlock;
if (currentBlock !== undefined) {
previousBlock = await storage.block.tryGet({ hashOrIndex: currentBlock.index - 1 });
}
const blockchain = new Blockchain({
currentBlock,
currentHeader,
previousBlock,
settings,
storage,
vm,
monitor,
});
if (currentHeader === undefined) {
await blockchain.persistHeaders([settings.genesisBlock.header]);
}
if (currentBlock === undefined) {
await blockchain.persistBlock({ block: settings.genesisBlock });
}
return blockchain;
}
public readonly deserializeWireContext: BlockchainType['deserializeWireContext'];
public readonly serializeJSONContext: BlockchainType['serializeJSONContext'];
public readonly feeContext: BlockchainType['feeContext'];
private readonly monitor: Monitor;
private readonly settings$: BehaviorSubject<Settings>;
private readonly storage: Storage;
private mutableCurrentBlock: BlockchainType['currentBlock'] | undefined;
private mutablePreviousBlock: BlockchainType['currentBlock'] | undefined;
private mutableCurrentHeader: BlockchainType['currentHeader'] | undefined;
private mutablePersistingBlocks = false;
private mutableBlockQueue: PriorityQueue<Entry> = new PriorityQueue({
comparator: (a, b) => a.block.index - b.block.index,
});
private mutableInQueue: Set<string> = new Set();
private readonly vm: VM;
private mutableRunning = false;
private mutableDoneRunningResolve: (() => void) | undefined;
private mutableBlock$: Subject<Block> = new Subject();
private mutableWriteBlockchain: WriteBatchBlockchain | undefined;
public constructor(options: BlockchainOptions) {
this.storage = options.storage;
this.mutableCurrentBlock = options.currentBlock;
this.mutablePreviousBlock = options.previousBlock;
this.mutableCurrentHeader = options.currentHeader;
this.vm = options.vm;
this.settings$ = new BehaviorSubject(options.settings);
this.monitor = options.monitor.at(NAMESPACE);
NEO_BLOCKCHAIN_BLOCK_INDEX_GAUGE.set(this.currentBlockIndex);
NEO_BLOCKCHAIN_PERSISTING_BLOCK_INDEX_GAUGE.set(this.currentBlockIndex);
// tslint:disable-next-line no-this-assignment
const self = this;
this.deserializeWireContext = {
get messageMagic() {
return self.settings.messageMagic;
},
};
this.feeContext = {
get getOutput() {
return self.output.get;
},
get governingToken() {
return self.settings.governingToken;
},
get utilityToken() {
return self.settings.utilityToken;
},
get fees() {
return self.settings.fees;
},
get registerValidatorFee() {
return self.settings.registerValidatorFee;
},
};
this.serializeJSONContext = {
get addressVersion() {
return self.settings.addressVersion;
},
get feeContext() {
return self.feeContext;
},
get tryGetInvocationData() {
return self.tryGetInvocationData;
},
get tryGetTransactionData() {
return self.tryGetTransactionData;
},
get getUnclaimed() {
return self.getUnclaimed;
},
get getUnspent() {
return self.getUnspent;
},
};
this.mutableWriteBlockchain = this.createWriteBlockchain();
this.start();
}
public get settings(): Settings {
return this.settings$.getValue();
}
public get currentBlock(): Block {
if (this.mutableCurrentBlock === undefined) {
throw new GenesisBlockNotRegisteredError();
}
return this.mutableCurrentBlock;
}
public get previousBlock(): Block | undefined {
return this.mutablePreviousBlock;
}
public get currentHeader(): Header {
if (this.mutableCurrentHeader === undefined) {
throw new GenesisBlockNotRegisteredError();
}
return this.mutableCurrentHeader;
}
public get currentBlockIndex(): number {
return this.mutableCurrentBlock === undefined ? -1 : this.currentBlock.index;
}
public get block$(): Observable<Block> {
return this.mutableBlock$;
}
public get isPersistingBlock(): boolean {
return this.mutablePersistingBlocks;
}
public get account(): BlockchainType['account'] {
return this.mutableWriteBlockchain === undefined ? this.storage.account : this.mutableWriteBlockchain.account;
}
public get accountUnclaimed(): BlockchainType['accountUnclaimed'] {
return this.mutableWriteBlockchain === undefined
? this.storage.accountUnclaimed
: this.mutableWriteBlockchain.accountUnclaimed;
}
public get accountUnspent(): BlockchainType['accountUnspent'] {
return this.mutableWriteBlockchain === undefined
? this.storage.accountUnspent
: this.mutableWriteBlockchain.accountUnspent;
}
public get action(): BlockchainType['action'] {
return this.mutableWriteBlockchain === undefined ? this.storage.action : this.mutableWriteBlockchain.action;
}
public get asset(): BlockchainType['asset'] {
return this.mutableWriteBlockchain === undefined ? this.storage.asset : this.mutableWriteBlockchain.asset;
}
public get block(): BlockchainType['block'] {
return this.mutableWriteBlockchain === undefined ? this.storage.block : this.mutableWriteBlockchain.block;
}
public get blockData(): BlockchainType['blockData'] {
return this.mutableWriteBlockchain === undefined ? this.storage.blockData : this.mutableWriteBlockchain.blockData;
}
public get header(): BlockchainType['header'] {
return this.mutableWriteBlockchain === undefined ? this.storage.header : this.mutableWriteBlockchain.header;
}
public get transaction(): BlockchainType['transaction'] {
return this.mutableWriteBlockchain === undefined
? this.storage.transaction
: this.mutableWriteBlockchain.transaction;
}
public get transactionData(): BlockchainType['transactionData'] {
return this.mutableWriteBlockchain === undefined
? this.storage.transactionData
: this.mutableWriteBlockchain.transactionData;
}
public get output(): BlockchainType['output'] {
return this.mutableWriteBlockchain === undefined ? this.storage.output : this.mutableWriteBlockchain.output;
}
public get contract(): BlockchainType['contract'] {
return this.mutableWriteBlockchain === undefined ? this.storage.contract : this.mutableWriteBlockchain.contract;
}
public get storageItem(): BlockchainType['storageItem'] {
return this.mutableWriteBlockchain === undefined
? this.storage.storageItem
: this.mutableWriteBlockchain.storageItem;
}
public get validator(): BlockchainType['validator'] {
return this.mutableWriteBlockchain === undefined ? this.storage.validator : this.mutableWriteBlockchain.validator;
}
public get invocationData(): BlockchainType['invocationData'] {
return this.mutableWriteBlockchain === undefined
? this.storage.invocationData
: this.mutableWriteBlockchain.invocationData;
}
public get validatorsCount(): BlockchainType['validatorsCount'] {
return this.mutableWriteBlockchain === undefined
? this.storage.validatorsCount
: this.mutableWriteBlockchain.validatorsCount;
}
public async stop(): Promise<void> {
if (!this.mutableRunning) {
return;
}
if (this.mutablePersistingBlocks) {
// tslint:disable-next-line promise-must-complete
const doneRunningPromise = new Promise<void>((resolve) => {
this.mutableDoneRunningResolve = resolve;
});
this.mutableRunning = false;
await doneRunningPromise;
this.mutableDoneRunningResolve = undefined;
} else {
this.mutableRunning = false;
}
this.monitor.log({ name: 'neo_blockchain_stop' });
}
public updateSettings(settings: Settings): void {
this.settings$.next(settings);
}
public async persistBlock({
monitor,
block,
unsafe = false,
}: {
readonly monitor?: Monitor;
readonly block: Block;
readonly unsafe?: boolean;
}): Promise<void> {
// tslint:disable-next-line promise-must-complete
return new Promise<void>((resolve, reject) => {
if (this.mutableInQueue.has(block.hashHex)) {
return;
}
this.mutableInQueue.add(block.hashHex);
this.mutableBlockQueue.queue({
monitor: this.getMonitor(monitor),
block,
resolve,
reject,
unsafe,
});
// tslint:disable-next-line no-floating-promises
this.persistBlocksAsync();
});
}
public async persistHeaders(_headers: ReadonlyArray<Header>): Promise<void> {
// We don't ever just persist the headers.
}
public async verifyBlock(block: Block, monitor?: Monitor): Promise<void> {
await this.getMonitor(monitor)
.withData({ [labels.NEO_BLOCK_INDEX]: block.index })
.captureSpan(
async (span) =>
block.verify({
genesisBlock: this.settings.genesisBlock,
tryGetBlock: this.block.tryGet,
tryGetHeader: this.header.tryGet,
isSpent: this.isSpent,
getAsset: this.asset.get,
getOutput: this.output.get,
tryGetAccount: this.account.tryGet,
getValidators: this.getValidators,
standbyValidators: this.settings.standbyValidators,
getAllValidators: this.getAllValidators,
calculateClaimAmount: async (claims) => this.calculateClaimAmount(claims, span),
verifyScript: async (options) => this.verifyScript(options, span),
currentHeight: this.mutableCurrentBlock === undefined ? 0 : this.mutableCurrentBlock.index,
governingToken: this.settings.governingToken,
utilityToken: this.settings.utilityToken,
fees: this.settings.fees,
registerValidatorFee: this.settings.registerValidatorFee,
}),
{ name: 'neo_blockchain_verify_block' },
);
}
public async verifyConsensusPayload(payload: ConsensusPayload, monitor?: Monitor): Promise<void> {
await this.getMonitor(monitor)
.withData({ [labels.NEO_CONSENSUS_HASH]: payload.hashHex })
.captureSpan(
async (span) =>
payload.verify({
getValidators: async () => this.getValidators([], span),
verifyScript: async (options) => this.verifyScript(options, span),
currentIndex: this.mutableCurrentBlock === undefined ? 0 : this.mutableCurrentBlock.index,
currentBlockHash: this.currentBlock.hash,
}),
{ name: 'neo_blockchain_verify_consensus' },
);
}
public async verifyTransaction({
monitor,
transaction,
memPool,
}: {
readonly monitor?: Monitor;
readonly transaction: Transaction;
readonly memPool?: ReadonlyArray<Transaction>;
}): Promise<void> {
try {
await this.getMonitor(monitor)
.withData({ [labels.NEO_TRANSACTION_HASH]: transaction.hashHex })
.captureSpan(
async (span) =>
transaction.verify({
calculateClaimAmount: this.calculateClaimAmount,
isSpent: this.isSpent,
getAsset: this.asset.get,
getOutput: this.output.get,
tryGetAccount: this.account.tryGet,
standbyValidators: this.settings.standbyValidators,
getAllValidators: this.getAllValidators,
verifyScript: async (options) => this.verifyScript(options, span),
governingToken: this.settings.governingToken,
utilityToken: this.settings.utilityToken,
fees: this.settings.fees,
registerValidatorFee: this.settings.registerValidatorFee,
currentHeight: this.currentBlockIndex,
memPool,
}),
{ name: 'neo_blockchain_verify_transaction' },
);
} catch (error) {
if (error.code === undefined || typeof error.code !== 'string' || !error.code.includes('VERIFY')) {
throw new UnknownVerifyError(error.message);
}
throw error;
}
}
public async invokeScript(script: Buffer, monitor?: Monitor): Promise<InvocationResult> {
const transaction = new InvocationTransaction({
script,
gas: common.ONE_HUNDRED_FIXED8,
});
return this.invokeTransaction(transaction, monitor);
}
public async invokeTransaction(transaction: InvocationTransaction, monitor?: Monitor): Promise<InvocationResult> {
const blockchain = this.createWriteBlockchain();
return wrapExecuteScripts(async () =>
this.vm.executeScripts({
monitor: this.getMonitor(monitor),
scripts: [{ code: transaction.script }],
blockchain,
scriptContainer: {
type: ScriptContainerType.Transaction,
value: transaction,
},
triggerType: TriggerType.Application,
action: NULL_ACTION,
gas: transaction.gas,
skipWitnessVerify: true,
}),
);
}
public async reset(): Promise<void> {
await this.stop();
await this.storage.reset();
this.mutableCurrentHeader = undefined;
this.mutableCurrentBlock = undefined;
this.mutablePreviousBlock = undefined;
this.mutableWriteBlockchain = undefined;
this.mutableWriteBlockchain = this.createWriteBlockchain();
this.start();
await this.persistHeaders([this.settings.genesisBlock.header]);
await this.persistBlock({ block: this.settings.genesisBlock });
}
public readonly getValidators = async (
transactions: ReadonlyArray<Transaction>,
monitor?: Monitor,
): Promise<ReadonlyArray<ECPoint>> =>
this.getMonitor(monitor).captureSpanLog(async () => getValidators(this, transactions), {
name: 'neo_blockchain_get_validators',
level: { log: 'verbose', span: 'info' },
});
public readonly calculateClaimAmount = async (claims: ReadonlyArray<Input>, monitor?: Monitor): Promise<BN> =>
this.getMonitor(monitor).captureSpanLog(
async () => {
const spentCoins = await Promise.all(claims.map(async (claim) => this.tryGetSpentCoin(claim)));
const filteredSpentCoins = spentCoins.filter(commonUtils.notNull);
if (spentCoins.length !== filteredSpentCoins.length) {
throw new CoinUnspentError();
}
if (filteredSpentCoins.some((coin) => coin.claimed)) {
throw new CoinClaimedError();
}
if (
filteredSpentCoins.some((coin) => !common.uInt256Equal(coin.output.asset, this.settings.governingToken.hash))
) {
throw new InvalidClaimError();
}
return utils.calculateClaimAmount({
coins: filteredSpentCoins.map((coin) => ({
value: coin.output.value,
startHeight: coin.startHeight,
endHeight: coin.endHeight,
})),
decrementInterval: this.settings.decrementInterval,
generationAmount: this.settings.generationAmount,
getSystemFee: async (index) => {
const header = await this.header.get({
hashOrIndex: index,
});
const blockData = await this.blockData.get({
hash: header.hash,
});
return blockData.systemFee;
},
});
},
{
name: 'neo_blockchain_calculate_claim_amount',
level: { log: 'verbose', span: 'info' },
},
);
private async persistBlocksAsync(): Promise<void> {
if (this.mutablePersistingBlocks || !this.mutableRunning) {
return;
}
this.mutablePersistingBlocks = true;
let entry: Entry | undefined;
try {
this.cleanBlockQueue();
entry = this.peekBlockQueue();
const isForkedBlock =
entry !== undefined &&
entry.block.index === this.currentBlockIndex &&
this.mutableCurrentBlock !== undefined &&
!common.uInt256Equal(entry.block.hash, this.mutableCurrentBlock.hash);
// tslint:disable-next-line no-loop-statement
while (
this.mutableRunning &&
entry !== undefined &&
((entry.block.index === this.currentBlockIndex + 1 &&
(this.mutableCurrentBlock === undefined ||
common.uInt256Equal(entry.block.previousHash, this.mutableCurrentBlock.hash))) ||
isForkedBlock)
) {
entry = this.mutableBlockQueue.dequeue();
const entryNonNull = entry;
if (isForkedBlock) {
[this.mutableCurrentBlock, this.mutablePreviousBlock] = await Promise.all([
this.block.tryGet({ hashOrIndex: entryNonNull.block.index - 1 }),
this.block.tryGet({ hashOrIndex: entryNonNull.block.index - 2 }),
]);
this.mutableCurrentHeader =
this.mutableCurrentBlock === undefined ? undefined : this.mutableCurrentBlock.header;
this.mutableWriteBlockchain = undefined;
this.mutableWriteBlockchain = this.createWriteBlockchain();
}
await entry.monitor
.withData({ [labels.NEO_BLOCK_INDEX]: entry.block.index })
.captureSpanLog(async (span) => this.persistBlockInternal(span, entryNonNull.block, entryNonNull.unsafe), {
name: 'neo_blockchain_persist_block_top_level',
level: { log: 'verbose', span: 'info' },
metric: {
total: NEO_BLOCKCHAIN_PERSIST_BLOCK_DURATION_SECONDS,
error: NEO_BLOCKCHAIN_PERSIST_BLOCK_FAILURES_TOTAL,
},
trace: true,
});
entry.resolve();
this.mutableBlock$.next(entry.block);
NEO_BLOCKCHAIN_BLOCK_INDEX_GAUGE.set(entry.block.index);
NEO_BLOCKCHAIN_PERSIST_BLOCK_LATENCY_SECONDS.observe(this.monitor.nowSeconds() - entry.block.timestamp);
this.cleanBlockQueue();
entry = this.peekBlockQueue();
}
} catch (error) {
if (entry !== undefined) {
entry.reject(error);
}
} finally {
this.mutablePersistingBlocks = false;
if (this.mutableDoneRunningResolve !== undefined) {
this.mutableDoneRunningResolve();
this.mutableDoneRunningResolve = undefined;
}
}
}
private cleanBlockQueue(): void {
let entry = this.peekBlockQueue();
// tslint:disable-next-line no-loop-statement
while (entry !== undefined && entry.block.index <= this.currentBlockIndex) {
if (
entry.block.index !== this.currentBlockIndex ||
this.mutableCurrentBlock === undefined ||
common.uInt256Equal(entry.block.hash, this.mutableCurrentBlock.hash)
) {
this.mutableBlockQueue.dequeue();
entry.resolve();
entry = this.peekBlockQueue();
}
}
}
private peekBlockQueue(): Entry | undefined {
if (this.mutableBlockQueue.length > 0) {
return this.mutableBlockQueue.peek();
}
return undefined;
}
private readonly verifyScript = async (
{ scriptContainer, hash, witness }: VerifyScriptOptions,
monitor: Monitor,
): Promise<void> => {
let { verification } = witness;
if (verification.length === 0) {
const builder = new ScriptBuilder();
builder.emitAppCallVerification(hash);
verification = builder.build();
} else if (!common.uInt160Equal(hash, crypto.toScriptHash(verification))) {
throw new WitnessVerifyError();
}
const blockchain = this.createWriteBlockchain();
const result = await this.vm.executeScripts({
monitor: this.getMonitor(monitor),
scripts: [{ code: witness.invocation, pushOnly: true }, { code: verification }],
blockchain,
scriptContainer,
triggerType: TriggerType.Verification,
action: NULL_ACTION,
gas: utils.ZERO,
});
const { stack } = result;
if (stack.length !== 1) {
throw new ScriptVerifyError(
`Verification did not return one result. This may be a bug in the ` +
`smart contract. Found ${stack.length} results.`,
);
}
const top = stack[0];
if (!top.asBoolean()) {
throw new ScriptVerifyError('Verification did not succeed.');
}
};
private readonly tryGetInvocationData = async (
transaction: InvocationTransaction,
): Promise<SerializableInvocationData | undefined> => {
const data = await this.invocationData.tryGet({
hash: transaction.hash,
});
if (data === undefined) {
return undefined;
}
const [asset, contracts, actions] = await Promise.all([
data.assetHash === undefined ? Promise.resolve(undefined) : this.asset.get({ hash: data.assetHash }),
Promise.all(data.contractHashes.map(async (contractHash) => this.contract.tryGet({ hash: contractHash }))),
data.actionIndexStart.eq(data.actionIndexStop)
? Promise.resolve([])
: this.action
.getAll$({
indexStart: data.actionIndexStart,
indexStop: data.actionIndexStop.sub(utils.ONE),
})
.pipe(toArray())
.toPromise(),
]);
return {
asset,
contracts: contracts.filter(commonUtils.notNull),
deletedContractHashes: data.deletedContractHashes,
migratedContractHashes: data.migratedContractHashes,
voteUpdates: data.voteUpdates,
result: data.result,
actions,
};
};
private readonly tryGetTransactionData = async (transaction: TransactionBase): Promise<TransactionData | undefined> =>
this.transactionData.tryGet({ hash: transaction.hash });
private readonly getUnclaimed = async (hash: UInt160): Promise<ReadonlyArray<Input>> =>
this.accountUnclaimed
.getAll$({ hash })
.pipe(toArray())
.toPromise()
.then((values) => values.map((value) => value.input));
private readonly getUnspent = async (hash: UInt160): Promise<ReadonlyArray<Input>> => {
const unspent = await this.accountUnspent
.getAll$({ hash })
.pipe(toArray())
.toPromise();
return unspent.map((value) => value.input);
};
private readonly getAllValidators = async (): Promise<ReadonlyArray<Validator>> =>
this.validator.all$.pipe(toArray()).toPromise();
private readonly isSpent = async (input: OutputKey): Promise<boolean> => {
const transactionData = await this.transactionData.tryGet({
hash: input.hash,
});
return (
transactionData !== undefined && (transactionData.endHeights[input.index] as number | undefined) !== undefined
);
};
private readonly tryGetSpentCoin = async (input: Input): Promise<SpentCoin | undefined> => {
const [transactionData, output] = await Promise.all([
this.transactionData.tryGet({ hash: input.hash }),
this.output.get(input),
]);
if (transactionData === undefined) {
return undefined;
}
const endHeight = transactionData.endHeights[input.index] as number | undefined;
if (endHeight === undefined) {
return undefined;
}
const claimed = transactionData.claimed[input.index];
return {
output,
startHeight: transactionData.startHeight,
endHeight,
claimed: !!claimed,
};
};
private start(): void {
this.mutableBlock$ = new Subject();
this.mutablePersistingBlocks = false;
this.mutableBlockQueue = new PriorityQueue({
comparator: (a, b) => a.block.index - b.block.index,
});
this.mutableInQueue = new Set();
this.mutableDoneRunningResolve = undefined;
this.mutableRunning = true;
this.monitor.log({ name: 'neo_blockchain_start' });
}
// private readonly getVotes = async (transactions: ReadonlyArray<Transaction>): Promise<ReadonlyArray<Vote>> => {
// const inputs = await Promise.all(
// transactions.map(async (transaction) =>
// transaction.getReferences({
// getOutput: this.output.get,
// }),
// ),
// ).then((results) =>
// results.reduce((acc, inputResults) => acc.concat(inputResults), []).map((output) => ({
// address: output.address,
// asset: output.asset,
// value: output.value.neg(),
// })),
// );
// const outputs = transactions
// .reduce<ReadonlyArray<Output>>((acc, transaction) => acc.concat(transaction.outputs), [])
// .map((output) => ({
// address: output.address,
// asset: output.asset,
// value: output.value,
// }));
// const changes = _.fromPairs(
// Object.entries(
// _.groupBy(
// inputs
// .concat(outputs)
// .filter((output) => common.uInt256Equal(output.asset, this.settings.governingToken.hash)),
// (output) => common.uInt160ToHex(output.address),
// ),
// ).map(([addressHex, addressOutputs]) => [
// addressHex,
// addressOutputs.reduce((acc, output) => acc.add(output.value), utils.ZERO),
// ]),
// );
// const votes = await this.account.all$
// .pipe(
// filter((account) => account.votes.length > 0),
// map((account) => {
// let balance = account.balances[this.settings.governingToken.hashHex];
// balance = balance === undefined ? utils.ZERO : balance;
// const change = changes[account.hashHex];
// balance = balance.add(change === undefined ? utils.ZERO : change);
// return balance.lte(utils.ZERO)
// ? undefined
// : {
// publicKeys: account.votes,
// count: balance,
// };
// }),
// toArray(),
// )
// .toPromise();
// if (votes.length === 0) {
// return [
// {
// publicKeys: this.settings.standbyValidators,
// count: this.settings.governingToken.asset.amount,
// },
// ];
// }
// return votes.filter(commonUtils.notNull);
// };
private async persistBlockInternal(monitor: Monitor, block: Block, unsafe?: boolean): Promise<void> {
NEO_BLOCKCHAIN_PERSISTING_BLOCK_INDEX_GAUGE.set(block.index);
if (!unsafe) {
await this.verifyBlock(block, monitor);
}
const blockchain = this.createWriteBlockchain();
await blockchain.persistBlock(monitor, block);
const writeBlockchain = this.mutableWriteBlockchain;
if (writeBlockchain === undefined) {
throw new Error('Something went wrong');
}
await monitor.captureSpan(async () => this.storage.commit(writeBlockchain.getChangeSet()), {
name: 'neo_blockchain_persist_block_commit_storage',
});
blockchain.setStorage(this.storage);
this.mutableWriteBlockchain = blockchain;
this.mutablePreviousBlock = this.mutableCurrentBlock;
this.mutableCurrentBlock = block;
this.mutableCurrentHeader = block.header;
}
private createWriteBlockchain(): WriteBatchBlockchain {
return new WriteBatchBlockchain({
settings: this.settings,
currentBlock: this.mutableCurrentBlock,
currentHeader: this.mutableCurrentHeader,
storage: this.mutableWriteBlockchain === undefined ? this.storage : this.mutableWriteBlockchain,
vm: this.vm,
getValidators: this.getValidators,
});
}
private getMonitor(monitor?: Monitor): Monitor {
if (monitor === undefined) {
return this.monitor;
}
return monitor.at(NAMESPACE);
}
}