diff --git a/packages/neo-one-build-tools/types/unit.d.ts b/packages/neo-one-build-tools/types/unit.d.ts new file mode 100644 index 0000000000..27254b7bba --- /dev/null +++ b/packages/neo-one-build-tools/types/unit.d.ts @@ -0,0 +1,6 @@ +declare interface One { + readonly addCleanup: (callback: () => Promise | void) => void; + readonly cleanupTest: () => Promise; + readonly teardown: () => Promise; +} +declare const one: One; diff --git a/packages/neo-one-client-common/src/models/types.ts b/packages/neo-one-client-common/src/models/types.ts index 33009db8cc..4d751a5af8 100644 --- a/packages/neo-one-client-common/src/models/types.ts +++ b/packages/neo-one-client-common/src/models/types.ts @@ -457,6 +457,7 @@ export interface TrimmedBlockJSON extends BlockBaseJSON { } export interface NetworkSettingsJSON { + readonly blockcount: number; readonly decrementinterval: number; readonly generationamount: readonly number[]; readonly privatekeyversion: number; diff --git a/packages/neo-one-client-common/src/types.ts b/packages/neo-one-client-common/src/types.ts index 2bc19ac154..0693bc9b73 100644 --- a/packages/neo-one-client-common/src/types.ts +++ b/packages/neo-one-client-common/src/types.ts @@ -2437,6 +2437,7 @@ export type ParamJSON = * Constant settings used to initialize the client APIs. */ export interface NetworkSettings { + readonly blockCount: number; readonly decrementInterval: number; readonly generationAmount: readonly number[]; readonly privateKeyVersion: number; diff --git a/packages/neo-one-client-core/src/Client.ts b/packages/neo-one-client-core/src/Client.ts index 65e9e82c0e..d83b65228f 100644 --- a/packages/neo-one-client-core/src/Client.ts +++ b/packages/neo-one-client-core/src/Client.ts @@ -645,12 +645,12 @@ export class Client< ): Promise { try { args.assertString('network', network); - args.assertAddress('contract', contract); + const contractHash = args.tryGetUInt160Hex('contract', contract); args.assertString('method', method); args .assertArray('params', params) .forEach((param) => args.assertNullableScriptBuilderParam('params.param', param)); - const receipt = await this.getNetworkProvider(network).call(network, contract, method, params); + const receipt = await this.getNetworkProvider(network).call(network, contractHash, method, params); await this.hooks.afterCall.promise(receipt); return receipt; diff --git a/packages/neo-one-client-core/src/__tests__/user/LocalUserAccountProvider.test.ts b/packages/neo-one-client-core/src/__tests__/user/LocalUserAccountProvider.test.ts index 2427ad19e9..02e5949361 100644 --- a/packages/neo-one-client-core/src/__tests__/user/LocalUserAccountProvider.test.ts +++ b/packages/neo-one-client-core/src/__tests__/user/LocalUserAccountProvider.test.ts @@ -1,7 +1,7 @@ -import { common, crypto, ScriptBuilder } from '@neo-one/client-common'; +import { common, crypto } from '@neo-one/client-common'; import { constants } from '@neo-one/utils'; import BigNumber from 'bignumber.js'; -import { NEOONEProvider, NEOONEDataProvider } from '../../provider'; +import { NEOONEProvider } from '../../provider'; import { LocalKeyStore, LocalMemoryStore, LocalUserAccountProvider } from '../../user'; const secondaryKeyString = '04c1784140445016cf0f8cc86dd10ad8764e1a89c563c500e21ac19a5d905ab3'; @@ -138,25 +138,71 @@ describe.skip('Test LocalUserAccountProvider transfer methods -- staging network expect(stackReturn.value).toEqual(true); }); + + test('Claim Gas -- forming our own transaction', async () => { + const keystore = new LocalKeyStore(new LocalMemoryStore()); + const account = await keystore.addUserAccount(masterAccount); + + const provider = new NEOONEProvider([providerOptions]); + const localProvider = new LocalUserAccountProvider({ provider, keystore }); + + const unclaimedInit = await provider.getUnclaimed('test', account.userAccount.id.address); + + const transfer = { + amount: new BigNumber(0), + asset: common.nativeScriptHashes.NEO, + to: account.userAccount.id.address, + }; + + const result = await localProvider.transfer([transfer], { + maxNetworkFee: new BigNumber(-1), + maxSystemFee: new BigNumber(-1), + }); + + await result.confirmed(); + + const unclaimedPost = await provider.getUnclaimed('test', account.userAccount.id.address); + expect(unclaimedPost.toNumber()).toBeLessThan(unclaimedInit.toNumber()); + }); + + test('Claim Gas -- using LUAP method', async () => { + const keystore = new LocalKeyStore(new LocalMemoryStore()); + const account = await keystore.addUserAccount(masterAccount); + + const provider = new NEOONEProvider([providerOptions]); + const localProvider = new LocalUserAccountProvider({ provider, keystore }); + + const unclaimedInit = await provider.getUnclaimed('test', account.userAccount.id.address); + + const result = await localProvider.claim({ + from: account.userAccount.id, + maxNetworkFee: new BigNumber(-1), + maxSystemFee: new BigNumber(-1), + }); + + await result.confirmed(); + + const unclaimedPost = await provider.getUnclaimed('test', account.userAccount.id.address); + expect(unclaimedPost.toNumber()).toBeLessThan(unclaimedInit.toNumber()); + }); }); -describe('contract info / usage testing', () => { +describe.skip('contract info / usage testing', () => { const knownContractHashString = '0x79597a92440ce385fe1b0f4d9d2a92ca8608a575'; - const knownContractHash = common.stringToUInt160(knownContractHashString); const providerOptions = { network: 'test', - rpcURL: 'http://localhost:8081/rpc', + rpcURL: 'https://staging.neotracker.io/rpc', }; const provider = new NEOONEProvider([providerOptions]); - test('use `call` to get name of NEO contract', async () => { - const result = await provider.call('test', common.nativeScriptHashes.NEO, 'name', []); + test('use `call` to get name of a non-native contract', async () => { + const result = await provider.call('test', knownContractHashString, 'name', []); const value = result.stack[0]; if (typeof value === 'string') { throw new Error(value); } - expect(value.value).toEqual('NEO'); + expect(value.value).toEqual('S3 Plus'); }); }); diff --git a/packages/neo-one-client-core/src/args.ts b/packages/neo-one-client-core/src/args.ts index 8c8d3cd1a5..d5288b2750 100644 --- a/packages/neo-one-client-core/src/args.ts +++ b/packages/neo-one-client-core/src/args.ts @@ -112,6 +112,22 @@ export const assertAddress = (name: string, addressIn?: unknown): AddressString } }; +export const tryGetUInt160Hex = (name: string, addressOrUInt160In: unknown): UInt160Hex => { + const addressOrUInt160 = assertString(name, addressOrUInt160In); + + try { + scriptHashToAddress(addressOrUInt160); + + return addressOrUInt160; + } catch { + try { + return addressToScriptHash(addressOrUInt160); + } catch { + throw new InvalidArgumentError('AddressOrUInt160', name, addressOrUInt160); + } + } +}; + export const assertHash256 = (name: string, hash?: unknown): Hash256String => { const value = assertString(name, hash); @@ -798,7 +814,6 @@ export const assertContractManifest = (name: string, value?: unknown): ContractM } return { - hash: assertProperty(value, 'ContractManifest', 'hash', assertUInt160Hex), groups: assertProperty(value, 'ContractManifest', 'groups', assertArray).map((group) => assertContractGroup('ContractManifest.groups', group), ), @@ -950,7 +965,7 @@ export const assertTransfer = (name: string, value?: unknown): Transfer => { return { amount: assertProperty(value, 'Transfer', 'amount', assertBigNumber), - asset: assertProperty(value, 'Transfer', 'asset', assertAddress), + asset: assertProperty(value, 'Transfer', 'asset', tryGetUInt160Hex), to: assertProperty(value, 'Transfer', 'to', assertAddress), }; }; @@ -1009,8 +1024,8 @@ export const assertTransactionOptions = (name: string, options?: unknown): Trans attributes: assertProperty(options, 'TransactionOptions', 'attributes', assertNullableArray).map((value) => assertAttribute('TransactionOption.attributes', value), ), - networkFee: assertProperty(options, 'TransactionOptions', 'networkFee', assertNullableBigNumber), - systemFee: assertProperty(options, 'TransactionOptions', 'systemFee', assertNullableBigNumber), + maxNetworkFee: assertProperty(options, 'TransactionOptions', 'networkFee', assertNullableBigNumber), + maxSystemFee: assertProperty(options, 'TransactionOptions', 'systemFee', assertNullableBigNumber), }; }; @@ -1034,13 +1049,13 @@ export const assertInvokeSendUnsafeReceiveTransactionOptions = ( 'attributes', assertNullableArray, ).map((value) => assertAttribute('TransactionOption.attributes', value)), - networkFee: assertProperty( + maxNetworkFee: assertProperty( options, 'InvokeSendUnsafeReceiveTransactionOptions', 'networkFee', assertNullableBigNumber, ), - systemFee: assertProperty( + maxSystemFee: assertProperty( options, 'InvokeSendUnsafeReceiveTransactionOptions', 'systemFee', diff --git a/packages/neo-one-client-core/src/provider/NEOONEDataProvider.ts b/packages/neo-one-client-core/src/provider/NEOONEDataProvider.ts index 88424500e1..350b66f225 100644 --- a/packages/neo-one-client-core/src/provider/NEOONEDataProvider.ts +++ b/packages/neo-one-client-core/src/provider/NEOONEDataProvider.ts @@ -481,6 +481,7 @@ export class NEOONEDataProvider implements DeveloperProvider { private convertNetworkSettings(settings: NetworkSettingsJSON): NetworkSettings { return { + blockCount: settings.blockcount, decrementInterval: settings.decrementinterval, generationAmount: settings.generationamount, privateKeyVersion: settings.privatekeyversion, diff --git a/packages/neo-one-client-core/src/provider/NEOONEProvider.ts b/packages/neo-one-client-core/src/provider/NEOONEProvider.ts index bfeb50b0f9..b2bd704da6 100644 --- a/packages/neo-one-client-core/src/provider/NEOONEProvider.ts +++ b/packages/neo-one-client-core/src/provider/NEOONEProvider.ts @@ -15,6 +15,7 @@ import { Transaction, TransactionModel, TransactionReceipt, + UInt160Hex, } from '@neo-one/client-common'; import BigNumber from 'bignumber.js'; import { BehaviorSubject, Observable } from 'rxjs'; @@ -86,7 +87,7 @@ export class NEOONEProvider implements Provider { public async getVerificationCost( network: NetworkType, - hash: AddressString, + hash: UInt160Hex, transaction: TransactionModel, ): Promise<{ readonly fee: BigNumber; @@ -101,7 +102,7 @@ export class NEOONEProvider implements Provider { public async call( network: NetworkType, - contract: AddressString, + contract: UInt160Hex, method: string, params: ReadonlyArray, ): Promise { diff --git a/packages/neo-one-client-core/src/user/LocalUserAccountProvider.ts b/packages/neo-one-client-core/src/user/LocalUserAccountProvider.ts index 4a83a7e078..495cfd1938 100644 --- a/packages/neo-one-client-core/src/user/LocalUserAccountProvider.ts +++ b/packages/neo-one-client-core/src/user/LocalUserAccountProvider.ts @@ -403,10 +403,7 @@ export class LocalUserAccountProvider { sb.emitAppCall( @@ -426,7 +423,7 @@ export class LocalUserAccountProvider { - const toAccount = this.getUserAccount(from); - const unclaimedAmount = await this.provider.getUnclaimed(from.network, from.address); - const toHash = crypto.toScriptHash(crypto.createSignatureRedeemScript(common.stringToECPoint(toAccount.publicKey))); - const script = new ScriptBuilder() - .emitAppCall(common.nativeHashes.GAS, 'transfer', [ - addressToScriptHash(from.address), - common.nativeHashes.GAS, - unclaimedAmount.toString(), - ]) - .build(); - - const [{ gas }, { count, messageMagic }] = await Promise.all([ - this.getSystemFee({ from, attributes, script }), - this.getCountAndMagic(from), - ]); + const transfer: Transfer = { + to: from.address, + asset: common.nativeScriptHashes.NEO, + amount: new BigNumber(0), + }; - const transaction = new TransactionModel({ - systemFee: utils.bigNumberToBN(gas, 8), // TODO: check this. check decimals - networkFee: utils.bigNumberToBN(networkFee, 8), // TODO: check decimals - script, - attributes: this.convertAttributes(attributes), - messageMagic, - validUntilBlock: count + 240, - }); - - return this.sendTransaction({ - from, - transaction, - onConfirm: async ({ receipt }) => receipt, - networkFee, - }); + return this.executeTransfer([transfer], from, attributes, maxNetworkFee, maxSystemFee, validBlockCount); } // TODO: see invokeScript and invokeFunction in neo-modules diff --git a/packages/neo-one-client-core/src/user/UserAccountProviderBase.ts b/packages/neo-one-client-core/src/user/UserAccountProviderBase.ts index 87d32481df..f2fe69dba3 100644 --- a/packages/neo-one-client-core/src/user/UserAccountProviderBase.ts +++ b/packages/neo-one-client-core/src/user/UserAccountProviderBase.ts @@ -6,7 +6,6 @@ import { AttributeModel, Block, common, - FeelessTransactionModel, ForwardValue, GetOptions, Hash256String, @@ -21,8 +20,6 @@ import { RawInvocationData, RawInvokeReceipt, ScriptBuilderParam, - Signer, - SignerModel, SourceMaps, Transaction, TransactionModel, @@ -30,16 +27,15 @@ import { TransactionReceipt, TransactionResult, Transfer, - UInt160, UserAccount, UserAccountID, utils, Witness, WitnessModel, + UInt160Hex, } from '@neo-one/client-common'; import { Labels, utils as commonUtils } from '@neo-one/utils'; import BigNumber from 'bignumber.js'; -import { BN } from 'bn.js'; import debug from 'debug'; import { Observable } from 'rxjs'; import { clientUtils } from '../clientUtils'; @@ -124,7 +120,7 @@ export interface Provider { readonly testTransaction: (network: NetworkType, transaction: TransactionModel) => Promise; readonly call: ( network: NetworkType, - contract: AddressString, + contract: UInt160Hex, method: string, params: ReadonlyArray, ) => Promise; @@ -136,7 +132,7 @@ export interface Provider { readonly getAccount: (network: NetworkType, address: AddressString) => Promise; readonly getVerificationCost: ( network: NetworkType, - hash: AddressString, + hash: UInt160Hex, transaction: TransactionModel, ) => Promise<{ readonly fee: BigNumber; @@ -208,7 +204,7 @@ export abstract class UserAccountProviderBase { public async claim(options?: TransactionOptions): Promise { const { from, attributes, maxNetworkFee, maxSystemFee, validBlockCount } = this.getTransactionOptions(options); - return this.capture(async () => this.executeClaim(from, attributes, networkFee), { + return this.capture(async () => this.executeClaim(from, attributes, maxNetworkFee, maxSystemFee, validBlockCount), { name: 'neo_claim', }); } @@ -658,7 +654,9 @@ export abstract class UserAccountProviderBase { protected abstract async executeClaim( from: UserAccountID, attributes: readonly Attribute[], - networkFee: BigNumber, + maxNetworkFee: BigNumber, + maxSystemFee: BigNumber, + validBlockCount: number, ): Promise; protected async invokeRaw({ @@ -725,17 +723,6 @@ export abstract class UserAccountProviderBase { ); } - protected async getCountAndMagic( - network: NetworkType, - ): Promise<{ readonly count: number; readonly messageMagic: number }> { - const [count, { messageMagic }] = await Promise.all([ - this.provider.getBlockCount(network), - this.provider.getNetworkSettings(network), - ]); - - return { count, messageMagic }; - } - private getScriptAndInvokeMethodOptions( invokeMethodOptionsOrScript: InvokeMethodOptions | Buffer, ): { readonly script: Buffer; readonly invokeMethodOptions: InvokeMethodOptions | undefined } { diff --git a/packages/neo-one-node-blockchain/src/Blockchain.ts b/packages/neo-one-node-blockchain/src/Blockchain.ts index 92a8d54b98..c0cba7b63e 100644 --- a/packages/neo-one-node-blockchain/src/Blockchain.ts +++ b/packages/neo-one-node-blockchain/src/Blockchain.ts @@ -61,6 +61,7 @@ export interface CreateBlockchainOptions { readonly storage: Storage; readonly native: NativeContainer; readonly vm: VM; + readonly onPersist?: () => void | Promise; } export interface BlockchainOptions extends CreateBlockchainOptions { @@ -119,7 +120,13 @@ export const recoverHeaderIndex = async (storage: Storage) => { }); }; export class Blockchain { - public static async create({ settings, storage, native, vm }: CreateBlockchainOptions): Promise { + public static async create({ + settings, + storage, + native, + vm, + onPersist, + }: CreateBlockchainOptions): Promise { const headerIndexCache = await recoverHeaderIndex(storage); if (headerIndexCache.length > 0) { const currentHeaderHash = await headerIndexCache.get(headerIndexCache.length - 1); @@ -137,6 +144,7 @@ export class Blockchain { native, vm, currentBlock, + onPersist, }); } @@ -146,6 +154,7 @@ export class Blockchain { storage, native, vm, + onPersist, }); await blockchain.persistBlock({ block: settings.genesisBlock }); @@ -164,6 +173,7 @@ export class Blockchain { private readonly storage: Storage; private readonly native: NativeContainer; private readonly vm: VM; + private readonly onPersist: () => void | Promise; private mutableBlockQueue: PriorityQueue = new PriorityQueue({ comparator: (a, b) => a.block.index - b.block.index, @@ -190,6 +200,7 @@ export class Blockchain { validatorsCount: this.settings.validatorsCount, }; this.mutableCurrentBlock = options.currentBlock; + this.onPersist = options.onPersist === undefined ? this.vm.updateSnapshots : options.onPersist; this.start(); } @@ -288,9 +299,9 @@ export class Blockchain { return this.storage.contractID; } - public get consensusState() { - return this.storage.consensusState; - } + // public get consensusState() { + // return this.storage.consensusState; + // } public get serializeJSONContext() { return { @@ -784,7 +795,7 @@ export class Blockchain { const applicationLogUpdates = this.updateApplicationLogs({ applicationsExecuted, block }); await this.storage.commit(applicationLogUpdates); - this.vm.updateSnapshots(); + await this.onPersist(); } private createPersistingBlockchain(): PersistingBlockchain { diff --git a/packages/neo-one-node-core/src/Storage.ts b/packages/neo-one-node-core/src/Storage.ts index 859a8fd005..2cefae8ca9 100644 --- a/packages/neo-one-node-core/src/Storage.ts +++ b/packages/neo-one-node-core/src/Storage.ts @@ -132,7 +132,7 @@ export interface BlockchainStorage { readonly nep5TransfersSent: ReadFindStorage; readonly nep5TransfersReceived: ReadFindStorage; readonly applicationLogs: ReadStorage; - readonly consensusState: ReadMetadataStorage; + // readonly consensusState: ReadMetadataStorage; readonly transactions: ReadStorage; readonly contracts: ReadStorage; readonly storages: ReadFindStorage; diff --git a/packages/neo-one-node-core/src/vm.ts b/packages/neo-one-node-core/src/vm.ts index 17b83962e0..1ed80d7449 100644 --- a/packages/neo-one-node-core/src/vm.ts +++ b/packages/neo-one-node-core/src/vm.ts @@ -91,4 +91,5 @@ export interface VM { readonly withSnapshots: ( func: (snapshots: { readonly main: SnapshotHandler; readonly clone: Omit }) => T, ) => T; + readonly updateStore: (storage: ReadonlyArray<{ key: Buffer; value: Buffer }>) => void; } diff --git a/packages/neo-one-node-rpc-handler/src/createHandler.ts b/packages/neo-one-node-rpc-handler/src/createHandler.ts index 4856f66009..35324bebd5 100644 --- a/packages/neo-one-node-rpc-handler/src/createHandler.ts +++ b/packages/neo-one-node-rpc-handler/src/createHandler.ts @@ -867,6 +867,7 @@ export const createHandler = ({ } = blockchain.settings; return { + blockcount: blockchain.currentBlockIndex + 1, decrementinterval: decrementInterval, generationamount: generationAmount, privatekeyversion: privateKeyVersion, diff --git a/packages/neo-one-node-storage-levelup/src/index.ts b/packages/neo-one-node-storage-levelup/src/index.ts index 5e17c56b41..bf80327334 100644 --- a/packages/neo-one-node-storage-levelup/src/index.ts +++ b/packages/neo-one-node-storage-levelup/src/index.ts @@ -1,2 +1,3 @@ // tslint:disable-next-line export-name export { levelUpStorage as storage } from './levelUpStorage'; +export * from './streamToObservable'; diff --git a/packages/neo-one-node-storage-levelup/src/levelUpStorage.ts b/packages/neo-one-node-storage-levelup/src/levelUpStorage.ts index 48fa3755a1..3d14a4f9a0 100644 --- a/packages/neo-one-node-storage-levelup/src/levelUpStorage.ts +++ b/packages/neo-one-node-storage-levelup/src/levelUpStorage.ts @@ -1,9 +1,7 @@ -import { common } from '@neo-one/client-common'; import { ApplicationLog, BinaryReader, BlockKey, - ConsensusContext, ContractIDState, ContractState, DeserializeWireContext, @@ -195,11 +193,11 @@ export const levelUpStorage = ({ db, context }: LevelUpStorageOptions): Storage deserializeValue: (buffer) => ContractIDState.deserializeWire({ context, buffer }), }), - consensusState: read.createReadMetadataStorage({ - db, - key: keys.consensusStateKey, - deserializeValue: (buffer) => ConsensusContext.deserializeWire({ context, buffer }), - }), + // consensusState: read.createReadMetadataStorage({ + // db, + // key: keys.consensusStateKey, + // deserializeValue: (buffer) => ConsensusContext.deserializeWire({ context, buffer }), + // }), async close(): Promise { await db.close(); diff --git a/packages/neo-one-node-vm/lib/Dispatcher.Snapshot.cs b/packages/neo-one-node-vm/lib/Dispatcher.Snapshot.cs index 8fe0efb52c..77c9d5f0ff 100644 --- a/packages/neo-one-node-vm/lib/Dispatcher.Snapshot.cs +++ b/packages/neo-one-node-vm/lib/Dispatcher.Snapshot.cs @@ -1,7 +1,6 @@ using System; using System.IO; using Neo; -using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -10,209 +9,209 @@ namespace NEOONE { - partial class Dispatcher + partial class Dispatcher + { + + private StoreView snapshot; + private StoreView clonedSnapshot; + + enum SnapshotMethod + { + snapshot_blocks_add, + snapshot_transactions_add, + snapshot_clone, + snapshot_commit, + snapshot_reset, + snapshot_change_block_hash_index, + snapshot_change_header_hash_index, + snapshot_set_persisting_block, + snapshot_has_persisting_block, + snapshot_get_change_set, + snapshot_transactions_delete, + } + + private dynamic dispatchSnapshotMethod(SnapshotMethod method, dynamic args) { + switch (method) + { + case SnapshotMethod.snapshot_blocks_add: + UInt256 blockHash = new UInt256((byte[])args.hash); + byte[] blockSerialized = (byte[])args.block; + Block block = new Block(); + using (MemoryStream ms = new MemoryStream(blockSerialized)) + using (BinaryReader reader = new BinaryReader(ms)) + { + block.Deserialize(reader); + } + + var tmp = block.Hash.ToString(); + + return this._snapshotBlocksAdd(this.selectSnapshot(args.snapshot), blockHash, block); + + case SnapshotMethod.snapshot_transactions_add: + uint blockIndex = (uint)args.index; + byte[] txSerialized = (byte[])args.tx; + VMState vmState = (VMState)args.state; + Transaction tx = new Transaction(); + using (MemoryStream ms = new MemoryStream(txSerialized)) + using (BinaryReader reader = new BinaryReader(ms)) + { + + ((IVerifiable)tx).Deserialize(reader); + } + + return this._snapshotTransactionsAdd(this.selectSnapshot(args.snapshot), tx, blockIndex, vmState); + + case SnapshotMethod.snapshot_transactions_delete: + UInt256 transactionHash = new UInt256((byte[])args.hash); + + return this._snapshotTransactionsDelete(this.selectSnapshot(args.snapshot), transactionHash); + + case SnapshotMethod.snapshot_clone: + return this._snapshotClone(); + + case SnapshotMethod.snapshot_change_block_hash_index: + uint bIndex = (uint)args.index; + UInt256 blockChangeHash = new UInt256((byte[])args.hash); + + return this._snapshotChangeBlockHashIndex(this.selectSnapshot(args.snapshot), bIndex, blockChangeHash); + + case SnapshotMethod.snapshot_change_header_hash_index: + uint hIndex = (uint)args.index; + UInt256 headerChangeHash = new UInt256((byte[])args.hash); - private StoreView snapshot; - private StoreView clonedSnapshot; + return this._snapshotChangeHeaderHashIndex(this.selectSnapshot(args.snapshot), hIndex, headerChangeHash); - enum SnapshotMethod - { - snapshot_blocks_add, - snapshot_transactions_add, - snapshot_clone, - snapshot_commit, - snapshot_reset, - snapshot_change_block_hash_index, - snapshot_change_header_hash_index, - snapshot_set_persisting_block, - snapshot_has_persisting_block, - snapshot_get_change_set, - snapshot_transactions_delete, - } + case SnapshotMethod.snapshot_get_change_set: + return this._snapshotGetChangeSet(this.selectSnapshot(args.snapshot, true)); + + + case SnapshotMethod.snapshot_commit: + this._snapshotCommit( + this.selectSnapshot(args.snapshot), + args.partial + ); + + return true; + + case SnapshotMethod.snapshot_set_persisting_block: + byte[] persistingBlockSerialized = (byte[])args.block; + Block persisting = new Block(); + using (MemoryStream ms = new MemoryStream(persistingBlockSerialized)) + using (BinaryReader reader = new BinaryReader(ms)) + { + persisting.Deserialize(reader); + } + + this.selectSnapshot(args.snapshot).PersistingBlock = persisting; + + return true; + + case SnapshotMethod.snapshot_has_persisting_block: + return this.selectSnapshot(args.snapshot).PersistingBlock != null; + + + case SnapshotMethod.snapshot_reset: + this.resetSnapshots(); + + return true; + + default: + throw new InvalidOperationException(); + } + } + + + private void resetSnapshots() + { + this.store?.Dispose(); + this.store = null; + this.store = this.path != null ? new RocksDBStore(this.path).GetStore() : new MemoryStore(); + this.snapshot = new SnapshotView(this.store); + this.clonedSnapshot = this.snapshot.Clone(); + } + + private StoreView selectSnapshot(string snapshot, bool required = true) + { + switch (snapshot) + { + case "main": + return this.snapshot; + case "clone": + return this.clonedSnapshot; + default: + if (required) throw new InvalidOperationException("Must specify snapshot for this method"); + return null; + } + } + + private void _snapshotCommit(StoreView snapshot, string partial) + { + switch (partial) + { + case "blocks": + snapshot.Blocks.Commit(); + break; + case "transactions": + snapshot.Transactions.Commit(); + break; + default: + snapshot.Commit(); + break; + } + } + + private bool _snapshotBlocksAdd(StoreView snapshot, UInt256 hash, Block block) + { + snapshot.Blocks.Add(hash, block.Trim()); + + return true; + } + + private bool _snapshotTransactionsAdd(StoreView snapshot, Transaction tx, uint index, VMState vmState = VMState.BREAK) + { + var state = new TransactionState { BlockIndex = index, Transaction = tx, VMState = vmState }; + snapshot.Transactions.Add(tx.Hash, state); + + return true; + } + + private bool _snapshotTransactionsDelete(StoreView snapshot, UInt256 hash) + { + snapshot.Transactions.Delete(hash); + + return true; + } + + private bool _snapshotChangeBlockHashIndex(StoreView snapshot, uint index, UInt256 hash) + { + HashIndexState hashIndex = snapshot.BlockHashIndex.GetAndChange(); + hashIndex.Index = index; + hashIndex.Hash = hash; + + return true; + } + + private bool _snapshotChangeHeaderHashIndex(StoreView snapshot, uint index, UInt256 hash) + { + HashIndexState hashIndex = snapshot.HeaderHashIndex.GetAndChange(); + hashIndex.Index = index; + hashIndex.Hash = hash; + + return true; + } + + private dynamic _snapshotGetChangeSet(StoreView snapshot) + { + return new ChangeSet(snapshot).set.ToArray(); + } + + private bool _snapshotClone() + { + this.clonedSnapshot = this.snapshot.Clone(); - private dynamic dispatchSnapshotMethod(SnapshotMethod method, dynamic args) - { - switch (method) - { - case SnapshotMethod.snapshot_blocks_add: - UInt256 blockHash = new UInt256((byte[])args.hash); - byte[] blockSerialized = (byte[])args.block; - Block block = new Block(); - using (MemoryStream ms = new MemoryStream(blockSerialized)) - using (BinaryReader reader = new BinaryReader(ms)) - { - block.Deserialize(reader); - } - - var tmp = block.Hash.ToString(); - - return this._snapshotBlocksAdd(this.selectSnapshot(args.snapshot), blockHash, block); - - case SnapshotMethod.snapshot_transactions_add: - uint blockIndex = (uint)args.index; - byte[] txSerialized = (byte[])args.tx; - VMState vmState = (VMState)args.state; - Transaction tx = new Transaction(); - using (MemoryStream ms = new MemoryStream(txSerialized)) - using (BinaryReader reader = new BinaryReader(ms)) - { - - ((IVerifiable)tx).Deserialize(reader); - } - - return this._snapshotTransactionsAdd(this.selectSnapshot(args.snapshot), tx, blockIndex, vmState); - - case SnapshotMethod.snapshot_transactions_delete: - UInt256 transactionHash = new UInt256((byte[])args.hash); - - return this._snapshotTransactionsDelete(this.selectSnapshot(args.snapshot), transactionHash); - - case SnapshotMethod.snapshot_clone: - return this._snapshotClone(); - - case SnapshotMethod.snapshot_change_block_hash_index: - uint bIndex = (uint)args.index; - UInt256 blockChangeHash = new UInt256((byte[])args.hash); - - return this._snapshotChangeBlockHashIndex(this.selectSnapshot(args.snapshot), bIndex, blockChangeHash); - - case SnapshotMethod.snapshot_change_header_hash_index: - uint hIndex = (uint)args.index; - UInt256 headerChangeHash = new UInt256((byte[])args.hash); - - return this._snapshotChangeHeaderHashIndex(this.selectSnapshot(args.snapshot), hIndex, headerChangeHash); - - case SnapshotMethod.snapshot_get_change_set: - return this._snapshotGetChangeSet(this.selectSnapshot(args.snapshot, true)); - - - case SnapshotMethod.snapshot_commit: - this._snapshotCommit( - this.selectSnapshot(args.snapshot), - args.partial - ); - - return true; - - case SnapshotMethod.snapshot_set_persisting_block: - byte[] persistingBlockSerialized = (byte[])args.block; - Block persisting = new Block(); - using (MemoryStream ms = new MemoryStream(persistingBlockSerialized)) - using (BinaryReader reader = new BinaryReader(ms)) - { - persisting.Deserialize(reader); - } - - this.selectSnapshot(args.snapshot).PersistingBlock = persisting; - - return true; - - case SnapshotMethod.snapshot_has_persisting_block: - return this.selectSnapshot(args.snapshot).PersistingBlock != null; - - - case SnapshotMethod.snapshot_reset: - this.resetSnapshots(); - - return true; - - default: - throw new InvalidOperationException(); - } - } - - - private void resetSnapshots() - { - this.store?.Dispose(); - this.store = null; - this.store = this.path != null ? new RocksDBStore(this.path).GetStore() : new MemoryStore(); - this.snapshot = new SnapshotView(this.store); - this.clonedSnapshot = this.snapshot.Clone(); - } - - private StoreView selectSnapshot(string snapshot, bool required = true) - { - switch (snapshot) - { - case "main": - return this.snapshot; - case "clone": - return this.clonedSnapshot; - default: - if (required) throw new InvalidOperationException("Must specify snapshot for this method"); - return null; - } - } - - private void _snapshotCommit(StoreView snapshot, string partial) - { - switch (partial) - { - case "blocks": - snapshot.Blocks.Commit(); - break; - case "transactions": - snapshot.Transactions.Commit(); - break; - default: - snapshot.Commit(); - break; - } - } - - private bool _snapshotBlocksAdd(StoreView snapshot, UInt256 hash, Block block) - { - snapshot.Blocks.Add(hash, block.Trim()); - - return true; - } - - private bool _snapshotTransactionsAdd(StoreView snapshot, Transaction tx, uint index, VMState vmState = VMState.BREAK) - { - var state = new TransactionState { BlockIndex = index, Transaction = tx, VMState = vmState }; - snapshot.Transactions.Add(tx.Hash, state); - - return true; - } - - private bool _snapshotTransactionsDelete(StoreView snapshot, UInt256 hash) - { - snapshot.Transactions.Delete(hash); - - return true; - } - - private bool _snapshotChangeBlockHashIndex(StoreView snapshot, uint index, UInt256 hash) - { - HashIndexState hashIndex = snapshot.BlockHashIndex.GetAndChange(); - hashIndex.Index = index; - hashIndex.Hash = hash; - - return true; - } - - private bool _snapshotChangeHeaderHashIndex(StoreView snapshot, uint index, UInt256 hash) - { - HashIndexState hashIndex = snapshot.HeaderHashIndex.GetAndChange(); - hashIndex.Index = index; - hashIndex.Hash = hash; - - return true; - } - - private dynamic _snapshotGetChangeSet(StoreView snapshot) - { - return new ChangeSet(snapshot).set.ToArray(); - } - - private bool _snapshotClone() - { - this.clonedSnapshot = this.snapshot.Clone(); - - return true; - } + return true; } + } } diff --git a/packages/neo-one-node-vm/lib/Dispatcher.TestHelper.cs b/packages/neo-one-node-vm/lib/Dispatcher.TestHelper.cs new file mode 100644 index 0000000000..46a88422b3 --- /dev/null +++ b/packages/neo-one-node-vm/lib/Dispatcher.TestHelper.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Neo.Persistence; + +// It is worth noting this helper method shouldn't be used outside of testing purposes. +namespace NEOONE +{ + partial class Dispatcher + { + enum TestMethod + { + test_update_store + } + + public class RawChange + { + public byte table { get; set; } + public byte[] key { get; set; } + public byte[] value { get; set; } + } + private dynamic dispatchTestMethod(TestMethod method, dynamic args) + { + switch (method) + { + case TestMethod.test_update_store: + List changes = new List { }; + foreach (dynamic change in args.changes) + { + changes.Add(new RawChange() { table = (byte)change.table, key = (byte[])change.key, value = (byte[])change.value }); + } + return this._updateStore(changes.ToArray()); + default: + throw new InvalidOperationException(); + } + } + + private bool _updateStore(RawChange[] changes) + { + if (this.path != null) + { + throw new InvalidOperationException("Must use a memory store for this operation to be useful"); + } + + this.store?.Dispose(); + this.store = null; + this.store = new MemoryStore(); + foreach (RawChange change in changes) + { + this.store.PutSync(change.table, change.key, change.value); + } + this.snapshot = new SnapshotView(this.store); + this.clonedSnapshot = this.snapshot.Clone(); + + return true; + } + } +} diff --git a/packages/neo-one-node-vm/lib/Dispatcher.cs b/packages/neo-one-node-vm/lib/Dispatcher.cs index bbd9513fff..1c8b40de6d 100644 --- a/packages/neo-one-node-vm/lib/Dispatcher.cs +++ b/packages/neo-one-node-vm/lib/Dispatcher.cs @@ -8,219 +8,225 @@ namespace NEOONE { - public partial class Dispatcher + public partial class Dispatcher + { + + private ApplicationEngine engine; + private IStore store; + private bool init = false; + private string path; + private enum BaseMethod { + init, + dispose, + test, + get_config, + } - private ApplicationEngine engine; - private IStore store; - private bool init = false; - private string path; - private enum BaseMethod - { - init, - dispose, - test, - get_config, - } - - private dynamic dispatchBaseMethod(BaseMethod method, dynamic args) - { - switch (method) - { - case BaseMethod.init: - if (args.path == null && args.settings == null) - { - return this._init(); - } - if (args.path == null && args.settings != null) - { - return this._init(parseConfig(args.settings)); - } - if (args.path != null && args.settings == null) - { - return this._init((string)args.path); - } - - return this._init((string)args.path, parseConfig(args.settings)); - - case BaseMethod.dispose: - return this._dispose(); - - case BaseMethod.get_config: - return this._getConfig(); - - case BaseMethod.test: - return this._test(); - - default: - throw new InvalidOperationException(); - - } - } + private dynamic dispatchBaseMethod(BaseMethod method, dynamic args) + { + switch (method) + { + case BaseMethod.init: + if (args.path == null && args.settings == null) + { + return this._init(); + } + if (args.path == null && args.settings != null) + { + return this._init(parseConfig(args.settings)); + } + if (args.path != null && args.settings == null) + { + return this._init((string)args.path); + } + + return this._init((string)args.path, parseConfig(args.settings)); + + case BaseMethod.dispose: + return this._dispose(); + + case BaseMethod.get_config: + return this._getConfig(); + + case BaseMethod.test: + return this._test(); + + default: + throw new InvalidOperationException(); + + } + } - private bool _init(string path, IConfiguration config) - { - if (!this.init) - { - Neo.ProtocolSettings.Initialize(config); - this.path = path; - this.resetSnapshots(); + private bool _init(string path, IConfiguration config) + { + if (!this.init) + { + Neo.ProtocolSettings.Initialize(config); + this.path = path; + this.resetSnapshots(); - this.init = true; - } + this.init = true; + } - return this.init; - } + return this.init; + } - private bool _init(IConfiguration config) - { - if (!this.init) - { - Neo.ProtocolSettings.Initialize(config); - this.resetSnapshots(); + private bool _init(IConfiguration config) + { + if (!this.init) + { + Neo.ProtocolSettings.Initialize(config); + this.resetSnapshots(); - this.init = true; - } + this.init = true; + } - return this.init; - } + return this.init; + } - private bool _init(string path) - { - if (!this.init) - { - this.path = path; - this.resetSnapshots(); + private bool _init(string path) + { + if (!this.init) + { + this.path = path; + this.resetSnapshots(); - this.init = true; - } + this.init = true; + } - return this.init; - } + return this.init; + } - private bool _init() - { - if (!this.init) - { - this.resetSnapshots(); + private bool _init() + { + if (!this.init) + { + this.resetSnapshots(); - this.init = true; - } + this.init = true; + } - return this.init; - } + return this.init; + } - private bool _dispose() - { - this.snapshot = null; - this.clonedSnapshot = null; - this.store.Dispose(); - this.store = null; - this.disposeEngine(); - this.init = false; - - return true; - } + private bool _dispose() + { + this.snapshot = null; + this.clonedSnapshot = null; + this.store.Dispose(); + this.store = null; + this.disposeEngine(); + this.init = false; + + return true; + } - private dynamic _test() - { - return ApplicationEngine.Neo_Crypto_CheckMultisigWithECDsaSecp256r1.Hash.ToString(); - } + private dynamic _test() + { + return ApplicationEngine.Neo_Crypto_CheckMultisigWithECDsaSecp256r1.Hash.ToString(); + } - private NEOONE.ReturnHelpers.ProtocolSettingsReturn _getConfig() - { - return new NEOONE.ReturnHelpers.ProtocolSettingsReturn(Neo.ProtocolSettings.Default); - } + private NEOONE.ReturnHelpers.ProtocolSettingsReturn _getConfig() + { + return new NEOONE.ReturnHelpers.ProtocolSettingsReturn(Neo.ProtocolSettings.Default); + } #pragma warning disable 1998 - public async Task Invoke(dynamic input) - { - string method = (string)input.method; - if (!this.init && method != "init") - { - throw new InvalidOperationException("Dispatcher must be initalized"); - } - - object args = hasArgs(input) ? input.args : null; - - BaseMethod baseMethod; - if (Enum.TryParse(method, out baseMethod)) - { - return this.dispatchBaseMethod(baseMethod, args); - } - - EngineMethod engineMethod; - if (Enum.TryParse(method, out engineMethod)) - { - return this.dispatchEngineMethod(engineMethod, args); - } - - SnapshotMethod snapshotMethod; - if (Enum.TryParse(method, out snapshotMethod)) - { - return this.dispatchSnapshotMethod(snapshotMethod, args); - } - - throw new ArgumentException($"{method} is not a valid dispatch method"); - } + public async Task Invoke(dynamic input) + { + string method = (string)input.method; + if (!this.init && method != "init") + { + throw new InvalidOperationException("Dispatcher must be initalized"); + } + + object args = hasArgs(input) ? input.args : null; + + BaseMethod baseMethod; + if (Enum.TryParse(method, out baseMethod)) + { + return this.dispatchBaseMethod(baseMethod, args); + } + + EngineMethod engineMethod; + if (Enum.TryParse(method, out engineMethod)) + { + return this.dispatchEngineMethod(engineMethod, args); + } + + SnapshotMethod snapshotMethod; + if (Enum.TryParse(method, out snapshotMethod)) + { + return this.dispatchSnapshotMethod(snapshotMethod, args); + } + + TestMethod testMethod; + if (Enum.TryParse(method, out testMethod)) + { + return this.dispatchTestMethod(testMethod, args); + } + + throw new ArgumentException($"{method} is not a valid dispatch method"); + } - private bool hasArgs(dynamic input) - { - Type inputType = input.GetType(); + private bool hasArgs(dynamic input) + { + Type inputType = input.GetType(); - if (inputType == typeof(ExpandoObject)) - { - return ((IDictionary)input).ContainsKey("args"); - } + if (inputType == typeof(ExpandoObject)) + { + return ((IDictionary)input).ContainsKey("args"); + } - return inputType.GetProperty("args") != null; - } + return inputType.GetProperty("args") != null; + } - private IConfiguration parseConfig(dynamic input) + private IConfiguration parseConfig(dynamic input) + { + Dictionary config = new Dictionary { }; + if (input.magic != null) + { + uint Magic = (uint)input.magic; + config.Add("ProtocolConfiguration:Magic", Magic.ToString()); + } + if (input.addressVersion != null) + { + byte AddressVersion = (byte)input.addressVersion; + config.Add("ProtocolConfiguration:AddressVersion", AddressVersion.ToString()); + } + if (input.standbyCommittee != null) + { + object[] StandbyCommittee = (object[])input.standbyCommittee; + int idx = 0; + foreach (object member in StandbyCommittee) { - Dictionary config = new Dictionary { }; - if (input.magic != null) - { - uint Magic = (uint)input.magic; - config.Add("ProtocolConfiguration:Magic", Magic.ToString()); - } - if (input.addressVersion != null) - { - byte AddressVersion = (byte)input.addressVersion; - config.Add("ProtocolConfiguration:AddressVersion", AddressVersion.ToString()); - } - if (input.standbyCommittee != null) - { - object[] StandbyCommittee = (object[])input.standbyCommittee; - int idx = 0; - foreach (object member in StandbyCommittee) - { - config.Add($"ProtocolConfiguration:StandbyCommittee:{idx}", member.ToString()); - idx++; - } - } - if (input.committeeMembersCount != null) - { - int CommitteeMembersCount = (int)input.committeeMembersCount; - config.Add("ProtocolConfiguration:CommitteeMembersCount", CommitteeMembersCount.ToString()); - } - if (input.validatorsCount != null) - { - int ValidatorsCount = (int)input.validatorsCount; - config.Add("ProtocolConfiguration:ValidatorsCount", ValidatorsCount.ToString()); - } - if (input.millisecondsPerBlock != null) - { - uint MillisecondsPerBlock = (uint)input.millisecondsPerBlock; - config.Add("ProtocolConfiguration:MillisecondsPerBlock", MillisecondsPerBlock.ToString()); - } - if (input.memoryPoolMaxTransactions != null) - { - int MemoryPoolMaxTransactions = (int)input.memoryPoolMaxTransactions; - config.Add("ProtocolConfiguration:MemoryPoolMaxTransactions", MemoryPoolMaxTransactions.ToString()); - } - - return new ConfigurationBuilder().AddInMemoryCollection(config).Build(); + config.Add($"ProtocolConfiguration:StandbyCommittee:{idx}", member.ToString()); + idx++; } + } + if (input.committeeMembersCount != null) + { + int CommitteeMembersCount = (int)input.committeeMembersCount; + config.Add("ProtocolConfiguration:CommitteeMembersCount", CommitteeMembersCount.ToString()); + } + if (input.validatorsCount != null) + { + int ValidatorsCount = (int)input.validatorsCount; + config.Add("ProtocolConfiguration:ValidatorsCount", ValidatorsCount.ToString()); + } + if (input.millisecondsPerBlock != null) + { + uint MillisecondsPerBlock = (uint)input.millisecondsPerBlock; + config.Add("ProtocolConfiguration:MillisecondsPerBlock", MillisecondsPerBlock.ToString()); + } + if (input.memoryPoolMaxTransactions != null) + { + int MemoryPoolMaxTransactions = (int)input.memoryPoolMaxTransactions; + config.Add("ProtocolConfiguration:MemoryPoolMaxTransactions", MemoryPoolMaxTransactions.ToString()); + } + + return new ConfigurationBuilder().AddInMemoryCollection(config).Build(); } + } } diff --git a/packages/neo-one-node-vm/lib/SnapshotHelpers.cs b/packages/neo-one-node-vm/lib/SnapshotHelpers.cs index c5e22cec22..79cfe7c76c 100644 --- a/packages/neo-one-node-vm/lib/SnapshotHelpers.cs +++ b/packages/neo-one-node-vm/lib/SnapshotHelpers.cs @@ -4,55 +4,55 @@ namespace NEOONE { - interface CommitStore + interface CommitStore + { + void Commit(); + } + + public class ChangeSet + { + public List set = new List(); + public ChangeSet(StoreView snapshot) { - void Commit(); + foreach (var value in snapshot.Blocks.GetChangeSet()) + { + set.Add(new Change("block", value)); + } + + foreach (var value in snapshot.Transactions.GetChangeSet()) + { + set.Add(new Change("transaction", value)); + } + + foreach (var value in snapshot.Contracts.GetChangeSet()) + { + set.Add(new Change("contract", value)); + } + foreach (var value in snapshot.Storages.GetChangeSet()) + { + set.Add(new Change("storage", value)); + } + foreach (var value in snapshot.HeaderHashList.GetChangeSet()) + { + set.Add(new Change("headerHashList", value)); + } } - - public class ChangeSet - { - public List set = new List(); - public ChangeSet(StoreView snapshot) - { - foreach (var value in snapshot.Blocks.GetChangeSet()) - { - set.Add(new Change("block", value)); - } - - foreach (var value in snapshot.Transactions.GetChangeSet()) - { - set.Add(new Change("transaction", value)); - } - - foreach (var value in snapshot.Contracts.GetChangeSet()) - { - set.Add(new Change("contract", value)); - } - foreach (var value in snapshot.Storages.GetChangeSet()) - { - set.Add(new Change("storage", value)); - } - foreach (var value in snapshot.HeaderHashList.GetChangeSet()) - { - set.Add(new Change("headerHashList", value)); - } - } - } - - public class Change + } + + public class Change + { + public string type; + public string itemType; + public byte[] key; + public byte[] value; + public Change(string itemType, dynamic change) { - public string type; - public string itemType; - public byte[] key; - public byte[] value; - public Change(string itemType, dynamic change) - { - this.type = change.State.ToString(); - this.key = ((ISerializable)change.Key).ToArray(); - this.value = ((ISerializable)change.Item).ToArray(); - this.itemType = itemType; - } + this.type = change.State.ToString(); + this.key = ((ISerializable)change.Key).ToArray(); + this.value = ((ISerializable)change.Item).ToArray(); + this.itemType = itemType; } + } } diff --git a/packages/neo-one-node-vm/lib/Storage/RocksDB/Options.cs b/packages/neo-one-node-vm/lib/Storage/RocksDB/Options.cs index af079a460c..f155db653a 100644 --- a/packages/neo-one-node-vm/lib/Storage/RocksDB/Options.cs +++ b/packages/neo-one-node-vm/lib/Storage/RocksDB/Options.cs @@ -2,24 +2,24 @@ namespace NEOONE.Storage.RocksDB { - public static class Options - { - public static readonly DbOptions Default = CreateDbOptions(); - public static readonly ReadOptions ReadDefault = new ReadOptions(); - public static readonly WriteOptions WriteDefault = new WriteOptions(); - public static readonly WriteOptions WriteDefaultSync = new WriteOptions().SetSync(true); + public static class Options + { + public static readonly DbOptions Default = CreateDbOptions(); + public static readonly ReadOptions ReadDefault = new ReadOptions(); + public static readonly WriteOptions WriteDefault = new WriteOptions(); + public static readonly WriteOptions WriteDefaultSync = new WriteOptions().SetSync(true); - public static DbOptions CreateDbOptions() - { - DbOptions options = new DbOptions(); - options.SetCreateMissingColumnFamilies(true); - options.SetCreateIfMissing(true); - options.SetErrorIfExists(false); - options.SetMaxOpenFiles(1000); - options.SetParanoidChecks(false); - options.SetWriteBufferSize(4 << 20); - options.SetBlockBasedTableFactory(new BlockBasedTableOptions().SetBlockSize(4096)); - return options; - } + public static DbOptions CreateDbOptions() + { + DbOptions options = new DbOptions(); + options.SetCreateMissingColumnFamilies(true); + options.SetCreateIfMissing(true); + options.SetErrorIfExists(false); + options.SetMaxOpenFiles(1000); + options.SetParanoidChecks(false); + options.SetWriteBufferSize(4 << 20); + options.SetBlockBasedTableFactory(new BlockBasedTableOptions().SetBlockSize(4096)); + return options; } + } } diff --git a/packages/neo-one-node-vm/lib/Storage/RocksDB/RocksDBStore.cs b/packages/neo-one-node-vm/lib/Storage/RocksDB/RocksDBStore.cs index 115645b310..0edca66d1f 100644 --- a/packages/neo-one-node-vm/lib/Storage/RocksDB/RocksDBStore.cs +++ b/packages/neo-one-node-vm/lib/Storage/RocksDB/RocksDBStore.cs @@ -3,19 +3,19 @@ namespace NEOONE.Storage { - public class RocksDBStore - { - private string path; + public class RocksDBStore + { + private string path; - public RocksDBStore(string path) - { - this.path = path; - } + public RocksDBStore(string path) + { + this.path = path; + } - public IStore GetStore() - { - return new Store(path); - } + public IStore GetStore() + { + return new Store(path); } + } } diff --git a/packages/neo-one-node-vm/lib/Storage/RocksDB/Settings.cs b/packages/neo-one-node-vm/lib/Storage/RocksDB/Settings.cs index 0d5c7e3607..e904e3cc2a 100644 --- a/packages/neo-one-node-vm/lib/Storage/RocksDB/Settings.cs +++ b/packages/neo-one-node-vm/lib/Storage/RocksDB/Settings.cs @@ -1,12 +1,12 @@ namespace NEOONE.Storage.RocksDB { - class Settings - { - public string Path { get; } + class Settings + { + public string Path { get; } - public Settings(string path) - { - this.Path = path; - } + public Settings(string path) + { + this.Path = path; } + } } diff --git a/packages/neo-one-node-vm/lib/Storage/RocksDB/Snapshot.cs b/packages/neo-one-node-vm/lib/Storage/RocksDB/Snapshot.cs index bb5c50821a..007ca64de5 100644 --- a/packages/neo-one-node-vm/lib/Storage/RocksDB/Snapshot.cs +++ b/packages/neo-one-node-vm/lib/Storage/RocksDB/Snapshot.cs @@ -6,70 +6,70 @@ namespace NEOONE.Storage.RocksDB { - internal class Snapshot : ISnapshot - { - private readonly Store store; - private readonly RocksDb db; - private readonly RocksDbSharp.Snapshot snapshot; - private readonly ReadOptions options; + internal class Snapshot : ISnapshot + { + private readonly Store store; + private readonly RocksDb db; + private readonly RocksDbSharp.Snapshot snapshot; + private readonly ReadOptions options; - public Snapshot(Store store, RocksDb db) - { - this.store = store; - this.db = db; - this.snapshot = db.CreateSnapshot(); + public Snapshot(Store store, RocksDb db) + { + this.store = store; + this.db = db; + this.snapshot = db.CreateSnapshot(); - options = new ReadOptions(); - options.SetFillCache(false); - options.SetSnapshot(snapshot); - } + options = new ReadOptions(); + options.SetFillCache(false); + options.SetSnapshot(snapshot); + } - public void Commit() - { - throw new InvalidOperationException(); - } + public void Commit() + { + throw new InvalidOperationException(); + } - public void Delete(byte table, byte[] key) - { - throw new InvalidOperationException(); - } + public void Delete(byte table, byte[] key) + { + throw new InvalidOperationException(); + } - public void Put(byte table, byte[] key, byte[] value) - { - throw new InvalidOperationException(); - } + public void Put(byte table, byte[] key, byte[] value) + { + throw new InvalidOperationException(); + } - public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte table, byte[] keyOrPrefix, SeekDirection direction) - { - if (keyOrPrefix == null) keyOrPrefix = Array.Empty(); + public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte table, byte[] keyOrPrefix, SeekDirection direction) + { + if (keyOrPrefix == null) keyOrPrefix = Array.Empty(); - using var it = db.NewIterator(store.defaultFamily, options); - byte[] fullKey = store.getFullKey(table, keyOrPrefix); + using var it = db.NewIterator(store.defaultFamily, options); + byte[] fullKey = store.getFullKey(table, keyOrPrefix); - if (direction == SeekDirection.Forward) - for (it.Seek(fullKey); it.Valid() && it.Key()[0] == table; it.Next()) - yield return (it.Key(), it.Value()); - else - for (it.SeekForPrev(fullKey); it.Valid() && it.Key()[0] == table; it.Prev()) - yield return (it.Key(), it.Value()); - } + if (direction == SeekDirection.Forward) + for (it.Seek(fullKey); it.Valid() && it.Key()[0] == table; it.Next()) + yield return (it.Key(), it.Value()); + else + for (it.SeekForPrev(fullKey); it.Valid() && it.Key()[0] == table; it.Prev()) + yield return (it.Key(), it.Value()); + } - public bool Contains(byte table, byte[] key) - { - byte[] fullKey = key == null ? new byte[] { table } : store.getFullKey(table, key); - return db.Get(fullKey ?? Array.Empty(), store.defaultFamily, options) != null; - } + public bool Contains(byte table, byte[] key) + { + byte[] fullKey = key == null ? new byte[] { table } : store.getFullKey(table, key); + return db.Get(fullKey ?? Array.Empty(), store.defaultFamily, options) != null; + } - public byte[] TryGet(byte table, byte[] key) - { - byte[] fullKey = key == null ? new byte[] { table } : store.getFullKey(table, key); - // Console.WriteLine($"trying to get from rocksdb store, key: {BitConverter.ToString(fullKey)}"); - return db.Get(fullKey ?? Array.Empty(), store.defaultFamily, options); - } + public byte[] TryGet(byte table, byte[] key) + { + byte[] fullKey = key == null ? new byte[] { table } : store.getFullKey(table, key); + // Console.WriteLine($"trying to get from rocksdb store, key: {BitConverter.ToString(fullKey)}"); + return db.Get(fullKey ?? Array.Empty(), store.defaultFamily, options); + } - public void Dispose() - { - snapshot.Dispose(); - } + public void Dispose() + { + snapshot.Dispose(); } + } } diff --git a/packages/neo-one-node-vm/lib/Storage/RocksDB/Store.cs b/packages/neo-one-node-vm/lib/Storage/RocksDB/Store.cs index 338e95a50a..b6eb31f2d8 100644 --- a/packages/neo-one-node-vm/lib/Storage/RocksDB/Store.cs +++ b/packages/neo-one-node-vm/lib/Storage/RocksDB/Store.cs @@ -8,82 +8,82 @@ namespace NEOONE.Storage.RocksDB { - internal class Store : IStore + internal class Store : IStore + { + private readonly RocksDb db; + public readonly ColumnFamilyHandle defaultFamily; + public Store(string path) { - private readonly RocksDb db; - public readonly ColumnFamilyHandle defaultFamily; - public Store(string path) - { - var families = new ColumnFamilies(); - db = RocksDb.OpenReadOnly(Options.Default, Path.GetFullPath(path), families, false); - defaultFamily = db.GetDefaultColumnFamily(); - } + var families = new ColumnFamilies(); + db = RocksDb.OpenReadOnly(Options.Default, Path.GetFullPath(path), families, false); + defaultFamily = db.GetDefaultColumnFamily(); + } - public void Dispose() - { - db.Dispose(); - } + public void Dispose() + { + db.Dispose(); + } - /// - /// Get family - /// - /// Table - /// Return column family - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Get family + /// + /// Table + /// Return column family + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ISnapshot GetSnapshot() - { - return new Snapshot(this, db); - } + public ISnapshot GetSnapshot() + { + return new Snapshot(this, db); + } - public byte[] getFullKey(byte table, byte[] keyOrPrefix) - { - byte[] fullKey = new byte[keyOrPrefix.Length + 1]; - System.Buffer.SetByte(fullKey, 0, table); - System.Buffer.BlockCopy(keyOrPrefix, 0, fullKey, 1, keyOrPrefix.Length); - return fullKey; - } + public byte[] getFullKey(byte table, byte[] keyOrPrefix) + { + byte[] fullKey = new byte[keyOrPrefix.Length + 1]; + System.Buffer.SetByte(fullKey, 0, table); + System.Buffer.BlockCopy(keyOrPrefix, 0, fullKey, 1, keyOrPrefix.Length); + return fullKey; + } - public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte table, byte[] keyOrPrefix, SeekDirection direction = SeekDirection.Forward) - { - if (keyOrPrefix == null) keyOrPrefix = Array.Empty(); + public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte table, byte[] keyOrPrefix, SeekDirection direction = SeekDirection.Forward) + { + if (keyOrPrefix == null) keyOrPrefix = Array.Empty(); - byte[] fullKey = getFullKey(table, keyOrPrefix); - using var it = db.NewIterator(defaultFamily, Options.ReadDefault); - if (direction == SeekDirection.Forward) - for (it.Seek(fullKey); it.Valid(); it.Next()) - yield return (it.Key(), it.Value()); - else - for (it.SeekForPrev(fullKey); it.Valid(); it.Prev()) - yield return (it.Key(), it.Value()); - } + byte[] fullKey = getFullKey(table, keyOrPrefix); + using var it = db.NewIterator(defaultFamily, Options.ReadDefault); + if (direction == SeekDirection.Forward) + for (it.Seek(fullKey); it.Valid(); it.Next()) + yield return (it.Key(), it.Value()); + else + for (it.SeekForPrev(fullKey); it.Valid(); it.Prev()) + yield return (it.Key(), it.Value()); + } - public bool Contains(byte table, byte[] key) - { - byte[] fullKey = key == null ? new byte[] { table } : getFullKey(table, key); - return db.Get(fullKey ?? Array.Empty(), defaultFamily, Options.ReadDefault) != null; - } + public bool Contains(byte table, byte[] key) + { + byte[] fullKey = key == null ? new byte[] { table } : getFullKey(table, key); + return db.Get(fullKey ?? Array.Empty(), defaultFamily, Options.ReadDefault) != null; + } - public byte[] TryGet(byte table, byte[] key) - { - byte[] fullKey = key == null ? new byte[] { table } : getFullKey(table, key); - return db.Get(fullKey ?? Array.Empty(), defaultFamily, Options.ReadDefault); - } + public byte[] TryGet(byte table, byte[] key) + { + byte[] fullKey = key == null ? new byte[] { table } : getFullKey(table, key); + return db.Get(fullKey ?? Array.Empty(), defaultFamily, Options.ReadDefault); + } - public void Delete(byte table, byte[] key) - { - throw new InvalidOperationException(); - } + public void Delete(byte table, byte[] key) + { + throw new InvalidOperationException(); + } - public void Put(byte table, byte[] key, byte[] value) - { - throw new InvalidOperationException(); - } + public void Put(byte table, byte[] key, byte[] value) + { + throw new InvalidOperationException(); + } - public void PutSync(byte table, byte[] key, byte[] value) - { - throw new InvalidOperationException(); - } + public void PutSync(byte table, byte[] key, byte[] value) + { + throw new InvalidOperationException(); } + } } diff --git a/packages/neo-one-node-vm/src/Dispatcher.ts b/packages/neo-one-node-vm/src/Dispatcher.ts index 4e00288372..96c3f1af91 100644 --- a/packages/neo-one-node-vm/src/Dispatcher.ts +++ b/packages/neo-one-node-vm/src/Dispatcher.ts @@ -1,11 +1,18 @@ import * as nodePath from 'path'; import { ApplicationEngine, CreateOptions } from './ApplicationEngine'; -import { BaseMethods, EngineMethods, ProtocolSettings, ProtocolSettingsReturn, SnapshotMethods } from './Methods'; +import { + BaseMethods, + EngineMethods, + ProtocolSettings, + ProtocolSettingsReturn, + SnapshotMethods, + TestMethods, +} from './Methods'; import { SnapshotHandler } from './SnapshotHandler'; import { DispatcherFunc } from './types'; import { constants, createCSharpDispatchInvoke, validateProtocolSettings } from './utils'; -export interface DispatcherMethods extends BaseMethods, SnapshotMethods, EngineMethods {} +export interface DispatcherMethods extends BaseMethods, SnapshotMethods, EngineMethods, TestMethods {} const engineAssemblyOptions = { assemblyFile: nodePath.join(constants.CSHARP_APP_ROOT, 'Dispatcher.dll'), @@ -87,6 +94,21 @@ export class Dispatcher { }); } + public updateStore(storage: ReadonlyArray<{ key: Buffer; value: Buffer }>): void { + const tableChanges = storage.map((change) => ({ + table: change.key[0], + key: change.key.slice(1), + value: change.value, + })); + + this.dispatch({ + method: 'test_update_store', + args: { + changes: tableChanges, + }, + }); + } + // tslint:disable-next-line: no-any public test(): any { return this.dispatch({ diff --git a/packages/neo-one-node-vm/src/Methods/TestMethods.ts b/packages/neo-one-node-vm/src/Methods/TestMethods.ts new file mode 100644 index 0000000000..d251a9fe15 --- /dev/null +++ b/packages/neo-one-node-vm/src/Methods/TestMethods.ts @@ -0,0 +1,15 @@ +import { DefaultMethods, DispatchMethod } from '../types'; + +interface RawChange { + readonly table: number; + readonly key: Buffer; + readonly value: Buffer; +} + +interface UpdateStoreArgs { + readonly changes: readonly RawChange[]; +} + +export interface TestMethods extends DefaultMethods { + readonly test_update_store: DispatchMethod; +} diff --git a/packages/neo-one-node-vm/src/Methods/index.ts b/packages/neo-one-node-vm/src/Methods/index.ts index acbca381c9..09f6a76c85 100644 --- a/packages/neo-one-node-vm/src/Methods/index.ts +++ b/packages/neo-one-node-vm/src/Methods/index.ts @@ -1,3 +1,4 @@ export * from './BaseMethods'; export * from './EngineMethods'; export * from './SnapshotMethods'; +export * from './TestMethods'; diff --git a/packages/neo-one-node/package.json b/packages/neo-one-node/package.json index ae12f3757e..ec080fa20b 100644 --- a/packages/neo-one-node/package.json +++ b/packages/neo-one-node/package.json @@ -42,8 +42,10 @@ "@types/jest": "^24.0.18", "@types/leveldown": "^4.0.0", "@types/levelup": "^3.1.1", + "@types/memdown": "^3.0.0", "@types/rocksdb": "3.0.0", "bn.js": "^5.0.0", - "gulp": "~4.0.2" + "gulp": "~4.0.2", + "memdown": "^5.0.0" } } diff --git a/packages/neo-one-node/src/__data__/data.ts b/packages/neo-one-node/src/__data__/data.ts index e06fbf2bfb..6ab55b78de 100644 --- a/packages/neo-one-node/src/__data__/data.ts +++ b/packages/neo-one-node/src/__data__/data.ts @@ -3,7 +3,7 @@ import { Block, ConsensusData, Signer, Transaction, Witness } from '@neo-one/nod import { BN } from 'bn.js'; import { debugBlockJSON, genesisJSON, secondBlockJSON, thirdBlockJSON } from './jsonBlocks'; -const convertBlock = (json: BlockJSON) => +const convertBlock = (json: BlockJSON, messageMagic: number) => new Block({ version: json.version, previousHash: JSONHelper.readUInt256(json.previousblockhash), @@ -43,6 +43,7 @@ const convertBlock = (json: BlockJSON) => scopes: (signer.scopes as any) === 'FeeOnly' ? WitnessScopeModel.None : toWitnessScope(signer.scopes), }), ), + messageMagic, }), ), consensusData: json.consensusdata @@ -51,11 +52,12 @@ const convertBlock = (json: BlockJSON) => nonce: new BN(json.consensusdata.nonce, 16), }) : undefined, + messageMagic, }); -export const data = { - genesisBlock: convertBlock(genesisJSON as any), - secondBlock: convertBlock(secondBlockJSON as any), - thirdBlock: convertBlock(thirdBlockJSON as any), - debugBlock: convertBlock(debugBlockJSON as any), -}; +export const getData = (messageMagic: number) => ({ + genesisBlock: convertBlock(genesisJSON as any, messageMagic), + secondBlock: convertBlock(secondBlockJSON as any, messageMagic), + thirdBlock: convertBlock(thirdBlockJSON as any, messageMagic), + debugBlock: convertBlock(debugBlockJSON as any, messageMagic), +}); diff --git a/packages/neo-one-node/src/__tests__/blockchain.test.ts b/packages/neo-one-node/src/__tests__/blockchain.test.ts index 85e0f83b90..ad2e704c56 100644 --- a/packages/neo-one-node/src/__tests__/blockchain.test.ts +++ b/packages/neo-one-node/src/__tests__/blockchain.test.ts @@ -1,24 +1,14 @@ -import { common, crypto, JSONHelper, ScriptBuilder } from '@neo-one/client-common'; +import { common, ScriptBuilder } from '@neo-one/client-common'; import { Blockchain } from '@neo-one/node-blockchain'; -import { - Nep5BalanceKey, - StorageKey, - StreamOptions, - TrimmedBlock, - utils, - ContractState, - VMLog, -} from '@neo-one/node-core'; -import { KeyBuilder, NativeContainer, NEOAccountState } from '@neo-one/node-native'; +import { ContractState } from '@neo-one/node-core'; +import { NativeContainer } from '@neo-one/node-native'; import { test as createTest } from '@neo-one/node-neo-settings'; import { storage as levelupStorage } from '@neo-one/node-storage-levelup'; import { blockchainSettingsToProtocolSettings, Dispatcher } from '@neo-one/node-vm'; -import { utils as commonUtils } from '@neo-one/utils'; -import { BN } from 'bn.js'; import LevelUp from 'levelup'; import RocksDB from 'rocksdb'; -import { filter, map, toArray } from 'rxjs/operators'; -import { data } from '../__data__'; +import Memdown from 'memdown'; +import { getData } from '../__data__'; const rawReadStreamPromise = async (db: any, options: { readonly gte: Buffer; readonly lte: Buffer }) => new Promise((resolve, reject) => { @@ -41,8 +31,8 @@ const rawReadStreamPromise = async (db: any, options: { readonly gte: Buffer; re .on('end', resolve); }); -describe('Blockchain invocation / storage tests', () => { - test.only('dispatcher can retrieve logs after invocation', async () => { +describe.skip('Blockchain invocation / storage tests', () => { + test('dispatcher can retrieve logs after invocation', async () => { const blockchainSettings = createTest(); const levelDBPath = '/Users/danielbyrne/Desktop/node-data'; const db = LevelUp(RocksDB(levelDBPath)); @@ -79,3 +69,49 @@ describe('Blockchain invocation / storage tests', () => { expect(result.logs[0].message).toEqual('Hello world!'); }); }); + +describe('VM memory store for testing', () => { + test('can persist 2 blocks in memory', async () => { + const settings = createTest(); + const blockData = getData(settings.messageMagic); + const db = LevelUp(Memdown()); + + const storage = levelupStorage({ + db, + context: { + messageMagic: settings.messageMagic, + validatorsCount: settings.validatorsCount, + }, + }); + + const dispatcher = new Dispatcher({ + protocolSettings: blockchainSettingsToProtocolSettings(settings), + }); + + const native = new NativeContainer(settings); + + const blockchain = await Blockchain.create({ + settings, + storage, + vm: dispatcher, + native, + }); + + const changes = await new Promise>((resolve, reject) => { + let changesInternal: Array<{ key: Buffer; value: Buffer }> = []; + db.createReadStream() + .on('data', (data) => { + changesInternal = changesInternal.concat(data); + }) + .on('close', () => resolve(changesInternal)) + .on('end', () => resolve(changesInternal)) + .on('error', (reason) => reject(reason)); + }); + + dispatcher.updateStore(changes); + + await blockchain.persistBlock({ block: blockData.secondBlock }); + + expect(blockchain.currentBlockIndex).toEqual(1); + }); +}); diff --git a/packages/neo-one-node/src/startFullNode.ts b/packages/neo-one-node/src/startFullNode.ts index 768578f212..4c5c506317 100644 --- a/packages/neo-one-node/src/startFullNode.ts +++ b/packages/neo-one-node/src/startFullNode.ts @@ -6,13 +6,15 @@ import { NativeContainer } from '@neo-one/node-native'; import { Network, NetworkOptions } from '@neo-one/node-network'; import { dumpChain, loadChain } from '@neo-one/node-offline'; import { Node, NodeOptions } from '@neo-one/node-protocol'; -import { storage as levelupStorage } from '@neo-one/node-storage-levelup'; +import { storage as levelupStorage, streamToObservable } from '@neo-one/node-storage-levelup'; import { blockchainSettingsToProtocolSettings, Dispatcher } from '@neo-one/node-vm'; import { composeDisposable, Disposable, noopDisposable } from '@neo-one/utils'; import { AbstractLevelDOWN } from 'abstract-leveldown'; import fs from 'fs-extra'; import LevelUp from 'levelup'; import RocksDB from 'rocksdb'; +import Memdown from 'memdown'; +import { toArray } from 'rxjs/operators'; export interface LoggingOptions { readonly level?: 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent'; @@ -38,6 +40,16 @@ export interface FullNodeOptions { readonly leveldown?: AbstractLevelDOWN; } +const getUpdateVMMemoryStore = (vm: Dispatcher, db: any) => async () => { + const updates = await streamToObservable<{ readonly key: Buffer; readonly value: Buffer }>(() => + db.createReadStream(), + ) + .pipe(toArray()) + .toPromise(); + + vm.updateStore(updates); +}; + export const startFullNode = async ({ options: { path: dataPath, @@ -52,17 +64,20 @@ export const startFullNode = async ({ leveldown: customLeveldown, }: FullNodeOptions): Promise => { let disposable = noopDisposable; + const isMemoryStore = customLeveldown !== undefined && customLeveldown instanceof Memdown; try { - await fs.ensureDir(dataPath); + if (!isMemoryStore) { + await fs.ensureDir(dataPath); + } if (telemetry !== undefined && telemetry.logging !== undefined && telemetry.logging.level !== undefined) { setGlobalLogLevel(telemetry.logging.level); } - const rocks = customLeveldown === undefined ? RocksDB(dataPath) : customLeveldown; + const level = customLeveldown === undefined ? RocksDB(dataPath) : customLeveldown; disposable = composeDisposable(disposable, async () => { await new Promise((resolve, reject) => { - rocks.close((err) => { + level.close((err) => { if (err) { reject(err); } else { @@ -72,8 +87,10 @@ export const startFullNode = async ({ }); }); + const db = LevelUp(level); + const storage = levelupStorage({ - db: LevelUp(rocks), + db, context: { messageMagic: blockchainSettings.messageMagic, validatorsCount: blockchainSettings.validatorsCount }, }); @@ -82,7 +99,7 @@ export const startFullNode = async ({ const native = new NativeContainer(blockchainSettings); const vm = new Dispatcher({ - levelDBPath: dataPath, + levelDBPath: isMemoryStore ? undefined : dataPath, protocolSettings: blockchainSettingsToProtocolSettings(blockchainSettings), }); @@ -93,6 +110,7 @@ export const startFullNode = async ({ storage, native, vm, + onPersist: isMemoryStore ? getUpdateVMMemoryStore(vm, db) : undefined, }); disposable = composeDisposable(async () => blockchain.stop(), disposable); diff --git a/packages/neo-one-smart-contract-compiler/package.json b/packages/neo-one-smart-contract-compiler/package.json index 23810372b1..9c06c4f709 100644 --- a/packages/neo-one-smart-contract-compiler/package.json +++ b/packages/neo-one-smart-contract-compiler/package.json @@ -34,6 +34,7 @@ "@neo-one/build-tools": "^1.0.0", "@neo-one/client-switch": "^3.0.0", "@neo-one/node-blockchain": "^3.0.0", + "@neo-one/node-native": "^3.0.0", "@neo-one/node-neo-settings": "^3.0.0", "@neo-one/node-storage-levelup": "^3.0.0", "@neo-one/node-vm": "^3.0.0", diff --git a/packages/neo-one-smart-contract-compiler/src/__data__/helpers/executeScript.ts b/packages/neo-one-smart-contract-compiler/src/__data__/helpers/executeScript.ts index 899a8f4d0b..6ba12f7cca 100644 --- a/packages/neo-one-smart-contract-compiler/src/__data__/helpers/executeScript.ts +++ b/packages/neo-one-smart-contract-compiler/src/__data__/helpers/executeScript.ts @@ -2,12 +2,13 @@ import { CallReceiptJSON, common, crypto, scriptHashToAddress, SourceMaps } from import { Blockchain } from '@neo-one/node-blockchain'; import { test as testNet } from '@neo-one/node-neo-settings'; import { storage } from '@neo-one/node-storage-levelup'; -import { vm } from '@neo-one/node-vm'; +import { Dispatcher, blockchainSettingsToProtocolSettings } from '@neo-one/node-vm'; import LevelUp from 'levelup'; import MemDown from 'memdown'; import { RawSourceMap } from 'source-map'; import ts from 'typescript'; import { throwOnDiagnosticErrorOrWarning } from '../../utils'; +import { NativeContainer } from '@neo-one/node-native'; export interface ExecuteOptions { readonly prelude?: Buffer; @@ -28,13 +29,16 @@ export const executeScript = async ( readonly receipt: CallReceiptJSON; readonly sourceMaps: SourceMaps; }> => { + const settings = testNet(); + const dispatcher = new Dispatcher({ protocolSettings: blockchainSettingsToProtocolSettings(settings) }); const blockchain = await Blockchain.create({ - settings: testNet(), + settings, storage: storage({ - context: { messageMagic: testNet().messageMagic }, + context: { messageMagic: settings.messageMagic, validatorsCount: settings.validatorsCount }, db: LevelUp(MemDown()), }), - vm, + vm: dispatcher, + native: new NativeContainer(settings), }); throwOnDiagnosticErrorOrWarning(diagnostics, ignoreWarnings); @@ -45,9 +49,10 @@ export const executeScript = async ( const address = scriptHashToAddress(common.uInt160ToString(crypto.toScriptHash(code))); await blockchain.stop(); + // TODO: pickup here, how are we converting this? return { receipt: { - result: receipt.result.serializeJSON(blockchain.serializeJSONContext), + result: receipt, actions: receipt.actions.map((action) => action.serializeJSON(blockchain.serializeJSONContext)), }, sourceMaps: { diff --git a/packages/neo-one-smart-contract-compiler/src/__data__/helpers/extractors.ts b/packages/neo-one-smart-contract-compiler/src/__data__/helpers/extractors.ts index f4b3e4c107..11b88fd733 100644 --- a/packages/neo-one-smart-contract-compiler/src/__data__/helpers/extractors.ts +++ b/packages/neo-one-smart-contract-compiler/src/__data__/helpers/extractors.ts @@ -14,27 +14,29 @@ export const checkResult = async (receiptIn: CallReceiptJSON, sourceMaps: Source }; export const checkRawResult = async (receipt: RawCallReceipt, sourceMaps: SourceMaps, checkStack = false) => { - if (receipt.result.state === 'FAULT') { + if (receipt.state === 'FAULT') { enableConsoleLogForTest(); try { - const message = await processActionsAndMessage({ - actions: receipt.actions, - message: receipt.result.message, - sourceMaps, - }); + // TODO: reimplement this message processing + // const message = await processActionsAndMessage({ + // actions: receipt.actions, + // message: receipt.message, + // sourceMaps, + // }); - throw new Error(message); + throw new Error(receipt.stack as string); } finally { disableConsoleLogForTest(); } } - await processConsoleLogMessages({ - actions: receipt.actions, - sourceMaps, - }); + // TODO: reimplement this log processing + // await processConsoleLogMessages({ + // actions: receipt.actions, + // sourceMaps, + // }); - if (checkStack && receipt.result.stack.length !== 0) { - throw new Error(`Found leftover stack items, length: ${receipt.result.stack.length}`); + if (checkStack && receipt.stack.length !== 0) { + throw new Error(`Found leftover stack items, length: ${receipt.stack.length}`); } }; diff --git a/packages/neo-one-smart-contract-compiler/src/__data__/helpers/startNode.ts b/packages/neo-one-smart-contract-compiler/src/__data__/helpers/startNode.ts index dc1e38a478..04b4c409a3 100644 --- a/packages/neo-one-smart-contract-compiler/src/__data__/helpers/startNode.ts +++ b/packages/neo-one-smart-contract-compiler/src/__data__/helpers/startNode.ts @@ -1,17 +1,17 @@ /// import { - ABI, common, Contract, crypto, InvocationResult, - InvocationTransaction, RawInvokeReceipt, scriptHashToAddress, SmartContractDefinition, SourceMaps, UserAccountID, + Transaction, + ContractABI, } from '@neo-one/client-common'; import { DeveloperClient, LocalKeyStore, LocalWallet, NEOONEDataProvider, SmartContract } from '@neo-one/client-core'; import { Client, InvokeExecuteTransactionOptions, ReadClient } from '@neo-one/client-full-core'; @@ -21,10 +21,10 @@ import { Modifiable } from '@neo-one/utils'; import * as appRootDir from 'app-root-dir'; import BigNumber from 'bignumber.js'; import ts from 'typescript'; -import { createNode } from '../../../../neo-one-smart-contract-test/src/createNode'; import { compile } from '../../compile'; import { CompileResult, LinkedContracts } from '../../compile/types'; import { Context } from '../../Context'; +import { createNode } from '@neo-one/smart-contract-test'; import { createContextForPath, createContextForSnippet } from '../../createContext'; import { throwOnDiagnosticErrorOrWarning } from '../../utils'; import { checkRawResult } from './extractors'; @@ -57,7 +57,7 @@ export interface TestNode { options?: InvokeExecuteTransactionOptions, ) => Promise<{ readonly receipt: RawInvokeReceipt; - readonly transaction: InvocationTransaction; + readonly transaction: Transaction; readonly sourceMaps: SourceMaps; }>; readonly compileScript: (script: string) => CompileResult; @@ -70,7 +70,7 @@ export interface TestNode { export interface Options { readonly script: Buffer; - readonly abi: ABI; + readonly abi: ContractABI; readonly diagnostics: ReadonlyArray; readonly ignoreWarnings?: boolean; } @@ -140,8 +140,8 @@ export const startNode = async (outerOptions: StartNodeOptions = {}): Promise { - // tslint:disable-next-line no-object-mutation - options.systemFee = new BigNumber(-1); + options.maxNetworkFee = new BigNumber(-1); + options.maxSystemFee = new BigNumber(-1); }); return { @@ -156,7 +156,7 @@ export const startNode = async (outerOptions: StartNodeOptions = {}): Promise { @@ -207,7 +203,7 @@ export const startNode = async (outerOptions: StartNodeOptions = {}): Promise +import { createNode } from '../createNode'; +import { + NEOONEDataProvider, + NEOONEProvider, + LocalMemoryStore, + LocalKeyStore, + LocalUserAccountProvider, +} from '@neo-one/client-core'; +import { common } from '@neo-one/client-common'; +import BigNumber from 'bignumber.js'; + +const secondaryKeyString = '04c1784140445016cf0f8cc86dd10ad8764e1a89c563c500e21ac19a5d905ab3'; + +describe('createNode tests', () => { + test('can send master transfer', async () => { + const { privateKey, node, rpcURL } = await createNode(); + one.addCleanup(async () => node.stop()); + + const dataProvider = new NEOONEDataProvider({ network: 'priv', rpcURL }); + const networkName = dataProvider.network; + const masterWalletName = 'master'; + + const keystore = new LocalKeyStore(new LocalMemoryStore()); + const [masterAccount, emptyAccount] = await Promise.all([ + keystore.addMultiSigUserAccount({ + network: networkName, + privateKeys: [privateKey], + name: masterWalletName, + }), + keystore.addUserAccount({ + network: networkName, + privateKey: secondaryKeyString, + name: 'empty', + }), + ]); + + const provider = new NEOONEProvider([dataProvider]); + + const localUserAccountProvider = new LocalUserAccountProvider({ + keystore, + provider, + }); + + const transfer = { + to: emptyAccount.userAccount.id.address, + asset: common.nativeScriptHashes.NEO, + amount: new BigNumber(10), + }; + + const result = await localUserAccountProvider.transfer([transfer], { + from: masterAccount.userAccount.id, + maxNetworkFee: new BigNumber(-1), + maxSystemFee: new BigNumber(-1), + }); + + await result.confirmed(); + + const receipt = await localUserAccountProvider.provider.getApplicationLogData('priv', result.transaction.hash); + + const stackReturn = receipt.stack[0]; + if (typeof stackReturn === 'string') { + throw new Error('expected good return'); + } + + expect(stackReturn.value).toEqual(true); + }); +}); diff --git a/packages/neo-one-smart-contract-test/src/createNode.ts b/packages/neo-one-smart-contract-test/src/createNode.ts index d9a665d480..298839907d 100644 --- a/packages/neo-one-smart-contract-test/src/createNode.ts +++ b/packages/neo-one-smart-contract-test/src/createNode.ts @@ -1,4 +1,4 @@ -import { common, crypto, privateKeyToScriptHash } from '@neo-one/client-common'; +import { common, crypto } from '@neo-one/client-common'; import { FullNode } from '@neo-one/node'; import { createMain } from '@neo-one/node-neo-settings'; import { constants } from '@neo-one/utils'; @@ -17,7 +17,7 @@ export const createNode = async () => { blockchain: createMain({ privateNet: true, standbyValidators: [constants.PRIVATE_NET_PUBLIC_KEY], - address: privateKeyToScriptHash(privateKey), + extraCommitteeMembers: [], }), path: '/tmp/fakePath/', rpc: { @@ -35,6 +35,7 @@ export const createNode = async () => { }, leveldown: MemDown(), }); + await node.start(); return { diff --git a/packages/neo-one-smart-contract-test/src/index.ts b/packages/neo-one-smart-contract-test/src/index.ts index 18545c2ffc..4d666e4b7a 100644 --- a/packages/neo-one-smart-contract-test/src/index.ts +++ b/packages/neo-one-smart-contract-test/src/index.ts @@ -1,2 +1,3 @@ export * from './createWithContracts'; export * from './withContracts'; +export * from './createNode';