UNPKG

neo-blockchain-impl

Version:
751 lines (667 loc) 20.8 kB
/* @flow */ import { Observable } from 'rxjs'; import { type Block, type Output, type OutputKey, type UInt256, common, utils, } from 'neo-blockchain-core'; import { type ChangeSet, type AddChange, type DeleteChange, type ReadStorage, type ReadAllStorage, type ReadGetAllStorage, } from 'neo-blockchain-node-core'; type TrackedChange<Key, AddValue, Value> = | {| type: 'add', addValue: AddValue, value: Value |} | {| type: 'delete', key: Key |}; type GetFunc<Key, Value> = (key: Key) => Promise<Value>; type TryGetFunc<Key, Value> = (key: Key) => Promise<?Value>; function createGet<Key, Value>({ tryGetTracked, readStorage, }: {| tryGetTracked: (key: Key) => ?TrackedChange<Key, *, Value>, readStorage: ReadStorage<Key, Value>, |}): GetFunc<Key, Value> { return async (key: Key): Promise<Value> => { const trackedChange = tryGetTracked(key); if (trackedChange != null) { // TODO: Better error if (trackedChange.type === 'delete') { throw new Error('Not found'); } return trackedChange.value; } const value = await readStorage.get(key); return value; }; } function createTryGet<Key, Value>({ tryGetTracked, readStorage, }: {| tryGetTracked: (key: Key) => ?TrackedChange<Key, *, Value>, readStorage: ReadStorage<Key, Value>, |}): TryGetFunc<Key, Value> { return async (key: Key): Promise<?Value> => { const trackedChange = tryGetTracked(key); if (trackedChange != null) { if (trackedChange.type === 'delete') { return null; } return trackedChange.value; } const value = await readStorage.tryGet(key); return value; }; } type BaseReadStorageCacheOptions<Key, AddValue, Value> = {| readStorage: ReadStorage<Key, Value>, name: string, createAddChange: (value: AddValue) => AddChange, createDeleteChange?: (key: Key) => DeleteChange, onAdd?: (value: AddValue) => Promise<void>, |}; class BaseReadStorageCache<Key, AddValue, Value> { _readStorage: ReadStorage<Key, Value>; _name: string; _createAddChange: (value: AddValue) => AddChange; _createDeleteChange: ?(key: Key) => DeleteChange; _onAdd: ?(value: AddValue) => Promise<void>; _values: { [key: string]: TrackedChange<Key, AddValue, Value> }; get: GetFunc<Key, Value>; tryGet: TryGetFunc<Key, Value>; constructor(options: BaseReadStorageCacheOptions<Key, AddValue, Value>) { this._readStorage = options.readStorage; this._name = options.name; this._createAddChange = options.createAddChange; this._createDeleteChange = options.createDeleteChange; this._onAdd = options.onAdd; this._values = {}; this.get = createGet({ readStorage: this._readStorage, tryGetTracked: this._tryGetTracked.bind(this), }); this.tryGet = createTryGet({ readStorage: this._readStorage, tryGetTracked: this._tryGetTracked.bind(this), }); } getChangeSet(): ChangeSet { const createDeleteChange = this._createDeleteChange; return utils.values(this._values).map(value => { if (value.type === 'delete') { if (createDeleteChange == null) { // TODO: Make better throw new Error('Invalid delete'); } return { type: 'delete', change: createDeleteChange(value.key) }; } return { type: 'add', change: this._createAddChange(value.addValue) }; }); } // eslint-disable-next-line _tryGetTracked(key: Key): ?TrackedChange<Key, AddValue, Value> { throw new Error('Not Implemented'); } } type ReadStorageCacheOptions<Key, AddValue, Value> = {| ...BaseReadStorageCacheOptions<Key, AddValue, Value>, getKeyString: (key: Key) => string, |}; class ReadStorageCache<Key, AddValue, Value> extends BaseReadStorageCache< Key, AddValue, Value, > { _getKeyString: (key: Key) => string; constructor(options: ReadStorageCacheOptions<Key, AddValue, Value>) { super({ readStorage: options.readStorage, name: options.name, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, }); this._getKeyString = options.getKeyString; } _tryGetTracked(key: Key): ?TrackedChange<Key, AddValue, Value> { return this._values[this._getKeyString(key)]; } } type ReadAllStorageCacheOptions<Key, Value> = {| readAllStorage: ReadAllStorage<Key, Value>, name: string, createAddChange: (value: Value) => AddChange, createDeleteChange?: (key: Key) => DeleteChange, onAdd?: (value: Value) => Promise<void>, getKeyString: (key: Key) => string, getKeyFromValue: (value: Value) => Key, |}; class ReadAllStorageCache<Key, Value> extends ReadStorageCache< Key, Value, Value, > { _readAllStorage: ReadAllStorage<Key, Value>; _getKeyFromValue: (value: Value) => Key; all: Observable<Value>; constructor(options: ReadAllStorageCacheOptions<Key, Value>) { super({ readStorage: { get: options.readAllStorage.get, tryGet: options.readAllStorage.tryGet, }, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, }); this._readAllStorage = options.readAllStorage; this._getKeyFromValue = options.getKeyFromValue; this.all = Observable.concat( Observable.defer(() => Observable.of( ...utils .values(this._values) .map(value => (value.type === 'add' ? value.value : null)) .filter(Boolean), ), ), this._readAllStorage.all.concatMap(value => { const trackedChange = this._tryGetTracked(this._getKeyFromValue(value)); if (trackedChange != null && trackedChange.type === 'delete') { return Observable.of(); } return Observable.of(value); }), ); } } type ReadGetAllStorageCacheOptions<Key, PartialKey, Value> = {| readGetAllStorage: ReadGetAllStorage<Key, PartialKey, Value>, name: string, createAddChange: (value: Value) => AddChange, createDeleteChange?: (key: Key) => DeleteChange, onAdd?: (value: Value) => Promise<void>, getKeyString: (key: Key) => string, getKeyFromValue: (value: Value) => Key, matchesPartialKey: (value: Value, key: PartialKey) => boolean, |}; class ReadGetAllStorageCache<Key, PartialKey, Value> extends ReadStorageCache< Key, Value, Value, > { _readGetAllStorage: ReadGetAllStorage<Key, PartialKey, Value>; _getKeyFromValue: (value: Value) => Key; _matchesPartialKey: (value: Value, key: PartialKey) => boolean; getAll: (key: PartialKey) => Observable<Value>; constructor(options: ReadGetAllStorageCacheOptions<Key, PartialKey, Value>) { super({ readStorage: { get: options.readGetAllStorage.get, tryGet: options.readGetAllStorage.tryGet, }, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, }); this._readGetAllStorage = options.readGetAllStorage; this._getKeyFromValue = options.getKeyFromValue; this._matchesPartialKey = options.matchesPartialKey; this.getAll = (key: PartialKey): Observable<Value> => { const createdValues = utils .values(this._values) .map( value => value.type === 'add' && this._matchesPartialKey(value.value, key) ? value.value : null, ) .filter(Boolean); return Observable.concat( Observable.of(...createdValues), this._readGetAllStorage.getAll(key).concatMap(value => { const trackedChange = this._tryGetTracked( this._getKeyFromValue(value), ); if (trackedChange != null && trackedChange.type === 'delete') { return Observable.of(); } return Observable.of(value); }), ); }; } } type AddFunc<Value> = (value: Value, force?: boolean) => Promise<void>; function createAdd<Key, Value>({ cache, getKeyFromValue, getKeyString, }: {| cache: ReadStorageCache<Key, Value, Value>, getKeyFromValue: (value: Value) => Key, getKeyString: (key: Key) => string, |}): AddFunc<Value> { return async (value: Value, force?: boolean): Promise<void> => { const key = getKeyFromValue(value); if (!force) { const currentValue = await cache.tryGet(key); if (currentValue != null) { // TODO: Better error throw new Error( `Attempted to add an already existing object for key ` + `${cache._name}:${getKeyString(key)}.`, ); } } if (cache._onAdd != null) { await cache._onAdd(value); } // eslint-disable-next-line cache._values[cache._getKeyString(key)] = { type: 'add', addValue: value, value, }; }; } type UpdateFunc<Value, Update> = ( value: Value, update: Update, ) => Promise<Value>; function createUpdate<Key, Value, Update>({ cache, update: updateFunc, getKeyFromValue, }: {| cache: ReadStorageCache<Key, Value, Value>, update: (value: Value, update: Update) => Value, getKeyFromValue: (value: Value) => Key, |}): UpdateFunc<Value, Update> { return async (value: Value, update: Update): Promise<Value> => { const key = getKeyFromValue(value); const updatedValue = updateFunc(value, update); // eslint-disable-next-line cache._values[cache._getKeyString(key)] = { type: 'add', addValue: updatedValue, value: updatedValue, }; return updatedValue; }; } type DeleteFunc<Key> = (key: Key) => Promise<void>; function createDelete<Key>({ cache, }: {| cache: ReadStorageCache<Key, *, *>, |}): DeleteFunc<Key> { return async (key: Key): Promise<void> => { // eslint-disable-next-line cache._values[cache._getKeyString(key)] = { type: 'delete', key }; }; } type ReadAddUpdateDeleteStorageCacheOptions<Key, Value, Update> = {| ...ReadStorageCacheOptions<Key, Value, Value>, update: (value: Value, update: Update) => Value, getKeyFromValue: (value: Value) => Key, |}; export class ReadAddUpdateDeleteStorageCache< Key, Value, Update, > extends ReadStorageCache<Key, Value, Value> { add: AddFunc<Value>; update: UpdateFunc<Value, Update>; delete: DeleteFunc<Key>; constructor( options: ReadAddUpdateDeleteStorageCacheOptions<Key, Value, Update>, ) { super({ readStorage: options.readStorage, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, }); this.add = createAdd({ cache: this, getKeyFromValue: options.getKeyFromValue, getKeyString: options.getKeyString, }); this.update = createUpdate({ cache: this, update: options.update, getKeyFromValue: options.getKeyFromValue, }); this.delete = createDelete({ cache: this }); } } type ReadAddUpdateStorageCacheOptions<Key, Value, Update> = {| ...ReadStorageCacheOptions<Key, Value, Value>, update: (value: Value, update: Update) => Value, getKeyFromValue: (value: Value) => Key, |}; export class ReadAddUpdateStorageCache< Key, Value, Update, > extends ReadStorageCache<Key, Value, Value> { add: AddFunc<Value>; update: UpdateFunc<Value, Update>; constructor(options: ReadAddUpdateStorageCacheOptions<Key, Value, Update>) { super({ readStorage: options.readStorage, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, }); this.add = createAdd({ cache: this, getKeyFromValue: options.getKeyFromValue, getKeyString: options.getKeyString, }); this.update = createUpdate({ cache: this, update: options.update, getKeyFromValue: options.getKeyFromValue, }); } } type ReadAddDeleteStorageCacheOptions<Key, Value> = {| ...ReadStorageCacheOptions<Key, Value, Value>, getKeyFromValue: (value: Value) => Key, |}; export class ReadAddDeleteStorageCache<Key, Value> extends ReadStorageCache< Key, Value, Value, > { add: AddFunc<Value>; delete: DeleteFunc<Key>; constructor(options: ReadAddDeleteStorageCacheOptions<Key, Value>) { super({ readStorage: options.readStorage, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, }); this.add = createAdd({ cache: this, getKeyFromValue: options.getKeyFromValue, getKeyString: options.getKeyString, }); this.delete = createDelete({ cache: this }); } } type ReadAddStorageCacheOptions<Key, Value> = {| ...ReadStorageCacheOptions<Key, Value, Value>, getKeyFromValue: (value: Value) => Key, |}; export class ReadAddStorageCache<Key, Value> extends ReadStorageCache< Key, Value, Value, > { add: AddFunc<Value>; constructor(options: ReadAddStorageCacheOptions<Key, Value>) { super({ readStorage: options.readStorage, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, }); this.add = createAdd({ cache: this, getKeyFromValue: options.getKeyFromValue, getKeyString: options.getKeyString, }); } } type ReadGetAllAddUpdateDeleteStorageCacheOptions< Key, PartialKey, Value, Update, > = {| ...ReadGetAllStorageCacheOptions<Key, PartialKey, Value>, update: (value: Value, update: Update) => Value, getKeyFromValue: (value: Value) => Key, |}; export class ReadGetAllAddUpdateDeleteStorageCache< Key, PartialKey, Value, Update, > extends ReadGetAllStorageCache<Key, PartialKey, Value> { add: AddFunc<Value>; update: UpdateFunc<Value, Update>; delete: DeleteFunc<Key>; constructor( options: ReadGetAllAddUpdateDeleteStorageCacheOptions< Key, PartialKey, Value, Update, >, ) { super({ readGetAllStorage: options.readGetAllStorage, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, getKeyFromValue: options.getKeyFromValue, matchesPartialKey: options.matchesPartialKey, }); this.add = createAdd({ cache: this, getKeyFromValue: options.getKeyFromValue, getKeyString: options.getKeyString, }); this.update = createUpdate({ cache: this, update: options.update, getKeyFromValue: options.getKeyFromValue, }); this.delete = createDelete({ cache: this }); } } type ReadGetAllAddStorageCacheOptions<Key, PartialKey, Value> = {| ...ReadGetAllStorageCacheOptions<Key, PartialKey, Value>, getKeyFromValue: (value: Value) => Key, |}; export class ReadGetAllAddStorageCache< Key, PartialKey, Value, > extends ReadGetAllStorageCache<Key, PartialKey, Value> { add: AddFunc<Value>; constructor( options: ReadGetAllAddStorageCacheOptions<Key, PartialKey, Value>, ) { super({ readGetAllStorage: options.readGetAllStorage, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, getKeyFromValue: options.getKeyFromValue, matchesPartialKey: options.matchesPartialKey, }); this.add = createAdd({ cache: this, getKeyFromValue: options.getKeyFromValue, getKeyString: options.getKeyString, }); } } type ReadAllAddUpdateDeleteStorageCacheOptions<Key, Value, Update> = {| ...ReadAllStorageCacheOptions<Key, Value>, update: (value: Value, update: Update) => Value, getKeyFromValue: (value: Value) => Key, |}; export class ReadAllAddUpdateDeleteStorageCache< Key, Value, Update, > extends ReadAllStorageCache<Key, Value> { add: AddFunc<Value>; update: UpdateFunc<Value, Update>; delete: DeleteFunc<Key>; constructor( options: ReadAllAddUpdateDeleteStorageCacheOptions<Key, Value, Update>, ) { super({ readAllStorage: options.readAllStorage, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, getKeyFromValue: options.getKeyFromValue, }); this.add = createAdd({ cache: this, getKeyFromValue: options.getKeyFromValue, getKeyString: options.getKeyString, }); this.update = createUpdate({ cache: this, update: options.update, getKeyFromValue: options.getKeyFromValue, }); this.delete = createDelete({ cache: this }); } } type ReadAllAddStorageCacheOptions<Key, Value> = {| ...ReadAllStorageCacheOptions<Key, Value>, getKeyFromValue: (value: Value) => Key, |}; export class ReadAllAddStorageCache<Key, Value> extends ReadAllStorageCache< Key, Value, > { add: AddFunc<Value>; constructor(options: ReadAllAddStorageCacheOptions<Key, Value>) { super({ readAllStorage: options.readAllStorage, name: options.name, getKeyString: options.getKeyString, createAddChange: options.createAddChange, createDeleteChange: options.createDeleteChange, onAdd: options.onAdd, getKeyFromValue: options.getKeyFromValue, }); this.add = createAdd({ cache: this, getKeyFromValue: options.getKeyFromValue, getKeyString: options.getKeyString, }); } } type BlockLikeKey = {| hashOrIndex: $PropertyType<Block, 'hash'> | $PropertyType<Block, 'index'>, |}; type BlockLike = { +hash: $PropertyType<Block, 'hash'>, +index: $PropertyType<Block, 'index'>, }; type BlockLikeStorageCacheOptions<Value: BlockLike> = {| ...BaseReadStorageCacheOptions<BlockLikeKey, Value, Value>, |}; export class BlockLikeStorageCache< Value: BlockLike, > extends BaseReadStorageCache<BlockLikeKey, Value, Value> { _create: (value: Value) => Value; _indexValues: { [index: string]: TrackedChange<BlockLikeKey, Value, Value> }; constructor(options: BlockLikeStorageCacheOptions<Value>) { super({ readStorage: options.readStorage, name: options.name, createAddChange: options.createAddChange, }); this._indexValues = {}; } async add(value: Value, force?: boolean): Promise<void> { if (!force) { const currentValue = await this.tryGet({ hashOrIndex: value.index }); if (currentValue != null) { // TODO: Better error throw new Error('Attempted to add an already existing object.'); } } const addValue = { type: 'add', addValue: value, value }; this._values[common.uInt256ToString(value.hash)] = addValue; this._indexValues[`${value.index}`] = addValue; } _tryGetTracked( key: BlockLikeKey, ): ?TrackedChange<BlockLikeKey, Value, Value> { if (typeof key.hashOrIndex !== 'number') { return this._values[common.uInt256ToString(key.hashOrIndex)]; } return this._indexValues[`${key.hashOrIndex}`]; } } type OutputValue = {| hash: UInt256, index: number, output: Output, |}; const getOutputValueKeyString = (key: OutputKey): string => `${common.uInt256ToHex(key.hash)}:${key.index}`; export class OutputStorageCache extends ReadStorageCache< OutputKey, OutputValue, Output, > { add: AddFunc<OutputValue>; constructor(readStorage: ReadStorage<OutputKey, Output>) { super({ readStorage, name: 'output', getKeyString: getOutputValueKeyString, createAddChange: (value: OutputValue) => ({ type: 'output', value }), }); this.add = async (value: OutputValue, force?: boolean): Promise<void> => { const key = { hash: value.hash, index: value.index }; if (!force) { const currentValue = await this.tryGet(key); if (currentValue != null) { // TODO: Better error throw new Error( `Attempted to add an already existing object for key ` + `${this._name}:${this._getKeyString(key)}.`, ); } } // eslint-disable-next-line this._values[this._getKeyString(key)] = { type: 'add', addValue: value, value: value.output, }; }; } }