diff --git a/packages/neo-one-cli/src/__data__/projects/exchange/codegen/Exchange/abi.ts b/packages/neo-one-cli/src/__data__/projects/exchange/codegen/Exchange/abi.ts index 03a0e1bd4a..821fb242b4 100644 --- a/packages/neo-one-cli/src/__data__/projects/exchange/codegen/Exchange/abi.ts +++ b/packages/neo-one-cli/src/__data__/projects/exchange/codegen/Exchange/abi.ts @@ -297,7 +297,7 @@ export const exchangeABI: ABI = { { claim: false, constant: false, - name: 'depositNEP5', + name: 'depositNEP17', parameters: [ { forwardedValue: false, @@ -330,7 +330,7 @@ export const exchangeABI: ABI = { { claim: false, constant: false, - name: 'withdrawNEP5', + name: 'withdrawNEP17', parameters: [ { forwardedValue: false, diff --git a/packages/neo-one-cli/src/__data__/projects/exchange/codegen/Exchange/types.ts b/packages/neo-one-cli/src/__data__/projects/exchange/codegen/Exchange/types.ts index 3739e8ced7..7c480b4295 100644 --- a/packages/neo-one-cli/src/__data__/projects/exchange/codegen/Exchange/types.ts +++ b/packages/neo-one-cli/src/__data__/projects/exchange/codegen/Exchange/types.ts @@ -86,7 +86,7 @@ export interface ExchangeSmartContract extends options?: TransactionOptions & GetOptions, ) => Promise & { readonly transaction: InvocationTransaction }>; }; - readonly depositNEP5: { + readonly depositNEP17: { (from: AddressString, assetID: AddressString, amount: BigNumber, options?: TransactionOptions): Promise< TransactionResult, InvocationTransaction> >; @@ -171,7 +171,7 @@ export interface ExchangeSmartContract extends options?: TransactionOptions & GetOptions, ) => Promise & { readonly transaction: InvocationTransaction }>; }; - readonly withdrawNEP5: { + readonly withdrawNEP17: { (from: AddressString, assetID: AddressString, amount: BigNumber, options?: TransactionOptions): Promise< TransactionResult, InvocationTransaction> >; @@ -198,7 +198,7 @@ export interface ExchangeMigrationSmartContract { owner?: AddressString | Promise, options?: TransactionOptions & GetOptions, ) => Promise & { readonly transaction: InvocationTransaction }>; - readonly depositNEP5: ( + readonly depositNEP17: ( from: AddressString | Promise, assetID: AddressString | Promise, amount: BigNumber | Promise, @@ -247,7 +247,7 @@ export interface ExchangeMigrationSmartContract { feeAddress: AddressString | Promise, options?: TransactionOptions & GetOptions, ) => Promise & { readonly transaction: InvocationTransaction }>; - readonly withdrawNEP5: ( + readonly withdrawNEP17: ( from: AddressString | Promise, assetID: AddressString | Promise, amount: BigNumber | Promise, diff --git a/packages/neo-one-cli/src/__data__/projects/exchange/neo-one/contracts/Exchange.ts b/packages/neo-one-cli/src/__data__/projects/exchange/neo-one/contracts/Exchange.ts index ec1b4c8e27..1969f7392c 100644 --- a/packages/neo-one-cli/src/__data__/projects/exchange/neo-one/contracts/Exchange.ts +++ b/packages/neo-one-cli/src/__data__/projects/exchange/neo-one/contracts/Exchange.ts @@ -40,7 +40,7 @@ const notifyFilled = createEventNotifier boolean; } @@ -81,22 +81,22 @@ export class Exchange extends SmartContract { return this.offers.get(offerHash); } - public depositNEP5(from: Address, assetID: Address, amount: Fixed8): void { + public depositNEP17(from: Address, assetID: Address, amount: Fixed8): void { if (!Address.isCaller(from)) throw new Error('Caller was not the sender!'); if (amount < 1) throw new Error('Amount must be greater than 0!'); - this.transferNEP5(from, this.address, assetID, amount); + this.transferNEP17(from, this.address, assetID, amount); this.balances.set([from, assetID], this.balanceOf(from, assetID) + amount); notifyDeposited(from, assetID, amount); } - public withdrawNEP5(from: Address, assetID: Address, amount: Fixed8): void { + public withdrawNEP17(from: Address, assetID: Address, amount: Fixed8): void { if (amount < 0) throw new Error(`Amount must be greater than 0: ${amount}`); const balance = this.balanceOf(from, assetID); if (balance < amount) throw new Error(`Not enough Balance to withdraw ${amount}!`); if (!Address.isCaller(from)) throw new Error('Caller is not authorized to withdraw funds!'); - this.transferNEP5(this.address, from, assetID, amount); + this.transferNEP17(this.address, from, assetID, amount); this.balances.set([from, assetID], this.balanceOf(from, assetID) - amount); notifyWithdrawn(from, assetID, amount); } @@ -309,9 +309,9 @@ export class Exchange extends SmartContract { return crypto.hash256(offerBuffer); } - private transferNEP5(from: Address, to: Address, assetID: Address, amount: Fixed8): void { - const nep5Asset = SmartContract.for(assetID); - if (!nep5Asset.transfer(from, to, amount)) { + private transferNEP17(from: Address, to: Address, assetID: Address, amount: Fixed8): void { + const nep17Asset = SmartContract.for(assetID); + if (!nep17Asset.transfer(from, to, amount)) { throw new Error('Failed to transfer NEP-5 tokens!'); } } diff --git a/packages/neo-one-cli/src/__e2e__/cmd/buildExchange.test.ts b/packages/neo-one-cli/src/__e2e__/cmd/buildExchange.test.ts index 1ca00c6c2f..7ade48d848 100644 --- a/packages/neo-one-cli/src/__e2e__/cmd/buildExchange.test.ts +++ b/packages/neo-one-cli/src/__e2e__/cmd/buildExchange.test.ts @@ -69,7 +69,7 @@ const checkBalances = async ({ expect(tokensDeposited.toString()).toEqual(expected.tokensOnExchange); }; -const depositNEP5 = async ({ +const depositNEP17 = async ({ token, tokenAssetID, exchange, @@ -97,7 +97,7 @@ const depositNEP5 = async ({ expect(approveDepositReceipt.result.value).toEqual(true); const [exchangeDepositReceipt] = await Promise.all([ - exchange.depositNEP5.confirmed(from.address, tokenAssetID, amount, { from }), + exchange.depositNEP17.confirmed(from.address, tokenAssetID, amount, { from }), developerClient.runConsensusNow(), ]); expect(exchangeDepositReceipt.result.state).toEqual('HALT'); @@ -107,7 +107,7 @@ const depositNEP5 = async ({ await checkBalances({ token, tokenAssetID, from, exchange, exchangeAddress, expected }); }; -const withdrawNEP5 = async ({ +const withdrawNEP17 = async ({ token, tokenAssetID, exchange, @@ -127,7 +127,7 @@ const withdrawNEP5 = async ({ readonly developerClient: DeveloperClient; }) => { const [exchangeWithdrawalReceipt] = await Promise.all([ - exchange.withdrawNEP5.confirmed(from.address, tokenAssetID, amount, { from }), + exchange.withdrawNEP17.confirmed(from.address, tokenAssetID, amount, { from }), developerClient.runConsensusNow(), ]); expect(exchangeWithdrawalReceipt.result.state).toEqual('HALT'); @@ -137,7 +137,7 @@ const withdrawNEP5 = async ({ await checkBalances({ token, tokenAssetID, from, exchange, exchangeAddress, expected }); }; -const verifyNEP5NEP5Exchange = async ({ +const verifyNEP17NEP17Exchange = async ({ token, tokenAssetID, coin, @@ -158,7 +158,7 @@ const verifyNEP5NEP5Exchange = async ({ readonly toAccountID: UserAccountID; readonly developerClient: DeveloperClient; }) => { - await depositNEP5({ + await depositNEP17({ token, tokenAssetID, exchange, @@ -169,7 +169,7 @@ const verifyNEP5NEP5Exchange = async ({ developerClient, }); - await withdrawNEP5({ + await withdrawNEP17({ token, tokenAssetID, exchange, @@ -187,7 +187,7 @@ const verifyNEP5NEP5Exchange = async ({ ); expect(coinTransferReceipt.result.value).toEqual(true); - await depositNEP5({ + await depositNEP17({ token: coin, tokenAssetID: coinAssetID, exchange, @@ -300,7 +300,7 @@ const verifyExchangeContractTesting = async (codegenPath: string) => { const coinAssetID = coin.definition.networks[masterAccountID.network].address; const exchangeAddress = exchange.definition.networks[masterAccountID.network].address; - await verifyNEP5NEP5Exchange({ + await verifyNEP17NEP17Exchange({ token, tokenAssetID, coin, diff --git a/packages/neo-one-client-common/src/ScriptBuilder.ts b/packages/neo-one-client-common/src/ScriptBuilder.ts index 8c197bd497..faf8958fec 100644 --- a/packages/neo-one-client-common/src/ScriptBuilder.ts +++ b/packages/neo-one-client-common/src/ScriptBuilder.ts @@ -11,7 +11,7 @@ export class ScriptBuilder extends BaseScriptBuilder { super(); this.pushParamCallbacks = { - undefined: () => this.emitPush(Buffer.alloc(0, 0)), + undefined: () => this.emitOp('PUSHNULL'), array: (param) => this.emitPushArray(param), map: (param) => this.emitPushMap(param), uInt160: (param) => this.emitPushUInt160(common.asUInt160(param)), @@ -49,10 +49,14 @@ export class ScriptBuilder extends BaseScriptBuilder { } public emitPushArray(params: readonly ScriptBuilderParam[]): this { + if (params.length === 0) { + return this.emitOp('NEWARRAY0'); + } + this.emitPushParams(...params); this.emitPushParam(params.length); - return params.length !== 0 ? this.emitOp('PACK') : this.emitOp('NEWARRAY'); + return this.emitOp('PACK'); } // tslint:disable-next-line readonly-array diff --git a/packages/neo-one-client-common/src/common.ts b/packages/neo-one-client-common/src/common.ts index 38586410c7..9cd482f450 100644 --- a/packages/neo-one-client-common/src/common.ts +++ b/packages/neo-one-client-common/src/common.ts @@ -211,6 +211,7 @@ const isWildcard = (input: unknown): input is Wildcard => input === '*'; const NEGATIVE_SATOSHI_FIXED8 = new BN(-1); const TEN_FIXED8 = fixed8FromDecimal(10); +const TWENTY_FIXED8 = fixed8FromDecimal(20); const ONE_HUNDRED_FIXED8 = fixed8FromDecimal(100); const FOUR_HUNDRED_FIXED8 = fixed8FromDecimal(400); const FIVE_HUNDRED_FIXED8 = fixed8FromDecimal(500); @@ -220,15 +221,21 @@ const TEN_THOUSAND_FIXED8 = fixed8FromDecimal(10000); const ONE_HUNDRED_MILLION_FIXED8 = fixed8FromDecimal(100000000); const nativeScriptHashes = { - GAS: '0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc', - NEO: '0xde5f57d430d3dece511cf975a8d37848cb9e0525', - Policy: '0xce06595079cd69583126dbfd1d2e25cca74cffe9', + Management: '0xcd97b70d82d69adfcd9165374109419fade8d6ab', + NEO: '0x0a46e2e37c9987f570b4af253fb77e7eef0f72b6', + GAS: '0xa6a6c15dcdc9b997dac448b6926522d22efeedfb', + Policy: '0xdde31084c0fdbebc7f5ed5f53a38905305ccee14', + Oracle: '0xb1c37d5847c2ae36bdde31d0cc833a7ad9667f8f', + Designation: '0xc0073f4c7069bf38995780c9da065f9b3949ea7a', }; const nativeHashes = { + Management: hexToUInt160(nativeScriptHashes.Management), GAS: hexToUInt160(nativeScriptHashes.GAS), NEO: hexToUInt160(nativeScriptHashes.NEO), Policy: hexToUInt160(nativeScriptHashes.Policy), + Oracle: hexToUInt160(nativeScriptHashes.Oracle), + Designation: hexToUInt160(nativeScriptHashes.Designation), }; export const common = { @@ -249,6 +256,7 @@ export const common = { MAX_UINT256, NEGATIVE_SATOSHI_FIXED8, TEN_FIXED8, + TWENTY_FIXED8, ONE_HUNDRED_FIXED8, FOUR_HUNDRED_FIXED8, FIVE_HUNDRED_FIXED8, diff --git a/packages/neo-one-client-common/src/crypto.ts b/packages/neo-one-client-common/src/crypto.ts index f2cd912838..fdb684dd72 100644 --- a/packages/neo-one-client-common/src/crypto.ts +++ b/packages/neo-one-client-common/src/crypto.ts @@ -187,6 +187,15 @@ const createPrivateKey = (): PrivateKey => common.bufferToPrivateKey(randomBytes const toScriptHash = hash160; +const getContractHash = (sender: UInt160, script: Buffer) => { + const builder = new ScriptBuilder(); + builder.emitOp('ABORT'); + builder.emitPushUInt160(sender); + builder.emitPush(script); + + return toScriptHash(builder.build()); +}; + // Takes various formats and converts to standard ECPoint const toECPoint = (publicKey: Buffer): ECPoint => toECPointFromKeyPair(ec().keyFromPublic(publicKey)); @@ -891,6 +900,7 @@ export const crypto = { verify, privateKeyToPublicKey, toScriptHash, + getContractHash, toECPoint, createKeyPair, scriptHashToAddress, diff --git a/packages/neo-one-client-common/src/errors.ts b/packages/neo-one-client-common/src/errors.ts index 4fcfdbe844..d1e3d26937 100644 --- a/packages/neo-one-client-common/src/errors.ts +++ b/packages/neo-one-client-common/src/errors.ts @@ -95,7 +95,12 @@ export const InvalidAttributeTypeJSONError = makeErrorWithCode( ); export const InvalidAttributeTypeError = makeErrorWithCode( 'INVALID_ATTRIBUTE_TYPE', - (transactionAttributeType: number) => `Expected transaction type, found: ${transactionAttributeType.toString(16)}`, + (transactionAttributeType: number) => + `Expected transaction attribute type, found: ${transactionAttributeType.toString(16)}`, +); +export const InvalidOracleResponseCodeError = makeErrorWithCode( + 'INVALID_ORACLE_RESPONSE_CODE', + (value: number) => `Expected oracle response code, found: ${value.toString()}`, ); export const InvalidAttributeUsageError = makeErrorWithCode( 'INVALID_ATTRIBUTE_USAGE', diff --git a/packages/neo-one-client-common/src/models/index.ts b/packages/neo-one-client-common/src/models/index.ts index 50c880cde2..aa8f4878e4 100644 --- a/packages/neo-one-client-common/src/models/index.ts +++ b/packages/neo-one-client-common/src/models/index.ts @@ -7,7 +7,7 @@ export * from './StorageFlagsModel'; export * from './WitnessModel'; export * from './WitnessScopeModel'; export * from './transaction'; -export * from './nep5'; +export * from './nep17'; export * from './types'; export * from './vm'; export * from './trigger'; diff --git a/packages/neo-one-client-common/src/models/nep5/Nep5BalanceKeyModel.ts b/packages/neo-one-client-common/src/models/nep17/Nep17BalanceKeyModel.ts similarity index 78% rename from packages/neo-one-client-common/src/models/nep5/Nep5BalanceKeyModel.ts rename to packages/neo-one-client-common/src/models/nep17/Nep17BalanceKeyModel.ts index 5d22e82247..7855614ede 100644 --- a/packages/neo-one-client-common/src/models/nep5/Nep5BalanceKeyModel.ts +++ b/packages/neo-one-client-common/src/models/nep17/Nep17BalanceKeyModel.ts @@ -2,17 +2,17 @@ import { BinaryWriter } from '../../BinaryWriter'; import { UInt160 } from '../../common'; import { createSerializeWire, SerializableWire, SerializeWire } from '../Serializable'; -export interface Nep5BalanceKeyModelAdd { +export interface Nep17BalanceKeyModelAdd { readonly userScriptHash: UInt160; readonly assetScriptHash: UInt160; } -export class Nep5BalanceKeyModel implements SerializableWire { +export class Nep17BalanceKeyModel implements SerializableWire { public readonly userScriptHash: UInt160; public readonly assetScriptHash: UInt160; public readonly serializeWire: SerializeWire = createSerializeWire(this.serializeWireBase.bind(this)); - public constructor({ userScriptHash, assetScriptHash }: Nep5BalanceKeyModelAdd) { + public constructor({ userScriptHash, assetScriptHash }: Nep17BalanceKeyModelAdd) { this.userScriptHash = userScriptHash; this.assetScriptHash = assetScriptHash; } diff --git a/packages/neo-one-client-common/src/models/nep5/Nep5BalanceModel.ts b/packages/neo-one-client-common/src/models/nep17/Nep17BalanceModel.ts similarity index 78% rename from packages/neo-one-client-common/src/models/nep5/Nep5BalanceModel.ts rename to packages/neo-one-client-common/src/models/nep17/Nep17BalanceModel.ts index c177181682..c70bfeb61f 100644 --- a/packages/neo-one-client-common/src/models/nep5/Nep5BalanceModel.ts +++ b/packages/neo-one-client-common/src/models/nep17/Nep17BalanceModel.ts @@ -1,17 +1,17 @@ import { BinaryWriter } from '../../BinaryWriter'; import { createSerializeWire, SerializableWire, SerializeWire } from '../Serializable'; -export interface Nep5BalanceModelAdd { +export interface Nep17BalanceModelAdd { readonly balanceBuffer: Buffer; readonly lastUpdatedBlock: number; } -export class Nep5BalanceModel implements SerializableWire { +export class Nep17BalanceModel implements SerializableWire { public readonly balanceInternal: Buffer; public readonly lastUpdatedBlock: number; public readonly serializeWire: SerializeWire = createSerializeWire(this.serializeWireBase.bind(this)); - public constructor({ balanceBuffer, lastUpdatedBlock }: Nep5BalanceModelAdd) { + public constructor({ balanceBuffer, lastUpdatedBlock }: Nep17BalanceModelAdd) { this.balanceInternal = balanceBuffer; this.lastUpdatedBlock = lastUpdatedBlock; } diff --git a/packages/neo-one-client-common/src/models/nep5/Nep5TransferKeyModel.ts b/packages/neo-one-client-common/src/models/nep17/Nep17TransferKeyModel.ts similarity index 89% rename from packages/neo-one-client-common/src/models/nep5/Nep5TransferKeyModel.ts rename to packages/neo-one-client-common/src/models/nep17/Nep17TransferKeyModel.ts index fadd3d4bd1..0ff8dc1957 100644 --- a/packages/neo-one-client-common/src/models/nep5/Nep5TransferKeyModel.ts +++ b/packages/neo-one-client-common/src/models/nep17/Nep17TransferKeyModel.ts @@ -3,14 +3,14 @@ import { BinaryWriter } from '../../BinaryWriter'; import { UInt160 } from '../../common'; import { createSerializeWire, SerializableWire, SerializeWire } from '../Serializable'; -export interface Nep5TransferKeyModelAdd { +export interface Nep17TransferKeyModelAdd { readonly userScriptHash: UInt160; readonly timestampMS: BN; readonly assetScriptHash: UInt160; readonly blockTransferNotificationIndex: number; } -export class Nep5TransferKeyModel implements SerializableWire { +export class Nep17TransferKeyModel implements SerializableWire { public readonly userScriptHash: UInt160; public readonly timestampMS: BN; public readonly assetScriptHash: UInt160; @@ -22,7 +22,7 @@ export class Nep5TransferKeyModel implements SerializableWire { timestampMS, assetScriptHash, blockTransferNotificationIndex, - }: Nep5TransferKeyModelAdd) { + }: Nep17TransferKeyModelAdd) { this.userScriptHash = userScriptHash; this.timestampMS = timestampMS; this.assetScriptHash = assetScriptHash; diff --git a/packages/neo-one-client-common/src/models/nep5/Nep5TransferModel.ts b/packages/neo-one-client-common/src/models/nep17/Nep17TransferModel.ts similarity index 88% rename from packages/neo-one-client-common/src/models/nep5/Nep5TransferModel.ts rename to packages/neo-one-client-common/src/models/nep17/Nep17TransferModel.ts index 8ba02bad5c..a88e6bfaa2 100644 --- a/packages/neo-one-client-common/src/models/nep5/Nep5TransferModel.ts +++ b/packages/neo-one-client-common/src/models/nep17/Nep17TransferModel.ts @@ -2,21 +2,21 @@ import { BinaryWriter } from '../../BinaryWriter'; import { UInt160, UInt256 } from '../../common'; import { createSerializeWire, SerializableWire, SerializeWire } from '../Serializable'; -export interface Nep5TransferModelAdd { +export interface Nep17TransferModelAdd { readonly userScriptHash: UInt160; readonly blockIndex: number; readonly txHash: UInt256; readonly amountBuffer: Buffer; } -export class Nep5TransferModel implements SerializableWire { +export class Nep17TransferModel implements SerializableWire { public readonly userScriptHash: UInt160; public readonly blockIndex: number; public readonly txHash: UInt256; public readonly amountInternal: Buffer; public readonly serializeWire: SerializeWire = createSerializeWire(this.serializeWireBase.bind(this)); - public constructor({ userScriptHash, blockIndex, txHash, amountBuffer }: Nep5TransferModelAdd) { + public constructor({ userScriptHash, blockIndex, txHash, amountBuffer }: Nep17TransferModelAdd) { this.userScriptHash = userScriptHash; this.blockIndex = blockIndex; this.txHash = txHash; diff --git a/packages/neo-one-client-common/src/models/nep17/index.ts b/packages/neo-one-client-common/src/models/nep17/index.ts new file mode 100644 index 0000000000..21d52dfbff --- /dev/null +++ b/packages/neo-one-client-common/src/models/nep17/index.ts @@ -0,0 +1,4 @@ +export * from './Nep17BalanceModel'; +export * from './Nep17TransferModel'; +export * from './Nep17TransferKeyModel'; +export * from './Nep17BalanceKeyModel'; diff --git a/packages/neo-one-client-common/src/models/nep5/index.ts b/packages/neo-one-client-common/src/models/nep5/index.ts deleted file mode 100644 index e011fa908e..0000000000 --- a/packages/neo-one-client-common/src/models/nep5/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './Nep5BalanceModel'; -export * from './Nep5TransferModel'; -export * from './Nep5TransferKeyModel'; -export * from './Nep5BalanceKeyModel'; diff --git a/packages/neo-one-client-common/src/models/transaction/FeelessTransactionModel.ts b/packages/neo-one-client-common/src/models/transaction/FeelessTransactionModel.ts index a96c6ad73e..84a64c3f07 100644 --- a/packages/neo-one-client-common/src/models/transaction/FeelessTransactionModel.ts +++ b/packages/neo-one-client-common/src/models/transaction/FeelessTransactionModel.ts @@ -38,7 +38,7 @@ export interface FeelessTransactionModelAdd< export const MAX_TRANSACTION_ATTRIBUTES = 16; export const MAX_TRANSACTION_SIZE = 102400; -export const MAX_VALID_UNTIL_BLOCK_INCREMENT = 2102400; +export const MAX_VALID_UNTIL_BLOCK_INCREMENT = 5760; // 24 hours export const DEFAULT_VERSION = 0; export class FeelessTransactionModel< diff --git a/packages/neo-one-client-common/src/models/transaction/TransactionModel.ts b/packages/neo-one-client-common/src/models/transaction/TransactionModel.ts index 2e60cfa238..7c7f4a1fa8 100644 --- a/packages/neo-one-client-common/src/models/transaction/TransactionModel.ts +++ b/packages/neo-one-client-common/src/models/transaction/TransactionModel.ts @@ -71,6 +71,14 @@ export class TransactionModel< this.networkFee = networkFee; } + public getAttributes(isAttr: (attr: TAttribute) => attr is T): readonly T[] { + return this.attributes.filter(isAttr); + } + + public getAttribute(isAttr: (attr: TAttribute) => attr is T): T | undefined { + return this.getAttributes(isAttr)[0]; + } + public getScriptHashesForVerifying(): readonly UInt160[] { return this.signers.map((signer) => signer.account); } diff --git a/packages/neo-one-client-common/src/models/transaction/attribute/AttributeModel.ts b/packages/neo-one-client-common/src/models/transaction/attribute/AttributeModel.ts index c3e007ac6f..88cb26e419 100644 --- a/packages/neo-one-client-common/src/models/transaction/attribute/AttributeModel.ts +++ b/packages/neo-one-client-common/src/models/transaction/attribute/AttributeModel.ts @@ -1,3 +1,4 @@ import { HighPriorityAttributeModel } from './HighPriorityAttributeModel'; +import { OracleResponseModel } from './OracleResponseModel'; -export type AttributeModel = HighPriorityAttributeModel; +export type AttributeModel = HighPriorityAttributeModel | OracleResponseModel; diff --git a/packages/neo-one-client-common/src/models/transaction/attribute/AttributeTypeModel.ts b/packages/neo-one-client-common/src/models/transaction/attribute/AttributeTypeModel.ts index 2e267729f8..1afe84f14d 100644 --- a/packages/neo-one-client-common/src/models/transaction/attribute/AttributeTypeModel.ts +++ b/packages/neo-one-client-common/src/models/transaction/attribute/AttributeTypeModel.ts @@ -2,7 +2,8 @@ import { InvalidAttributeTypeError, InvalidAttributeTypeJSONError } from '../../ import { AttributeTypeJSON } from '../../types'; export enum AttributeTypeModel { - HighPriority = 1, + HighPriority = 0x01, + OracleResponse = 0x11, } const isAttributeType = (value: number): value is AttributeTypeModel => diff --git a/packages/neo-one-client-common/src/models/transaction/attribute/OracleResponseCode.ts b/packages/neo-one-client-common/src/models/transaction/attribute/OracleResponseCode.ts new file mode 100644 index 0000000000..0d67158b02 --- /dev/null +++ b/packages/neo-one-client-common/src/models/transaction/attribute/OracleResponseCode.ts @@ -0,0 +1,26 @@ +import { InvalidOracleResponseCodeError } from '../../../errors'; + +export enum OracleResponseCode { + Success = 0x00, + + ConsensusUnreachable = 0x10, + NotFound = 0x12, + Timeout = 0x14, + Forbidden = 0x16, + ResponseTooLarge = 0x18, + InsufficientFunds = 0x1a, + + Error = 0xff, +} + +const isOracleResponseCode = (value: number): value is OracleResponseCode => + // tslint:disable-next-line: strict-type-predicates + OracleResponseCode[value] !== undefined; + +export const assertOracleResponseCode = (value: number): OracleResponseCode => { + if (isOracleResponseCode(value)) { + return value; + } + + throw new InvalidOracleResponseCodeError(value); +}; diff --git a/packages/neo-one-client-common/src/models/transaction/attribute/OracleResponseModel.ts b/packages/neo-one-client-common/src/models/transaction/attribute/OracleResponseModel.ts new file mode 100644 index 0000000000..4695399ba3 --- /dev/null +++ b/packages/neo-one-client-common/src/models/transaction/attribute/OracleResponseModel.ts @@ -0,0 +1,37 @@ +import { BN } from 'bn.js'; +import { BinaryWriter } from '../../../BinaryWriter'; +import { IOHelper } from '../../../IOHelper'; +import { AttributeBaseModel } from './AttributeBaseModel'; +import { AttributeTypeModel } from './AttributeTypeModel'; +import { OracleResponseCode } from './OracleResponseCode'; + +export interface OracleResponseModelAdd { + readonly id: BN; + readonly code: OracleResponseCode; + readonly result: Buffer; +} + +export class OracleResponseModel extends AttributeBaseModel { + public readonly type = AttributeTypeModel.OracleResponse; + public readonly allowMultiple = false; + public readonly id: BN; + public readonly code: OracleResponseCode; + public readonly result: Buffer; + + public constructor({ id, code, result }: OracleResponseModelAdd) { + super(); + this.id = id; + this.code = code; + this.result = result; + } + + protected serializeWithoutTypeBase(writer: BinaryWriter) { + writer.writeUInt64LE(this.id); + writer.writeUInt8(this.code); + writer.writeVarBytesLE(this.result); + } + + protected sizeExclusive(): number { + return IOHelper.sizeOfUInt64LE + IOHelper.sizeOfUInt8 + IOHelper.sizeOfVarBytesLE(this.result); + } +} diff --git a/packages/neo-one-client-common/src/models/transaction/attribute/index.ts b/packages/neo-one-client-common/src/models/transaction/attribute/index.ts index 09d44f1986..9e1778f2d4 100644 --- a/packages/neo-one-client-common/src/models/transaction/attribute/index.ts +++ b/packages/neo-one-client-common/src/models/transaction/attribute/index.ts @@ -2,3 +2,5 @@ export * from './AttributeBaseModel'; export * from './AttributeModel'; export * from './AttributeTypeModel'; export * from './HighPriorityAttributeModel'; +export * from './OracleResponseCode'; +export * from './OracleResponseModel'; diff --git a/packages/neo-one-client-common/src/models/trigger.ts b/packages/neo-one-client-common/src/models/trigger.ts index 0f90bf91e2..fd8a6b923f 100644 --- a/packages/neo-one-client-common/src/models/trigger.ts +++ b/packages/neo-one-client-common/src/models/trigger.ts @@ -1,9 +1,12 @@ import { InvalidFormatError } from '../common'; export enum TriggerType { + OnPersist = 0x01, + PostPersist = 0x02, Verification = 0x20, - System = 0x01, Application = 0x40, + System = OnPersist | PostPersist, + All = OnPersist | PostPersist | Verification | Application, } export type TriggerTypeJSON = keyof typeof TriggerType; diff --git a/packages/neo-one-client-common/src/models/types.ts b/packages/neo-one-client-common/src/models/types.ts index 4d751a5af8..e57892139a 100644 --- a/packages/neo-one-client-common/src/models/types.ts +++ b/packages/neo-one-client-common/src/models/types.ts @@ -3,7 +3,7 @@ import { UInt256Hex } from '../common'; import { UserAccount } from '../types'; import { ContractParameterTypeModel } from './ContractParameterTypeModel'; import { StorageFlagsModel } from './StorageFlagsModel'; -import { AttributeTypeModel } from './transaction/attribute/AttributeTypeModel'; +import { AttributeTypeModel } from './transaction'; import { TriggerType, TriggerTypeJSON } from './trigger'; import { VerifyResultModel } from './VerifyResultModel'; import { VMState, VMStateJSON } from './vm'; @@ -196,10 +196,23 @@ export interface SignerJSON { readonly allowedgroups?: readonly string[]; } -export interface AttributeJSON { +export interface AttributeJSONBase { readonly type: AttributeTypeJSON; } +export interface HighPriorityAttributeJSON extends AttributeJSONBase { + readonly type: 'HighPriority'; +} + +export interface OracleResponseJSON extends AttributeJSONBase { + readonly type: 'OracleResponse'; + readonly id: string; + readonly code: number; + readonly result: string; +} + +export type AttributeJSON = HighPriorityAttributeJSON | OracleResponseJSON; + export type AttributeTypeJSON = keyof typeof AttributeTypeModel; export type VerifyResultJSON = keyof typeof VerifyResultModel; @@ -347,6 +360,7 @@ export interface ContractMethodDescriptorJSON { readonly parameters: readonly ContractParameterDefinitionJSON[]; readonly offset: number; readonly returntype: ContractParameterTypeJSON; + readonly safe: boolean; } export interface ContractEventDescriptorJSON { @@ -377,11 +391,6 @@ export interface ContractManifestJSON { readonly groups: readonly ContractGroupJSON[]; readonly permissions: readonly ContractPermissionJSON[]; readonly trusts: WildcardContainerJSON; - readonly safemethods: WildcardContainerJSON; - readonly features: { - readonly storage: boolean; - readonly payable: boolean; - }; readonly supportedstandards: readonly string[]; readonly extra?: JSONObject; } @@ -393,29 +402,30 @@ export interface ContractParameterDefinitionJSON { export interface ContractJSON { readonly id: number; + readonly updatecounter: number; readonly hash: string; readonly script: string; readonly manifest: ContractManifestJSON; } -export interface Nep5TransfersJSON { +export interface Nep17TransfersJSON { readonly address: string; - readonly received: readonly Nep5TransferJSON[]; - readonly sent: readonly Nep5TransferJSON[]; + readonly received: readonly Nep17TransferJSON[]; + readonly sent: readonly Nep17TransferJSON[]; } -export interface Nep5BalancesJSON { +export interface Nep17BalancesJSON { readonly address: string; - readonly balance: readonly Nep5BalanceJSON[]; + readonly balance: readonly Nep17BalanceJSON[]; } -export interface Nep5BalanceJSON { +export interface Nep17BalanceJSON { readonly assethash: string; readonly amount: string; readonly lastupdatedblock: number; } -export interface Nep5TransferJSON { +export interface Nep17TransferJSON { readonly timestamp: number; readonly assethash: string; readonly transferaddress: string; diff --git a/packages/neo-one-client-common/src/models/vm.ts b/packages/neo-one-client-common/src/models/vm.ts index 9ea9278aa7..a318c74b94 100644 --- a/packages/neo-one-client-common/src/models/vm.ts +++ b/packages/neo-one-client-common/src/models/vm.ts @@ -224,18 +224,17 @@ export enum SysCall { 'System.Blockchain.GetTransaction' = 'System.Blockchain.GetTransaction', 'System.Blockchain.GetTransactionHeight' = 'System.Blockchain.GetTransactionHeight', 'System.Blockchain.GetTransactionFromBlock' = 'System.Blockchain.GetTransactionFromBlock', - 'System.Blockchain.GetContract' = 'System.Blockchain.GetContract', 'System.Callback.Create' = 'System.Callback.Create', 'System.Callback.CreateFromMethod' = 'System.Callback.CreateFromMethod', 'System.Callback.CreateFromSyscall' = 'System.Callback.CreateFromSyscall', 'System.Callback.Invoke' = 'System.Callback.Invoke', - 'System.Contract.Create' = 'System.Contract.Create', - 'System.Contract.Update' = 'System.Contract.Update', - 'System.Contract.Destroy' = 'System.Contract.Destroy', 'System.Contract.Call' = 'System.Contract.Call', 'System.Contract.CallEx' = 'System.Contract.CallEx', + 'System.Contract.CallNative' = 'System.Contract.CallNative', 'System.Contract.IsStandard' = 'System.Contract.IsStandard', 'System.Contract.GetCallFlags' = 'System.Contract.GetCallFlags', + 'System.Contract.NativeOnPersist' = 'System.Contract.NativeOnPersist', + 'System.Contract.NativePostPersist' = 'System.Contract.NativePostPersist', 'System.Contract.CreateStandardAccount' = 'System.Contract.CreateStandardAccount', 'Neo.Crypto.RIPEMD160' = 'Neo.Crypto.RIPEMD160', 'Neo.Crypto.SHA256' = 'Neo.Crypto.SHA256', @@ -254,8 +253,6 @@ export enum SysCall { 'System.Iterator.Concat' = 'System.Iterator.Concat', 'System.Json.Serialize' = 'System.Json.Serialize', 'System.Json.Deserialize' = 'System.Json.Deserialize', - 'Neo.Native.Deploy' = 'Neo.Native.Deploy', - 'Neo.Native.Call' = 'Neo.Native.Call', 'System.Runtime.Platform' = 'System.Runtime.Platform', 'System.Runtime.GetTrigger' = 'System.Runtime.GetTrigger', 'System.Runtime.GetTime' = 'System.Runtime.GetTime', @@ -277,6 +274,10 @@ export enum SysCall { 'System.Storage.Put' = 'System.Storage.Put', 'System.Storage.PutEx' = 'System.Storage.PutEx', 'System.Storage.Delete' = 'System.Storage.Delete', + 'System.Binary.Base58Encode' = 'System.Binary.Base58Encode', + 'System.Binary.Base58Decode' = 'System.Binary.Base58Decode', + 'System.Binary.Itoa' = 'System.Binary.Itoa', + 'System.Binary.Atoi' = 'System.Binary.Atoi', } export type SysCallName = keyof typeof SysCall; diff --git a/packages/neo-one-client-common/src/prices.ts b/packages/neo-one-client-common/src/prices.ts index 3f92cefa78..9c3a77afa2 100644 --- a/packages/neo-one-client-common/src/prices.ts +++ b/packages/neo-one-client-common/src/prices.ts @@ -81,7 +81,7 @@ const opCodePrices: Record = { [Op.REVERSE4]: new BigNumber(60), [Op.REVERSEN]: new BigNumber(400), [Op.INITSSLOT]: new BigNumber(400), - [Op.INITSLOT]: new BigNumber(800), + [Op.INITSLOT]: new BigNumber(1600), [Op.LDSFLD0]: new BigNumber(60), [Op.LDSFLD1]: new BigNumber(60), [Op.LDSFLD2]: new BigNumber(60), @@ -140,8 +140,8 @@ const opCodePrices: Record = { [Op.AND]: new BigNumber(200), [Op.OR]: new BigNumber(200), [Op.XOR]: new BigNumber(200), - [Op.EQUAL]: new BigNumber(200), - [Op.NOTEQUAL]: new BigNumber(200), + [Op.EQUAL]: new BigNumber(1000), + [Op.NOTEQUAL]: new BigNumber(1000), [Op.SIGN]: new BigNumber(100), [Op.ABS]: new BigNumber(100), [Op.NEGATE]: new BigNumber(100), @@ -167,8 +167,8 @@ const opCodePrices: Record = { [Op.MIN]: new BigNumber(200), [Op.MAX]: new BigNumber(200), [Op.WITHIN]: new BigNumber(200), - [Op.PACK]: new BigNumber(7000), - [Op.UNPACK]: new BigNumber(7000), + [Op.PACK]: new BigNumber(15000), + [Op.UNPACK]: new BigNumber(15000), [Op.NEWARRAY0]: new BigNumber(400), [Op.NEWARRAY]: new BigNumber(15000), [Op.NEWARRAY_T]: new BigNumber(15000), @@ -178,11 +178,11 @@ const opCodePrices: Record = { [Op.SIZE]: new BigNumber(150), [Op.HASKEY]: new BigNumber(270000), [Op.KEYS]: new BigNumber(500), - [Op.VALUES]: new BigNumber(7000), + [Op.VALUES]: new BigNumber(270000), [Op.PICKITEM]: new BigNumber(270000), - [Op.APPEND]: new BigNumber(15000), + [Op.APPEND]: new BigNumber(270000), [Op.SETITEM]: new BigNumber(270000), - [Op.REVERSEITEMS]: new BigNumber(500), + [Op.REVERSEITEMS]: new BigNumber(270000), [Op.REMOVE]: new BigNumber(500), [Op.CLEARITEMS]: new BigNumber(400), [Op.ISNULL]: new BigNumber(60), @@ -199,3 +199,17 @@ export const getOpCodePrice = (value: Op): BigNumber => { return fee; }; + +export const signatureContractCost = getOpCodePrice(Op.PUSHDATA1) + .multipliedBy(2) + .plus(getOpCodePrice(Op.PUSHNULL)) + .plus(getOpCodePrice(Op.SYSCALL)) + .plus(ECDsaVerifyPrice); + +export const multiSignatureContractCost = (m: number, n: number) => + getOpCodePrice(Op.PUSHDATA1) + .multipliedBy(m + n) + .plus(getOpCodePrice(Op.PUSHINT8).multipliedBy(2)) + .plus(getOpCodePrice(Op.PUSHNULL)) + .plus(getOpCodePrice(Op.SYSCALL)) + .plus(ECDsaVerifyPrice.multipliedBy(n)); diff --git a/packages/neo-one-client-common/src/types.ts b/packages/neo-one-client-common/src/types.ts index 0693bc9b73..081dcba952 100644 --- a/packages/neo-one-client-common/src/types.ts +++ b/packages/neo-one-client-common/src/types.ts @@ -9,6 +9,7 @@ import { AccountContract, AttributeTypeModel, NotificationJSON, + OracleResponseCode, StackItemJSON, TriggerTypeJSON, VerifyResultModel, @@ -73,21 +74,28 @@ export type NetworkType = 'main' | 'test' | string; * @see Attribute */ export interface AttributeBase { + /** + * `type` specifies the `Attribute` type + */ readonly type: AttributeTypeModel; } /** * `Attribute` whose transaction is "high priority". */ export interface HighPriorityAttribute extends AttributeBase { - /** - * `type` specifies the `Attribute` type - */ readonly type: AttributeTypeModel.HighPriority; } + +export interface OracleResponse extends AttributeBase { + readonly type: AttributeTypeModel.OracleResponse; + readonly id: BigNumber; + readonly code: OracleResponseCode; + readonly result: BufferString; +} /** * `Attribute`s are used to store additional data on `Transaction`s. */ -export type Attribute = HighPriorityAttribute; +export type Attribute = HighPriorityAttribute | OracleResponse; export type WitnessScope = | 'None' @@ -429,6 +437,10 @@ export interface Transfer { * Destination address. */ readonly to: AddressString; + /** + * Additional data to be attached to the transaction. Typed as `any` but should be used cautiously since it will need to be converted. + */ + readonly data?: any; } /** @@ -682,7 +694,7 @@ export interface DeveloperProvider { } /** - * An `Account` represents the balances of NEO, GAS an other NEP5 assets at a given `Address`. + * An `Account` represents the balances of NEO, GAS an other NEP17 assets at a given `Address`. */ export interface Account { /** @@ -1321,6 +1333,10 @@ export interface ContractMethodDescriptorClient { * TODO: describe */ readonly returnType: ABIReturn; + /** + * flags this as a safe method callable by any source. + */ + readonly safe: boolean; /** * TODO: fill out description here */ @@ -1467,28 +1483,6 @@ export interface ContractGroup { readonly signature: BufferString; } -/** - * Flag which determines which features are available to a contract. - */ -export enum ContractFeatures { - /** - * Contract does not use any available features. - */ - NoProperty = 0x00, - /** - * Contract modifies blockchain storage. - */ - HasStorage = 0x01, - /** - * Contract can receive native assets. - */ - Payable = 0x04, - /** - * Contract modifies blockchain storage and can receive native assets. - */ - HasStoragePayable = 0x05, -} - export type WildcardContainer = readonly T[] | Wildcard; /** @@ -1514,8 +1508,8 @@ export interface ContractPermission { } /** - * A manifest explicitly declares the features and permissions a Contract will use. Once deployed, - * it will be limited by its declared list of features and permissions. `ContractManifestClient` + * A manifest explicitly declares the permissions a Contract will use. Once deployed, + * it will be limited by its declared list of permissions. `ContractManifestClient` * specifically contains extra contract information for use in the NEO•ONE Client */ export interface ContractManifestClient { @@ -1527,13 +1521,6 @@ export interface ContractManifestClient { * Set of mutually trusted contracts. */ readonly groups: readonly ContractGroup[]; - /** - * The features field describes what features are available for the contract. - */ - readonly features: { - readonly storage: boolean; - readonly payable: boolean; - }; /** * The Neo Enhancement Proposals (NEPs) and other standards that this smart contract supports. */ @@ -1552,46 +1539,21 @@ export interface ContractManifestClient { * The trusts field is an array containing a set of contract hashes or group of public keys. */ readonly trusts: WildcardContainer; - /** - * The safeMethods field is an array containing a set of safe methods. - */ - readonly safeMethods: WildcardContainer; /** * Custom user-defined JSON object. */ readonly extra?: JSONObject; - /** - * True if the `Contract` modified blockchain storage. - */ - readonly hasStorage: boolean; - /** - * True if the `Contract` can receive native assets. - */ - readonly payable: boolean; } /** - * A manifest explicitly declares the features and permissions a Contract will use. Once deployed, - * it will be limited by its declared list of features and permissions. + * A manifest explicitly declares the permissions a Contract will use. Once deployed, + * it will be limited by its declared list of permissions. */ export interface ContractManifest { /** * Set of mutually trusted contracts. */ readonly groups: readonly ContractGroup[]; - /** - * The features field describes what features are available for the contract. - */ - readonly features: { - /** - * True if the `Contract` modified blockchain storage. - */ - readonly storage: boolean; - /** - * True if the `Contract` can receive assets. - */ - readonly payable: boolean; - }; /** * The Neo Enhancement Proposals (NEPs) and other standards that this smart contract supports. */ @@ -1609,22 +1571,10 @@ export interface ContractManifest { * The trusts field is an array containing a set of contract hashes or group of public keys. */ readonly trusts: WildcardContainer; - /** - * The safeMethods field is an array containing a set of safe methods. - */ - readonly safeMethods: WildcardContainer; /** * Custom user-defined JSON object. */ readonly extra?: JSONObject; - /** - * True if the `Contract` modified blockchain storage. - */ - readonly hasStorage: boolean; - /** - * True if the `Contract` can receive assets. - */ - readonly payable: boolean; } declare const OpaqueTagSymbol: unique symbol; diff --git a/packages/neo-one-client-core/src/__data__/factory.ts b/packages/neo-one-client-core/src/__data__/factory.ts index 31624a78a3..9acf972495 100644 --- a/packages/neo-one-client-core/src/__data__/factory.ts +++ b/packages/neo-one-client-core/src/__data__/factory.ts @@ -78,7 +78,7 @@ import BigNumber from 'bignumber.js'; import { BN } from 'bn.js'; import { ContractEventDescriptor, ContractMethodDescriptor } from '../../../neo-one-node-core/src/manifest'; import { Hash160 } from '../Hash160'; -import * as nep5 from '../nep5'; +import * as nep17 from '../nep17'; import { LockedWallet, UnlockedWallet } from '../user'; import { data } from './data'; import { keys } from './keys'; @@ -93,7 +93,7 @@ const createContractABIJSON = (options: Partial = {}): Contract }); const createManifestJSON = (options: Partial = {}): ContractManifestJSON => { - const { abi, features, ...optionsIn } = options; + const { abi, ...optionsIn } = options; return { hash: keys[0].scriptHashString, @@ -102,12 +102,6 @@ const createManifestJSON = (options: Partial = {}): Contra groups: [], // TODO permissions: [], // TODO trusts: '*', - safeMethods: '*', - features: { - storage: true, - payable: true, - ...features, - }, supportedStandards: [], // TODO ...optionsIn, }; @@ -120,8 +114,6 @@ const createContractJSON = (options: Partial = {}): ContractJSON = id: 0, script: data.buffers.b, manifest: createManifestJSON(manifest), - hasStorage: true, - payable: true, ...optionsIn, }; }; @@ -732,7 +724,7 @@ const createStringABIParameter = (options: Partial = {}): St }); const createABI = (options: Partial = {}): ContractABI => ({ - ...nep5.abi(8), + ...nep17.abi(8), ...options, }); diff --git a/packages/neo-one-client-core/src/__data__/verifyDataProvider.ts b/packages/neo-one-client-core/src/__data__/verifyDataProvider.ts index b14f47a22e..5e805e8de9 100644 --- a/packages/neo-one-client-core/src/__data__/verifyDataProvider.ts +++ b/packages/neo-one-client-core/src/__data__/verifyDataProvider.ts @@ -96,8 +96,6 @@ const verifyManifest = (manifest: ContractManifest, manifestJSON: ContractManife expect(manifest.hash).toEqual(scriptHashToAddress(manifestJSON.hash)); // TODO: hashHex expect(manifest.trusts).toEqual(manifestJSON.trusts); - expect(manifest.safeMethods).toEqual(manifestJSON.safeMethods); - expect(manifest.features).toEqual(manifestJSON.features); expect(manifest.supportedStandards).toEqual(manifestJSON.supportedStandards); expect(manifest.extra).toEqual(manifestJSON.extra); verifyAbi(manifest.abi, manifestJSON.abi); @@ -114,8 +112,6 @@ const verifyManifest = (manifest: ContractManifest, manifestJSON: ContractManife const verifyContract = (contract: Contract, contractJSON: ContractJSON, returnType = 'Buffer') => { expect(contract.id).toEqual(contractJSON.id); expect(contract.script).toEqual(contractJSON.script); - expect(contract.hasStorage).toEqual(contractJSON.hasStorage); - expect(contract.payable).toEqual(contractJSON.payable); verifyManifest(contract.manifest, contractJSON.manifest); }; diff --git a/packages/neo-one-client-core/src/__tests__/__snapshots__/nep5.test.ts.snap b/packages/neo-one-client-core/src/__tests__/__snapshots__/nep5.test.ts.snap index eb39263e1a..58fdc25058 100644 --- a/packages/neo-one-client-core/src/__tests__/__snapshots__/nep5.test.ts.snap +++ b/packages/neo-one-client-core/src/__tests__/__snapshots__/nep5.test.ts.snap @@ -98,7 +98,7 @@ Object { } `; -exports[`nep5 createNEP5SmartContract 1`] = ` +exports[`nep5 createNEP17SmartContract 1`] = ` Array [ Array [ Object { diff --git a/packages/neo-one-client-core/src/__tests__/nep5.test.ts b/packages/neo-one-client-core/src/__tests__/nep17.test.ts similarity index 64% rename from packages/neo-one-client-core/src/__tests__/nep5.test.ts rename to packages/neo-one-client-core/src/__tests__/nep17.test.ts index c8016a34b0..513dbdd0f3 100644 --- a/packages/neo-one-client-core/src/__tests__/nep5.test.ts +++ b/packages/neo-one-client-core/src/__tests__/nep17.test.ts @@ -2,11 +2,11 @@ import BigNumber from 'bignumber.js'; import { data, factory } from '../__data__'; import { Client } from '../Client'; -import * as nep5 from '../nep5'; +import * as nep17 from '../nep17'; -describe('nep5', () => { +describe('nep17', () => { test('abi', () => { - expect(nep5.abi(4)).toMatchSnapshot(); + expect(nep17.abi(4)).toMatchSnapshot(); }); const smartContract: { decimals?: () => BigNumber } = {}; @@ -18,13 +18,13 @@ describe('nep5', () => { test('getDecimals', async () => { smartContract.decimals = jest.fn(() => data.bigNumbers.a); - const result = await nep5.getDecimals(client, factory.createSmartContractDefinition().networks, 'main'); + const result = await nep17.getDecimals(client, factory.createSmartContractDefinition().networks, 'main'); expect(result).toEqual(data.bigNumbers.a.toNumber()); }); - test('createNEP5SmartContract', () => { - const contract = nep5.createNEP5SmartContract(client, factory.createSmartContractDefinition().networks, 8); + test('createNEP17SmartContract', () => { + const contract = nep17.createNEP17SmartContract(client, factory.createSmartContractDefinition().networks, 8); expect(contract).toEqual(smartContract); expect(clientSmartContract.mock.calls).toMatchSnapshot(); diff --git a/packages/neo-one-client-core/src/__tests__/provider/temp.test.ts b/packages/neo-one-client-core/src/__tests__/provider/temp.test.ts index a20c2d6a29..45099661d8 100644 --- a/packages/neo-one-client-core/src/__tests__/provider/temp.test.ts +++ b/packages/neo-one-client-core/src/__tests__/provider/temp.test.ts @@ -28,8 +28,8 @@ describe('JSONRPCClient Tests', async () => { ['getStorage', getStorageArgs(common.nativeHashes.GAS, 11)], ['getApplicationLog', [transactionHash]], ['getBlock', [1]], - ['getNep5Balances', [address]], - ['getNep5Transfers', [addressWithTransfers, 1468595301000, 1603753592250]], + ['getNep17Balances', [address]], + ['getNep17Transfers', [addressWithTransfers, 1468595301000, 1603753592250]], ['getBestBlockHash', []], ['getBlockCount', []], ['getContract', [Hash160.NEO]], diff --git a/packages/neo-one-client-core/src/args.ts b/packages/neo-one-client-core/src/args.ts index d5288b2750..80e64786cd 100644 --- a/packages/neo-one-client-core/src/args.ts +++ b/packages/neo-one-client-core/src/args.ts @@ -789,10 +789,6 @@ export const assertContractManifestClient = (name: string, value?: unknown): Con groups: assertProperty(value, 'ContractManifest', 'groups', assertArray).map((group) => assertContractGroup('ContractManifest.groups', group), ), - features: { - storage: assertProperty(value, 'ContractManifest', 'hasStorage', assertBoolean), - payable: assertProperty(value, 'ContractManifest', 'payable', assertBoolean), - }, supportedStandards: assertProperty(value, 'ContractManifest', 'supportedStandards', assertArray).map((std) => assertString('ContractManifest.supportedStandards', std), ), @@ -801,10 +797,7 @@ export const assertContractManifestClient = (name: string, value?: unknown): Con assertContractPermission('ContractManifest.permissions', permission), ), trusts: assertWildcardContainerProperty(value, 'ContractManifest', 'trusts', assertUInt160Hex), - safeMethods: assertWildcardContainerProperty(value, 'ContractManifest', 'safeMethods', assertString), extra: assertProperty(value, 'ContractManifest', 'extra', assertNullableJSON), - hasStorage: assertProperty(value, 'ContractManifest', 'hasStorage', assertBoolean), - payable: assertProperty(value, 'ContractManifest', 'payable', assertBoolean), }; }; @@ -817,10 +810,6 @@ export const assertContractManifest = (name: string, value?: unknown): ContractM groups: assertProperty(value, 'ContractManifest', 'groups', assertArray).map((group) => assertContractGroup('ContractManifest.groups', group), ), - features: { - storage: assertProperty(value, 'ContractManifest', 'hasStorage', assertBoolean), - payable: assertProperty(value, 'ContractManifest', 'payable', assertBoolean), - }, supportedStandards: assertProperty(value, 'ContractManifest', 'supportedStandards', assertArray).map((std) => assertString('ContractManifest.supportedStandards', std), ), @@ -829,10 +818,7 @@ export const assertContractManifest = (name: string, value?: unknown): ContractM assertContractPermission('ContractManifest.permissions', permission), ), trusts: assertWildcardContainerProperty(value, 'ContractManifest', 'trusts', assertUInt160Hex), - safeMethods: assertWildcardContainerProperty(value, 'ContractManifest', 'safeMethods', assertString), extra: assertProperty(value, 'ContractManifest', 'extra', assertNullableJSON), - hasStorage: assertProperty(value, 'ContractManifest', 'hasStorage', assertBoolean), - payable: assertProperty(value, 'ContractManifest', 'payable', assertBoolean), }; }; diff --git a/packages/neo-one-client-core/src/index.ts b/packages/neo-one-client-core/src/index.ts index a891bda3d8..5cb6bb7070 100644 --- a/packages/neo-one-client-core/src/index.ts +++ b/packages/neo-one-client-core/src/index.ts @@ -1,6 +1,6 @@ import * as args from './args'; import { addLocalKeysSync } from './clientUtils'; -import * as nep5 from './nep5'; +import * as nep17 from './nep17'; export * from './AsyncBlockIterator'; export * from './Client'; @@ -12,4 +12,4 @@ export * from './sc'; export * from './trace'; export * from './types'; export * from './user'; -export { args, nep5, addLocalKeysSync }; +export { args, nep17, addLocalKeysSync }; diff --git a/packages/neo-one-client-core/src/nep5.ts b/packages/neo-one-client-core/src/nep17.ts similarity index 85% rename from packages/neo-one-client-core/src/nep5.ts rename to packages/neo-one-client-core/src/nep17.ts index fa54822a9f..3a68367979 100644 --- a/packages/neo-one-client-core/src/nep5.ts +++ b/packages/neo-one-client-core/src/nep17.ts @@ -18,19 +18,18 @@ import BigNumber from 'bignumber.js'; import { Client } from './Client'; import { SmartContract } from './types'; -export type NEP5Event = NEP5TransferEvent; +export type NEP17Event = NEP17TransferEvent; -export interface NEP5TransferEventParameters { +export interface NEP17TransferEventParameters { readonly from: AddressString | undefined; readonly to: AddressString | undefined; readonly amount: BigNumber; } -export interface NEP5TransferEvent extends Event<'transfer', NEP5TransferEventParameters> {} +export interface NEP17TransferEvent extends Event<'transfer', NEP17TransferEventParameters> {} -export interface NEP5SmartContract extends SmartContract { +export interface NEP17SmartContract extends SmartContract { readonly balanceOf: (address: AddressString, options?: SmartContractReadOptions) => Promise; readonly decimals: (options?: SmartContractReadOptions) => Promise; - readonly name: (options?: SmartContractReadOptions) => Promise; readonly owner: (options?: SmartContractReadOptions) => Promise; readonly symbol: (options?: SmartContractReadOptions) => Promise; readonly totalSupply: (options?: SmartContractReadOptions) => Promise; @@ -39,7 +38,7 @@ export interface NEP5SmartContract extends Smar to: AddressString, amount: BigNumber, options?: TransactionOptions, - ) => TransactionResult>; + ) => TransactionResult>; } const decimalsFunction: ContractMethodDescriptorClient = { @@ -48,6 +47,7 @@ const decimalsFunction: ContractMethodDescriptorClient = { parameters: [], returnType: { type: 'Integer', decimals: 0 }, offset: 0, + safe: true, }; // TODO: check that the script/hash can/should be blank here. also check offsets @@ -63,6 +63,7 @@ export const abi = (decimals: number): ContractABIClient => ({ parameters: [], returnType: { type: 'String' }, offset: 0, + safe: true, }, { name: 'symbol', @@ -70,6 +71,7 @@ export const abi = (decimals: number): ContractABIClient => ({ parameters: [], returnType: { type: 'String' }, offset: 0, + safe: true, }, decimalsFunction, { @@ -78,6 +80,7 @@ export const abi = (decimals: number): ContractABIClient => ({ parameters: [], returnType: { type: 'Integer', decimals }, offset: 0, + safe: true, }, { name: 'transfer', @@ -98,6 +101,7 @@ export const abi = (decimals: number): ContractABIClient => ({ ], returnType: { type: 'Boolean' }, offset: 0, + safe: false, }, { name: 'balanceOf', @@ -110,6 +114,7 @@ export const abi = (decimals: number): ContractABIClient => ({ ], returnType: { type: 'Integer', decimals }, offset: 0, + safe: false, }, ], events: [ @@ -140,17 +145,10 @@ export const abi = (decimals: number): ContractABIClient => ({ export const manifest = (decimals: number): ContractManifestClient => ({ hash: common.uInt160ToString(blankHash), groups: [], - features: { - storage: true, - payable: true, - }, supportedStandards: [], abi: abi(decimals), permissions: [], trusts: '*', - safeMethods: '*', - hasStorage: true, - payable: true, }); export const getDecimals = async ( @@ -171,12 +169,12 @@ export const getDecimals = async ( return decimalsBigNumber.toNumber(); }; -export const createNEP5SmartContract = ( +export const createNEP17SmartContract = ( client: TClient, networksDefinition: SmartContractNetworksDefinition, decimals: number, -): NEP5SmartContract => - client.smartContract>({ +): NEP17SmartContract => + client.smartContract>({ networks: networksDefinition, manifest: manifest(decimals), }); diff --git a/packages/neo-one-client-core/src/provider/JSONRPCClient.ts b/packages/neo-one-client-core/src/provider/JSONRPCClient.ts index c7f9b4f730..9dbd07b8ab 100644 --- a/packages/neo-one-client-core/src/provider/JSONRPCClient.ts +++ b/packages/neo-one-client-core/src/provider/JSONRPCClient.ts @@ -10,8 +10,8 @@ import { Hash256String, HeaderJSON, InvocationDataJSON, - Nep5BalancesJSON, - Nep5TransfersJSON, + Nep17BalancesJSON, + Nep17TransfersJSON, NetworkSettingsJSON, Peer, PrivateNetworkSettings, @@ -47,23 +47,23 @@ export class JSONRPCClient { ); } - public async getNep5Balances(address: AddressString): Promise { + public async getNep17Balances(address: AddressString): Promise { return this.withInstance(async (provider) => provider.request({ - method: 'getnep5balances', + method: 'getnep17balances', params: [addressToScriptHash(address)], }), ); } - public async getNep5Transfers( + public async getNep17Transfers( address: AddressString, startTime?: number, endTime?: number, - ): Promise { + ): Promise { return this.withInstance(async (provider) => provider.request({ - method: 'getnep5transfers', + method: 'getnep17transfers', params: [addressToScriptHash(address), startTime, endTime], }), ); @@ -81,6 +81,10 @@ export class JSONRPCClient { return this.withInstance(async (provider) => provider.request({ method: 'getfeeperbyte' })); } + public async getExecFeeFactor(): Promise { + return this.withInstance(async (provider) => provider.request({ method: 'getexecfeefactor' })); + } + public async getContract(address: AddressString): Promise { return this.withInstance(async (provider) => provider.request({ diff --git a/packages/neo-one-client-core/src/provider/NEOONEDataProvider.ts b/packages/neo-one-client-core/src/provider/NEOONEDataProvider.ts index 350b66f225..8c5548bed9 100644 --- a/packages/neo-one-client-core/src/provider/NEOONEDataProvider.ts +++ b/packages/neo-one-client-core/src/provider/NEOONEDataProvider.ts @@ -5,6 +5,7 @@ import { ApplicationLogJSON, Attribute, AttributeJSON, + AttributeTypeModel, Block, BlockJSON, ConfirmedTransaction, @@ -27,7 +28,6 @@ import { ContractPermission, ContractPermissionJSON, DeveloperProvider, - FeelessTransactionModel, GetOptions, Hash256String, IterOptions, @@ -35,6 +35,7 @@ import { NetworkSettings, NetworkSettingsJSON, NetworkType, + OracleResponseJSON, Peer, PrivateNetworkSettings, RawApplicationLogData, @@ -54,9 +55,9 @@ import { TransactionModel, TransactionReceipt, TransactionReceiptJSON, + UInt160Hex, VerifyResultJSON, VerifyResultModel, - UInt160Hex, } from '@neo-one/client-common'; import { utils as commonUtils } from '@neo-one/utils'; import { AsyncIterableX } from '@reactivex/ix-es2015-cjs/asynciterable/asynciterablex'; @@ -150,6 +151,10 @@ export class NEOONEDataProvider implements DeveloperProvider { return new BigNumber(feePerByte); } + public async getExecFeeFactor(): Promise { + return this.mutableClient.getExecFeeFactor(); + } + public async getVerificationCost( hash: AddressString, transaction: TransactionModel, @@ -265,7 +270,7 @@ export class NEOONEDataProvider implements DeveloperProvider { } public async getAccount(address: AddressString): Promise { - const balances = await this.mutableClient.getNep5Balances(address); + const balances = await this.mutableClient.getNep17Balances(address); return { address, @@ -356,9 +361,29 @@ export class NEOONEDataProvider implements DeveloperProvider { } private convertAttributes(attributes: readonly AttributeJSON[]): readonly Attribute[] { - return attributes.map((attribute) => ({ - type: toAttributeType(attribute.type), - })); + return attributes.map(this.convertAttribute); + } + + private convertAttribute(attribute: AttributeJSON): Attribute { + const type = toAttributeType(attribute.type); + switch (type) { + case AttributeTypeModel.HighPriority: + return { + type, + }; + case AttributeTypeModel.OracleResponse: + // tslint:disable-next-line: no-any we know this is true but TS is being mean + const oracleJSON = attribute as OracleResponseJSON; + + return { + type, + id: new BigNumber(oracleJSON.id), + code: oracleJSON.code, + result: oracleJSON.result, + }; + default: + throw new Error(); + } } private convertContract(contract: ContractJSON): Contract { @@ -373,18 +398,11 @@ export class NEOONEDataProvider implements DeveloperProvider { private convertContractManifest(manifest: ContractManifestJSON): ContractManifest { return { groups: manifest.groups.map(this.convertContractGroup), - features: { - storage: manifest.features.storage, - payable: manifest.features.payable, - }, supportedStandards: manifest.supportedstandards, abi: this.convertContractABI(manifest.abi), permissions: manifest.permissions.map(this.convertContractPermission), trusts: manifest.trusts, - safeMethods: manifest.safemethods, extra: manifest.extra, - hasStorage: manifest.features.storage, - payable: manifest.features.payable, }; } diff --git a/packages/neo-one-client-core/src/provider/NEOONEProvider.ts b/packages/neo-one-client-core/src/provider/NEOONEProvider.ts index b2bd704da6..a49661024b 100644 --- a/packages/neo-one-client-core/src/provider/NEOONEProvider.ts +++ b/packages/neo-one-client-core/src/provider/NEOONEProvider.ts @@ -2,7 +2,6 @@ import { Account, AddressString, Block, - FeelessTransactionModel, GetOptions, Hash256String, IterOptions, @@ -85,6 +84,10 @@ export class NEOONEProvider implements Provider { return this.getProvider(network).getFeePerByte(); } + public async getExecFeeFactor(network: NetworkType): Promise { + return this.getProvider(network).getExecFeeFactor(); + } + public async getVerificationCost( network: NetworkType, hash: UInt160Hex, diff --git a/packages/neo-one-client-core/src/user/LocalUserAccountProvider.ts b/packages/neo-one-client-core/src/user/LocalUserAccountProvider.ts index 495cfd1938..92cebe99b1 100644 --- a/packages/neo-one-client-core/src/user/LocalUserAccountProvider.ts +++ b/packages/neo-one-client-core/src/user/LocalUserAccountProvider.ts @@ -12,7 +12,6 @@ import { NetworkType, Op, Param, - RawAction, RelayTransactionResult, ScriptBuilder, ScriptBuilderParam, @@ -33,7 +32,6 @@ import { WitnessScopeModel, } from '@neo-one/client-common'; import { processActionsAndMessage } from '@neo-one/client-switch'; -import { utils as commonUtils } from '@neo-one/utils'; import BigNumber from 'bignumber.js'; import { Observable } from 'rxjs'; import { InsufficientNetworkFeeError, InvokeError, UnknownAccountError } from '../errors'; @@ -171,19 +169,38 @@ export class LocalUserAccountProvider { + if (crypto.toScriptHash(witness.verification).equals(hash)) { + witnessScript = witness.verification; + } + }); + } + + // it may seem odd to throw here and continue logic, but it helps keep our other checks type safe when it IS defined. + if (witnessScript === undefined) { + throw new Error('Witness still not defined so we try to look it up as a contract with a "verify" method'); + } const multiSig = crypto.isMultiSigContractWithResult(witnessScript); if (multiSig.result) { @@ -191,17 +208,21 @@ export class LocalUserAccountProvider account.id.network === id.network && account.id.address === id.address); + } + + private getUserAccount(id: UserAccountID): UserAccount { + const userAccount = this.tryGetUserAccount(id); if (userAccount === undefined) { throw new UnknownAccountError(id.address); } diff --git a/packages/neo-one-client-core/src/user/UserAccountProviderBase.ts b/packages/neo-one-client-core/src/user/UserAccountProviderBase.ts index f2fe69dba3..b0279b5b6c 100644 --- a/packages/neo-one-client-core/src/user/UserAccountProviderBase.ts +++ b/packages/neo-one-client-core/src/user/UserAccountProviderBase.ts @@ -27,12 +27,12 @@ import { TransactionReceipt, TransactionResult, Transfer, + UInt160Hex, 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'; @@ -127,6 +127,7 @@ export interface Provider { readonly getNetworkSettings: (network: NetworkType) => Promise; readonly getBlockCount: (network: NetworkType) => Promise; readonly getFeePerByte: (network: NetworkType) => Promise; + readonly getExecFeeFactor: (network: NetworkType) => Promise; readonly getTransaction: (network: NetworkType, hash: Hash256String) => Promise; readonly iterBlocks: (network: NetworkType, options?: IterOptions) => AsyncIterable; readonly getAccount: (network: NetworkType, address: AddressString) => Promise; diff --git a/packages/neo-one-client-core/src/user/converters/attribute.ts b/packages/neo-one-client-core/src/user/converters/attribute.ts index 46fceb2ff8..cc66ed3c7e 100644 --- a/packages/neo-one-client-core/src/user/converters/attribute.ts +++ b/packages/neo-one-client-core/src/user/converters/attribute.ts @@ -1,8 +1,23 @@ -import { Attribute, AttributeModel, HighPriorityAttributeModel } from '@neo-one/client-common'; +import { + Attribute, + AttributeModel, + AttributeTypeModel, + HighPriorityAttributeModel, + OracleResponseModel, +} from '@neo-one/client-common'; +import { BN } from 'bn.js'; export const attribute = (attrib: Attribute): AttributeModel => { switch (attrib.type) { - default: + case AttributeTypeModel.HighPriority: return new HighPriorityAttributeModel(); + case AttributeTypeModel.OracleResponse: + return new OracleResponseModel({ + id: new BN(attrib.id.toString()), + code: attrib.code, + result: Buffer.from(attrib.result, 'hex'), + }); + default: + throw new Error('for ts'); } }; diff --git a/packages/neo-one-client-full-common/src/__data__/models.ts b/packages/neo-one-client-full-common/src/__data__/models.ts index ad343a0b07..8fffd94f32 100644 --- a/packages/neo-one-client-full-common/src/__data__/models.ts +++ b/packages/neo-one-client-full-common/src/__data__/models.ts @@ -1,9 +1,8 @@ -import { common, ContractParameterTypeModel, ECPoint, SignatureString, UInt160 } from '@neo-one/client-common'; +import { common, ContractParameterTypeModel, ECPoint, UInt160 } from '@neo-one/client-common'; import { constants } from '@neo-one/utils'; import { ContractABIModel, ContractEventDescriptorModel, - ContractFeaturesModel, ContractGroupModel, ContractManifestModel, ContractMethodDescriptorModel, @@ -73,7 +72,7 @@ export const contractMethodDescriptorModel = ( returnType: ContractParameterTypeModel = ContractParameterTypeModel.Void, name = 'function', offset = 0, -) => new ContractMethodDescriptorModel({ name, parameters, returnType, offset }); +) => new ContractMethodDescriptorModel({ name, parameters, returnType, offset, safe: true }); export const contractEventDescriptorModel = ( parameters: readonly ContractParameterDefinitionModel[] = [contractParamDefinitionModel.boolean], @@ -112,14 +111,13 @@ export const contractPermissionModel = ( export const contractManifestModel = ( groups: readonly ContractGroupModel[] = [contractGroupModel()], - features: ContractFeaturesModel = ContractFeaturesModel.HasStoragePayable, abi: ContractABIModel = contractAbiModel(), permissions: readonly ContractPermissionModel[] = [contractPermissionModel('uint160', ['method1'])], trusts: readonly UInt160[] = [common.bufferToUInt160(Buffer.alloc(20, 1))], - safeMethods: readonly string[] = ['method1', 'method2'], supportedStandards: readonly string[] = [], -) => new ContractManifestModel({ groups, features, supportedStandards, abi, permissions, trusts, safeMethods }); +) => new ContractManifestModel({ groups, supportedStandards, abi, permissions, trusts }); +// TODO: fixup export const contractModel = ( id = 1, script: Buffer = Buffer.alloc(25), diff --git a/packages/neo-one-client-full-common/src/__tests__/models/ContractFeaturesModel.test.ts b/packages/neo-one-client-full-common/src/__tests__/models/ContractFeaturesModel.test.ts deleted file mode 100644 index 1fd5c61650..0000000000 --- a/packages/neo-one-client-full-common/src/__tests__/models/ContractFeaturesModel.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { assertContractFeature, getContractProperties, HasPayable, HasStorage } from '../../models'; - -describe('ContractFeaturesModel', () => { - test('isContractFeatures', () => { - expect(assertContractFeature(0)).toEqual(0); - expect(assertContractFeature(1)).toEqual(1); - expect(assertContractFeature(4)).toEqual(4); - expect(assertContractFeature(5)).toEqual(5); - - const assertThrowNeg1 = () => assertContractFeature(-1); - expect(assertThrowNeg1).toThrowErrorMatchingSnapshot(); - const assertThrow2 = () => assertContractFeature(2); - expect(assertThrow2).toThrowErrorMatchingSnapshot(); - const assertThrow3 = () => assertContractFeature(3); - expect(assertThrow3).toThrowErrorMatchingSnapshot(); - const assertThrow6 = () => assertContractFeature(6); - expect(assertThrow6).toThrowErrorMatchingSnapshot(); - }); - - test('getContractProperties', () => { - expect(getContractProperties({ hasStorage: false, payable: false })).toEqual(0); - expect(getContractProperties({ hasStorage: false, payable: true })).toEqual(4); - expect(getContractProperties({ hasStorage: true, payable: false })).toEqual(1); - expect(getContractProperties({ hasStorage: true, payable: true })).toEqual(5); - }); - - test('HasStorage', () => { - expect(HasStorage.has(0)).toEqual(false); - expect(HasStorage.has(1)).toEqual(true); - expect(HasStorage.has(2)).toEqual(false); - expect(HasStorage.has(3)).toEqual(false); - expect(HasStorage.has(4)).toEqual(false); - expect(HasStorage.has(5)).toEqual(true); - expect(HasStorage.has(6)).toEqual(false); - }); - - test('HasPayable', () => { - expect(HasPayable.has(0)).toEqual(false); - expect(HasPayable.has(1)).toEqual(false); - expect(HasPayable.has(2)).toEqual(false); - expect(HasPayable.has(3)).toEqual(false); - expect(HasPayable.has(4)).toEqual(true); - expect(HasPayable.has(5)).toEqual(true); - expect(HasPayable.has(6)).toEqual(false); - }); -}); diff --git a/packages/neo-one-client-full-common/src/__tests__/models/__snapshots__/ContractFeaturesModel.test.ts.snap b/packages/neo-one-client-full-common/src/__tests__/models/__snapshots__/ContractFeaturesModel.test.ts.snap deleted file mode 100644 index 34676b5689..0000000000 --- a/packages/neo-one-client-full-common/src/__tests__/models/__snapshots__/ContractFeaturesModel.test.ts.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ContractFeaturesModel isContractFeatures 1`] = `"Expected contract feature, found: -1"`; - -exports[`ContractFeaturesModel isContractFeatures 2`] = `"Expected contract feature, found: 2"`; - -exports[`ContractFeaturesModel isContractFeatures 3`] = `"Expected contract feature, found: 3"`; - -exports[`ContractFeaturesModel isContractFeatures 4`] = `"Expected contract feature, found: 6"`; diff --git a/packages/neo-one-client-full-common/src/__tests__/models/__snapshots__/ContractManifestModel.test.ts.snap b/packages/neo-one-client-full-common/src/__tests__/models/__snapshots__/ContractManifestModel.test.ts.snap index 4a417d4526..84602f7915 100644 --- a/packages/neo-one-client-full-common/src/__tests__/models/__snapshots__/ContractManifestModel.test.ts.snap +++ b/packages/neo-one-client-full-common/src/__tests__/models/__snapshots__/ContractManifestModel.test.ts.snap @@ -30,10 +30,6 @@ Object { ], }, "extra": undefined, - "features": Object { - "payable": true, - "storage": true, - }, "groups": Array [ Object { "publicKey": "0248be3c070df745a60f3b8b494efcc6caf90244d803a9a72fe95d9bae2120ec70", diff --git a/packages/neo-one-client-full-common/src/models/ContractStateModel.ts b/packages/neo-one-client-full-common/src/models/ContractStateModel.ts index 7b2a75726c..b1dd175ecf 100644 --- a/packages/neo-one-client-full-common/src/models/ContractStateModel.ts +++ b/packages/neo-one-client-full-common/src/models/ContractStateModel.ts @@ -1,32 +1,26 @@ -import { BinaryWriter, createSerializeWire, SerializableWire, SerializeWire } from '@neo-one/client-common'; +import { UInt160 } from '@neo-one/client-common'; import { ContractManifestModel } from './manifest'; export interface ContractStateModelAdd { readonly id: number; + readonly updateCounter: number; + readonly hash: UInt160; readonly script: Buffer; readonly manifest: TContractManifest; } -export class ContractStateModel - implements SerializableWire { +export class ContractStateModel { public readonly id: number; + public readonly updateCounter: number; + public readonly hash: UInt160; public readonly script: Buffer; public readonly manifest: TContractManifest; - public readonly hasStorage: boolean; - public readonly payable: boolean; - public readonly serializeWire: SerializeWire = createSerializeWire(this.serializeWireBase.bind(this)); - public constructor({ script, manifest, id }: ContractStateModelAdd) { + public constructor({ script, hash, manifest, id, updateCounter }: ContractStateModelAdd) { this.id = id; + this.updateCounter = updateCounter; + this.hash = hash; this.script = script; this.manifest = manifest; - this.hasStorage = manifest.hasStorage; - this.payable = manifest.payable; - } - - public serializeWireBase(writer: BinaryWriter): void { - writer.writeUInt32LE(this.id); - writer.writeVarBytesLE(this.script); - this.manifest.serializeWireBase(writer); } } diff --git a/packages/neo-one-client-full-common/src/models/manifest/ContractFeaturesModel.ts b/packages/neo-one-client-full-common/src/models/manifest/ContractFeaturesModel.ts deleted file mode 100644 index fca7fd9d26..0000000000 --- a/packages/neo-one-client-full-common/src/models/manifest/ContractFeaturesModel.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { InvalidContractFeatureError } from '../../errors'; - -export enum ContractFeaturesModel { - NoProperty = 0x00, - HasStorage = 0x01, - Payable = 0x04, - HasStoragePayable = 0x05, -} - -// tslint:disable-next-line variable-name -export const HasStorage = new Set([ContractFeaturesModel.HasStorage, ContractFeaturesModel.HasStoragePayable]); - -// tslint:disable-next-line variable-name -export const HasPayable = new Set([ContractFeaturesModel.Payable, ContractFeaturesModel.HasStoragePayable]); - -const isContractFeature = (value: number): value is ContractFeaturesModel => - // tslint:disable-next-line strict-type-predicates - ContractFeaturesModel[value] !== undefined; - -export const assertContractFeature = (value: number): ContractFeaturesModel => { - if (isContractFeature(value)) { - return value; - } - - throw new InvalidContractFeatureError(value); -}; - -export const getContractProperties = ({ - hasStorage, - payable, -}: { - readonly hasStorage: boolean; - readonly payable: boolean; -}): ContractFeaturesModel => { - if (hasStorage && payable) { - return ContractFeaturesModel.HasStoragePayable; - } - - if (hasStorage) { - return ContractFeaturesModel.HasStorage; - } - - if (payable) { - return ContractFeaturesModel.Payable; - } - - return ContractFeaturesModel.NoProperty; -}; diff --git a/packages/neo-one-client-full-common/src/models/manifest/ContractManifestModel.ts b/packages/neo-one-client-full-common/src/models/manifest/ContractManifestModel.ts index 9f2a0fd509..ce1ca62f5d 100644 --- a/packages/neo-one-client-full-common/src/models/manifest/ContractManifestModel.ts +++ b/packages/neo-one-client-full-common/src/models/manifest/ContractManifestModel.ts @@ -14,7 +14,6 @@ import { } from '@neo-one/client-common'; import { JSONObject } from '@neo-one/utils'; import { ContractABIModel } from './ContractABIModel'; -import { ContractFeaturesModel, HasPayable, HasStorage } from './ContractFeaturesModel'; import { ContractGroupModel } from './ContractGroupModel'; import { ContractPermissionModel } from './ContractPermissionModel'; @@ -24,12 +23,10 @@ export interface ContractManifestModelAdd< TContractPermission extends ContractPermissionModel = ContractPermissionModel > { readonly groups: readonly TContractGroup[]; - readonly features: ContractFeaturesModel; readonly supportedStandards: readonly string[]; readonly abi: TContractABI; readonly permissions: readonly TContractPermission[]; readonly trusts: WildcardContainer; - readonly safeMethods: WildcardContainer; readonly extra?: JSONObject; } @@ -40,38 +37,28 @@ export class ContractManifestModel< > implements SerializableWire, SerializableJSON { public static readonly maxLength = common.MAX_MANIFEST_LENGTH; public readonly groups: readonly TContractGroup[]; - public readonly features: ContractFeaturesModel; public readonly supportedStandards: readonly string[]; public readonly abi: TContractABI; public readonly permissions: readonly TContractPermission[]; public readonly trusts: WildcardContainer; - public readonly safeMethods: WildcardContainer; public readonly extra: JSONObject | undefined; - public readonly hasStorage: boolean; - public readonly payable: boolean; public readonly serializeWire: SerializeWire = createSerializeWire(this.serializeWireBase.bind(this)); private readonly hashInternal = utils.lazy(() => this.abi.hash); private readonly hashHexInternal = utils.lazy(() => common.uInt160ToHex(this.hash)); public constructor({ groups, - features, supportedStandards, abi, permissions, trusts, - safeMethods, extra, }: ContractManifestModelAdd) { this.groups = groups; - this.features = features; this.supportedStandards = supportedStandards; this.abi = abi; this.permissions = permissions; this.trusts = trusts; - this.safeMethods = safeMethods; - this.hasStorage = HasStorage.has(features); - this.payable = HasPayable.has(features); this.extra = extra; } @@ -86,15 +73,10 @@ export class ContractManifestModel< public serializeJSON(): ContractManifestJSON { return { groups: this.groups.map((group) => group.serializeJSON()), - features: { - storage: this.hasStorage, - payable: this.payable, - }, supportedstandards: this.supportedStandards, abi: this.abi.serializeJSON(), permissions: this.permissions.map((permission) => permission.serializeJSON()), trusts: common.isWildcard(this.trusts) ? this.trusts : this.trusts.map((trust) => JSONHelper.writeUInt160(trust)), - safemethods: this.safeMethods, extra: this.extra, }; } diff --git a/packages/neo-one-client-full-common/src/models/manifest/ContractMethodDescriptorModel.ts b/packages/neo-one-client-full-common/src/models/manifest/ContractMethodDescriptorModel.ts index dea125bc5e..78ece19fd8 100644 --- a/packages/neo-one-client-full-common/src/models/manifest/ContractMethodDescriptorModel.ts +++ b/packages/neo-one-client-full-common/src/models/manifest/ContractMethodDescriptorModel.ts @@ -12,6 +12,7 @@ export interface ContractMethodDescriptorModelAdd< > extends ContractEventDescriptorModelAdd { readonly returnType: ContractParameterTypeModel; readonly offset: number; + readonly safe: boolean; } export class ContractMethodDescriptorModel< @@ -21,11 +22,13 @@ export class ContractMethodDescriptorModel< implements SerializableJSON { public readonly returnType: ContractParameterTypeModel; public readonly offset: number; + public readonly safe: boolean; public constructor(options: ContractMethodDescriptorModelAdd) { super(options); this.returnType = options.returnType; this.offset = options.offset; + this.safe = options.safe; } public serializeJSON(): ContractMethodDescriptorJSON { @@ -33,6 +36,7 @@ export class ContractMethodDescriptorModel< ...super.serializeJSON(), offset: this.offset, returntype: toJSONContractParameterType(this.returnType), + safe: this.safe, }; } } diff --git a/packages/neo-one-client-full-common/src/models/manifest/index.ts b/packages/neo-one-client-full-common/src/models/manifest/index.ts index 5377d49665..fad1e417bb 100644 --- a/packages/neo-one-client-full-common/src/models/manifest/index.ts +++ b/packages/neo-one-client-full-common/src/models/manifest/index.ts @@ -1,6 +1,5 @@ export * from './ContractABIModel'; export * from './ContractEventDescriptorModel'; -export * from './ContractFeaturesModel'; export * from './ContractGroupModel'; export * from './ContractManifestModel'; export * from './ContractMethodDescriptorModel'; diff --git a/packages/neo-one-client-full-core/src/__data__/factory.ts b/packages/neo-one-client-full-core/src/__data__/factory.ts index b2f3fd47a4..9d7699134c 100644 --- a/packages/neo-one-client-full-core/src/__data__/factory.ts +++ b/packages/neo-one-client-full-core/src/__data__/factory.ts @@ -93,7 +93,7 @@ import { Witness, WitnessJSON, } from '@neo-one/client-common'; -import { Hash256, LockedWallet, nep5, UnlockedWallet } from '@neo-one/client-core'; +import { Hash256, LockedWallet, nep17, UnlockedWallet } from '@neo-one/client-core'; import BigNumber from 'bignumber.js'; import { BN } from 'bn.js'; import { AssetRegister, ContractRegister } from '../types'; @@ -894,7 +894,7 @@ const createStringABIParameter = (options: Partial = {}): St }); const createABI = (options: Partial = {}): ABI => ({ - ...nep5.abi(8), + ...Nep17.abi(8), ...options, }); diff --git a/packages/neo-one-client-full-core/src/__tests__/args.test.ts b/packages/neo-one-client-full-core/src/__tests__/args.test.ts index 8f4730aa3b..9fa4deb34f 100644 --- a/packages/neo-one-client-full-core/src/__tests__/args.test.ts +++ b/packages/neo-one-client-full-core/src/__tests__/args.test.ts @@ -1,4 +1,4 @@ -import { args as clientArgs, nep5 } from '@neo-one/client-core'; +import { args as clientArgs, nep17 } from '@neo-one/client-core'; import { data } from '../__data__'; import * as args from '../args'; @@ -129,14 +129,14 @@ describe('arg assertions', () => { expect(() => args.assertContractRegister('value', value)).toThrowErrorMatchingSnapshot(); }); - test('assertABI - nep5', () => { - const value = nep5.abi(8); + test('assertABI - nep17', () => { + const value = nep17.abi(8); expect(clientArgs.assertContractABIClient('value', value)).toEqual(value); }); test('assertContractManifest', () => { - const value = nep5.manifest(8); + const value = nep17.manifest(8); expect(clientArgs.assertContractManifest('value', value)).toEqual(value); }); diff --git a/packages/neo-one-client-full-core/src/user/LocalUserAccountProvider.ts b/packages/neo-one-client-full-core/src/user/LocalUserAccountProvider.ts index 5ce24d5d62..6fd3f1b487 100644 --- a/packages/neo-one-client-full-core/src/user/LocalUserAccountProvider.ts +++ b/packages/neo-one-client-full-core/src/user/LocalUserAccountProvider.ts @@ -106,6 +106,7 @@ const contractRegisterToContractModel = (contractIn: ContractRegister): Contract name: param.name, }), ), + safe: methodIn.safe, }), ), events: contractIn.manifest.abi.events.map( @@ -122,10 +123,6 @@ const contractRegisterToContractModel = (contractIn: ContractRegister): Contract groups: contractIn.manifest.groups.map( (group) => new ContractGroupModel({ publicKey: group.publicKey, signature: Buffer.from(group.signature, 'hex') }), ), - features: getContractProperties({ - hasStorage: contractIn.manifest.features.storage, - payable: contractIn.manifest.features.payable, - }), supportedStandards: contractIn.manifest.supportedStandards, abi, permissions: contractIn.manifest.permissions.map( @@ -136,7 +133,6 @@ const contractRegisterToContractModel = (contractIn: ContractRegister): Contract }), ), trusts: contractIn.manifest.trusts, - safeMethods: contractIn.manifest.safeMethods, extra: contractIn.manifest.extra, }); diff --git a/packages/neo-one-client-full/src/index.ts b/packages/neo-one-client-full/src/index.ts index 37167dcac8..e55e0e778b 100644 --- a/packages/neo-one-client-full/src/index.ts +++ b/packages/neo-one-client-full/src/index.ts @@ -127,7 +127,7 @@ export { decryptNEP2, encryptNEP2, isNEP2, - nep5, + nep17, privateKeyToAddress, privateKeyToPublicKey, privateKeyToScriptHash, diff --git a/packages/neo-one-client/src/index.ts b/packages/neo-one-client/src/index.ts index 36ef85f618..9912593ea8 100644 --- a/packages/neo-one-client/src/index.ts +++ b/packages/neo-one-client/src/index.ts @@ -39,7 +39,6 @@ export { ContractABIClient, ContractEventDescriptor, ContractEventDescriptorClient, - ContractFeatures, ContractGroup, ContractManifest, ContractManifestClient, @@ -199,7 +198,7 @@ export { SmartContract, SmartContractAny, UnlockedWallet, - nep5, + nep17, } from '@neo-one/client-core'; // export { DeveloperTools } from '@neo-one/developer-tools'; diff --git a/packages/neo-one-developer-tools-frame/src/AddToken.tsx b/packages/neo-one-developer-tools-frame/src/AddToken.tsx index ab3f4d4d69..516286ebfc 100644 --- a/packages/neo-one-developer-tools-frame/src/AddToken.tsx +++ b/packages/neo-one-developer-tools-frame/src/AddToken.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled'; -import { nep5 } from '@neo-one/client-core'; +import { nep17 } from '@neo-one/client-core'; import { Box, Button, TextInput } from '@neo-one/react-common'; import * as React from 'react'; import { DeveloperToolsContext, useTokens } from './DeveloperToolsContext'; @@ -24,8 +24,8 @@ export function AddToken(props: {}) { Promise.resolve() .then(async () => { const network = client.getCurrentNetwork(); - const decimals = await nep5.getDecimals(client, { [network]: { address } }, network); - const smartContract = nep5.createNEP5SmartContract(client, { [network]: { address } }, decimals); + const decimals = await nep17.getDecimals(client, { [network]: { address } }, network); + const smartContract = nep17.createNEP17SmartContract(client, { [network]: { address } }, decimals); const symbol = await smartContract.symbol({ network }); onChangeTokens(tokens.concat({ network, address, decimals, symbol })); diff --git a/packages/neo-one-developer-tools-frame/src/BalanceSelector.tsx b/packages/neo-one-developer-tools-frame/src/BalanceSelector.tsx index bbec89cf3a..f0730abcd2 100644 --- a/packages/neo-one-developer-tools-frame/src/BalanceSelector.tsx +++ b/packages/neo-one-developer-tools-frame/src/BalanceSelector.tsx @@ -1,6 +1,6 @@ // tslint:disable no-object-mutation import styled from '@emotion/styled'; -import { nep5 } from '@neo-one/client-core'; +import { nep17 } from '@neo-one/client-core'; import { Box, usePrevious, useStream } from '@neo-one/react-common'; import { utils } from '@neo-one/utils'; import BigNumber from 'bignumber.js'; @@ -75,7 +75,7 @@ export function BalanceSelector() { accountState$.pipe(filter(utils.notNull)).pipe( switchMap(async ({ currentUserAccount, account }) => { if (asset.type === 'token') { - const smartContract = nep5.createNEP5SmartContract( + const smartContract = nep17.createNEP17SmartContract( client, { [asset.token.network]: { address: asset.token.address } }, asset.token.decimals, diff --git a/packages/neo-one-developer-tools-frame/src/WalletSelectorBase.tsx b/packages/neo-one-developer-tools-frame/src/WalletSelectorBase.tsx index d3d3851d98..59f7462300 100644 --- a/packages/neo-one-developer-tools-frame/src/WalletSelectorBase.tsx +++ b/packages/neo-one-developer-tools-frame/src/WalletSelectorBase.tsx @@ -1,7 +1,7 @@ // tslint:disable no-any import styled from '@emotion/styled'; import { Account, UserAccount } from '@neo-one/client-common'; -import { Client, Hash256, nep5 } from '@neo-one/client-core'; +import { Client, Hash256, nep17 } from '@neo-one/client-core'; import { Box, Select } from '@neo-one/react-common'; import { PromiseReturnType, utils } from '@neo-one/utils'; import BigNumber from 'bignumber.js'; @@ -43,7 +43,7 @@ export const makeOption = async ({ tokens .filter((token) => token.network === userAccount.id.network) .map>(async (token) => { - const smartContract = nep5.createNEP5SmartContract( + const smartContract = nep17.createNEP17SmartContract( client, { [token.network]: { address: token.address } }, token.decimals, diff --git a/packages/neo-one-developer-tools-frame/src/WalletTransfer.tsx b/packages/neo-one-developer-tools-frame/src/WalletTransfer.tsx index dd44f310bc..454969a5e4 100644 --- a/packages/neo-one-developer-tools-frame/src/WalletTransfer.tsx +++ b/packages/neo-one-developer-tools-frame/src/WalletTransfer.tsx @@ -1,5 +1,5 @@ import { TransactionResult, UserAccount } from '@neo-one/client-common'; -import { nep5 } from '@neo-one/client-core'; +import { nep17 } from '@neo-one/client-core'; import BigNumber from 'bignumber.js'; import * as React from 'react'; import { useNetworkClients } from './DeveloperToolsContext'; @@ -69,7 +69,7 @@ export function WalletTransfer() { // tslint:disable-next-line possible-timing-attack if (asset.type === 'token') { - const smartContract = nep5.createNEP5SmartContract( + const smartContract = nep17.createNEP17SmartContract( client, { [asset.token.network]: { address: asset.token.address } }, asset.token.decimals, diff --git a/packages/neo-one-node-bin/src/__data__/configs/consensus.ts b/packages/neo-one-node-bin/src/__data__/configs/consensus.ts index 1a694f2ef6..dfba130d35 100644 --- a/packages/neo-one-node-bin/src/__data__/configs/consensus.ts +++ b/packages/neo-one-node-bin/src/__data__/configs/consensus.ts @@ -1,7 +1,7 @@ import { createMain, serializeSettings } from '@neo-one/node-neo-settings'; import { getTestKeys } from '../getTestKeys'; -const { standbyValidator, address, privateKeyString } = getTestKeys(); +const { standbyValidator, privateKeyString } = getTestKeys(); export const consensus = (rpcPort: number, path: string) => ({ path, diff --git a/packages/neo-one-node-blockchain/package.json b/packages/neo-one-node-blockchain/package.json index 1f54a93d5e..051756ad3a 100644 --- a/packages/neo-one-node-blockchain/package.json +++ b/packages/neo-one-node-blockchain/package.json @@ -33,9 +33,11 @@ "@types/leveldown": "^4.0.0", "@types/lodash": "^4.14.138", "@types/lru-cache": "^5.1.0", + "@types/memdown": "^3.0.0", "leveldown": "^5.1.1", "levelup": "4.1.0", "gulp": "~4.0.2", + "memdown": "^5.0.0", "tslib": "^1.10.0" } } diff --git a/packages/neo-one-node-blockchain/src/Blockchain.ts b/packages/neo-one-node-blockchain/src/Blockchain.ts index c0cba7b63e..2545f01232 100644 --- a/packages/neo-one-node-blockchain/src/Blockchain.ts +++ b/packages/neo-one-node-blockchain/src/Blockchain.ts @@ -24,14 +24,13 @@ import { Header, Mempool, NativeContainer, - Nep5Balance, + Nep17Balance, Notification, RunEngineOptions, Signers, Storage, Transaction, TransactionVerificationContext, - VerifyConsensusPayloadOptions, VerifyOptions, VM, Witness, @@ -47,16 +46,17 @@ import { GenesisBlockNotRegisteredError, RecoverBlockchainError, } from './errors'; -import { getNep5UpdateOptions } from './getNep5UpdateOptions'; +import { getNep17UpdateOptions } from './getNep17UpdateOptions'; import { HeaderIndexCache } from './HeaderIndexCache'; import { PersistingBlockchain } from './PersistingBlockchain'; import { utils } from './utils'; -import { verifyWitnesses } from './verify'; +import { verifyWitness, verifyWitnesses } from './verify'; const logger = createChild(nodeLogger, { service: 'blockchain' }); export interface CreateBlockchainOptions { readonly onPersistNativeContractScript?: Buffer; + readonly postPersistNativeContractScript?: Buffer; readonly settings: BlockchainSettings; readonly storage: Storage; readonly native: NativeContainer; @@ -164,9 +164,11 @@ export class Blockchain { public readonly deserializeWireContext: DeserializeWireContext; + public readonly verifyWitness = verifyWitness; public readonly verifyWitnesses = verifyWitnesses; public readonly settings: BlockchainSettings; public readonly onPersistNativeContractScript: Buffer; + public readonly postPersistNativeContractScript: Buffer; // tslint:disable-next-line: readonly-array private readonly headerIndexCache: HeaderIndexCache; @@ -195,12 +197,19 @@ export class Blockchain { this.vm = options.vm; this.onPersistNativeContractScript = options.onPersistNativeContractScript ?? utils.getOnPersistNativeContractScript(); + this.postPersistNativeContractScript = + options.postPersistNativeContractScript ?? utils.getPostPersistNativeContractScript(); this.deserializeWireContext = { messageMagic: this.settings.messageMagic, validatorsCount: this.settings.validatorsCount, }; this.mutableCurrentBlock = options.currentBlock; - this.onPersist = options.onPersist === undefined ? this.vm.updateSnapshots : options.onPersist; + this.onPersist = + options.onPersist === undefined + ? () => { + this.vm.updateSnapshots(); + } + : options.onPersist; this.start(); } @@ -238,15 +247,7 @@ export class Blockchain { storage: this.storage, native: this.native, verifyWitnesses: this.verifyWitnesses, - }; - } - - public get verifyConsensusPayloadOptions(): VerifyConsensusPayloadOptions { - return { - vm: this.vm, - storage: this.storage, - native: this.native, - verifyWitnesses: this.verifyWitnesses, + verifyWitness: this.verifyWitness, height: this.currentBlockIndex, }; } @@ -255,16 +256,16 @@ export class Blockchain { return this.storage.blocks; } - public get nep5Balances() { - return this.storage.nep5Balances; + public get nep17Balances() { + return this.storage.nep17Balances; } - public get nep5TransfersReceived() { - return this.storage.nep5TransfersReceived; + public get nep17TransfersReceived() { + return this.storage.nep17TransfersReceived; } - public get nep5TransfersSent() { - return this.storage.nep5TransfersSent; + public get nep17TransfersSent() { + return this.storage.nep17TransfersSent; } public get applicationLogs() { @@ -275,10 +276,6 @@ export class Blockchain { return this.storage.transactions; } - public get contracts() { - return this.storage.contracts; - } - public get storages() { return this.storage.storages; } @@ -295,10 +292,6 @@ export class Blockchain { return this.storage.headerHashIndex; } - public get contractID() { - return this.storage.contractID; - } - // public get consensusState() { // return this.storage.consensusState; // } @@ -358,20 +351,11 @@ export class Blockchain { return VerifyResultModel.AlreadyExists; } - // TODO: to save some compute time we could keep a local cache of the current blocks return values - // from native contract calls and pass those in, instead of passing the whole native container. - const verifyOptions = { - native: this.native, - vm: this.vm, - storage: this.storage, - verifyWitnesses: this.verifyWitnesses, - }; - - return transaction.verify(verifyOptions, context); + return transaction.verify(this.verifyOptions, context); } public async verifyConsensusPayload(payload: ConsensusPayload) { - const verification = await payload.verify(this.verifyConsensusPayloadOptions); + const verification = await payload.verify(this.verifyOptions); if (!verification) { throw new ConsensusPayloadVerifyError(payload.hashHex); } @@ -442,7 +426,7 @@ export class Blockchain { } public async getValidators(): Promise { - return this.native.NEO.getValidators(this.storage); + return this.native.NEO.computeNextBlockValidators(this.storage); } public async getNextBlockValidators(): Promise { @@ -465,6 +449,10 @@ export class Blockchain { return this.native.Policy.getFeePerByte(this.storage); } + public shouldRefreshCommittee(offset = 0): boolean { + return (this.currentBlockIndex + offset) % this.settings.committeeMembersCount === 0; + } + public async persistBlock({ block, verify = false, @@ -493,7 +481,7 @@ export class Blockchain { } public async getVerificationCost(contractHash: UInt160, transaction: Transaction) { - const contract = await this.contracts.tryGet(contractHash); + const contract = await this.native.Management.getContract(this.storage, contractHash); if (contract === undefined) { return { fee: utils.ZERO, size: 0 }; } @@ -507,7 +495,6 @@ export class Blockchain { snapshot: 'clone', container: transaction, gas: common.ONE_HUNDRED_FIXED8, - testMode: true, }); } @@ -516,7 +503,6 @@ export class Blockchain { script, snapshot: 'main', container: signers, - gas: common.TEN_FIXED8, }); } @@ -526,8 +512,7 @@ export class Blockchain { container, persistingBlock, offset = 0, - testMode = false, - gas = new BN(0), + gas = common.TEN_FIXED8, }: RunEngineOptions): CallReceipt { return this.vm.withSnapshots(({ main, clone }) => { const handler = snapshot === 'main' ? main : clone; @@ -543,11 +528,9 @@ export class Blockchain { container, snapshot, gas, - testMode, }, (engine) => { - engine.loadScript(script); - engine.setInstructionPointer(offset); + engine.loadScript({ script, initialPosition: offset }); engine.execute(); return utils.getCallReceipt(engine, container); @@ -666,32 +649,32 @@ export class Blockchain { return [updateBlockHashIndex, updateHeaderHashIndex]; } - private updateNep5Balances({ + private updateNep17Balances({ applicationsExecuted, block, }: { readonly applicationsExecuted: readonly ApplicationExecuted[]; readonly block: Block; }) { - const { assetKeys, transfersSent, transfersReceived } = getNep5UpdateOptions({ + const { assetKeys, transfersSent, transfersReceived } = getNep17UpdateOptions({ applicationsExecuted, block, }); - const nep5BalancePairs = assetKeys.map((key) => { + const nep17BalancePairs = assetKeys.map((key) => { const script = new ScriptBuilder().emitAppCall(key.assetScriptHash, 'balanceOf', key.userScriptHash).build(); const callReceipt = this.invokeScript(script); const balanceBuffer = callReceipt.stack[0].getInteger().toBuffer(); - return { key, value: new Nep5Balance({ balanceBuffer, lastUpdatedBlock: this.currentBlockIndex }) }; + return { key, value: new Nep17Balance({ balanceBuffer, lastUpdatedBlock: this.currentBlockIndex }) }; }); - const nep5BalanceChangeSet: ChangeSet = nep5BalancePairs.map(({ key, value }) => { + const nep17BalanceChangeSet: ChangeSet = nep17BalancePairs.map(({ key, value }) => { if (value.balance.eqn(0)) { return { type: 'delete', change: { - type: 'nep5Balance', + type: 'nep17Balance', key, }, }; @@ -700,7 +683,7 @@ export class Blockchain { return { type: 'add', change: { - type: 'nep5Balance', + type: 'nep17Balance', key, value, }, @@ -708,27 +691,27 @@ export class Blockchain { }; }); - const nep5TransfersSentChangeSet: ChangeSet = transfersSent.map(({ key, value }) => ({ + const nep17TransfersSentChangeSet: ChangeSet = transfersSent.map(({ key, value }) => ({ type: 'add', subType: 'add', change: { - type: 'nep5TransferSent', + type: 'nep17TransferSent', key, value, }, })); - const nep5TransfersReceivedChangeSet: ChangeSet = transfersReceived.map(({ key, value }) => ({ + const nep17TransfersReceivedChangeSet: ChangeSet = transfersReceived.map(({ key, value }) => ({ type: 'add', subType: 'add', change: { - type: 'nep5TransferReceived', + type: 'nep17TransferReceived', key, value, }, })); - return nep5BalanceChangeSet.concat(nep5TransfersReceivedChangeSet, nep5TransfersSentChangeSet); + return nep17BalanceChangeSet.concat(nep17TransfersReceivedChangeSet, nep17TransfersSentChangeSet); } private updateApplicationLogs({ @@ -789,8 +772,8 @@ export class Blockchain { const blockMetadataBatch = this.updateBlockMetadata(block); await this.storage.commit(blockMetadataBatch); - const nep5Updates = this.updateNep5Balances({ applicationsExecuted, block }); - await this.storage.commit(nep5Updates); + const nep17Updates = this.updateNep17Balances({ applicationsExecuted, block }); + await this.storage.commit(nep17Updates); const applicationLogUpdates = this.updateApplicationLogs({ applicationsExecuted, block }); await this.storage.commit(applicationLogUpdates); @@ -801,6 +784,7 @@ export class Blockchain { private createPersistingBlockchain(): PersistingBlockchain { return new PersistingBlockchain({ onPersistNativeContractScript: this.onPersistNativeContractScript, + postPersistNativeContractScript: this.postPersistNativeContractScript, vm: this.vm, }); } diff --git a/packages/neo-one-node-blockchain/src/HeaderIndexCache.ts b/packages/neo-one-node-blockchain/src/HeaderIndexCache.ts index 67635bd4e6..aab6e69817 100644 --- a/packages/neo-one-node-blockchain/src/HeaderIndexCache.ts +++ b/packages/neo-one-node-blockchain/src/HeaderIndexCache.ts @@ -59,7 +59,7 @@ export class HeaderIndexCache { return this.getHeaderHashIndexFromCurrent(hashListHashIndex); } - const hashListIndex = (index - hashListHashIndex) / 2000; + const hashListIndex = index - hashListHashIndex; const headerHashList = await this.getHeaderHashList(hashListIndex); if (headerHashList === undefined) { return undefined; diff --git a/packages/neo-one-node-blockchain/src/PersistingBlockchain.ts b/packages/neo-one-node-blockchain/src/PersistingBlockchain.ts index e32139ec54..a8c345c51f 100644 --- a/packages/neo-one-node-blockchain/src/PersistingBlockchain.ts +++ b/packages/neo-one-node-blockchain/src/PersistingBlockchain.ts @@ -1,21 +1,24 @@ // tslint:disable no-array-mutation no-object-mutation -import { TriggerType, VMState } from '@neo-one/client-common'; +import { common, TriggerType, VMState } from '@neo-one/client-common'; import { ApplicationExecuted, Block, SnapshotHandler, Transaction, VM } from '@neo-one/node-core'; -import { PersistNativeContractsError } from './errors'; -import { utils, utils as blockchainUtils } from './utils'; +import { PersistNativeContractsError, PostPersistError } from './errors'; +import { utils } from './utils'; interface PersistingBlockchainOptions { readonly vm: VM; readonly onPersistNativeContractScript: Buffer; + readonly postPersistNativeContractScript: Buffer; } export class PersistingBlockchain { private readonly vm: VM; private readonly onPersistNativeContractScript: Buffer; + private readonly postPersistNativeContractScript: Buffer; public constructor(options: PersistingBlockchainOptions) { this.vm = options.vm; this.onPersistNativeContractScript = options.onPersistNativeContractScript; + this.postPersistNativeContractScript = options.postPersistNativeContractScript; } public persistBlock( @@ -31,34 +34,38 @@ export class PersistingBlockchain { const appsExecuted: ApplicationExecuted[] = []; main.setPersistingBlock(block); - if (block.index > 0) { - const executed = this.vm.withApplicationEngine( - { - trigger: TriggerType.System, - snapshot: 'main', - gas: utils.ZERO, - testMode: true, - }, - (engine) => { - engine.loadScript(this.onPersistNativeContractScript); - const result = engine.execute(); - if (result !== VMState.HALT) { - throw new PersistNativeContractsError(); - } - - return blockchainUtils.getApplicationExecuted(engine); - }, - ); - - appsExecuted.push(executed); - } + const executed = this.vm.withApplicationEngine( + { + trigger: TriggerType.OnPersist, + snapshot: 'main', + gas: common.TWENTY_FIXED8, + }, + (engine) => { + engine.loadScript({ script: this.onPersistNativeContractScript }); + const result = engine.execute(); + if (result !== VMState.HALT) { + throw new PersistNativeContractsError(); + } + + return utils.getApplicationExecuted(engine); + }, + ); + + appsExecuted.push(executed); main.addBlock(block); main.clone(); const executedTransactions = this.persistTransactions(block, main, clone); - return { changeBatch: main.getChangeSet(), applicationsExecuted: appsExecuted.concat(executedTransactions) }; + main.changeBlockHashIndex(block.index, block.hash); + + const postPersistExecuted = this.postPersist(); + + return { + changeBatch: main.getChangeSet(), + applicationsExecuted: appsExecuted.concat(executedTransactions).concat(postPersistExecuted), + }; }); } @@ -89,10 +96,9 @@ export class PersistingBlockchain { container: transaction, snapshot: 'clone', gas: transaction.systemFee, - testMode: false, }, (engine) => { - engine.loadScript(transaction.script); + engine.loadScript({ script: transaction.script }); const state = engine.execute(); if (state === VMState.HALT) { clone.deleteTransaction(transaction.hash); @@ -102,7 +108,27 @@ export class PersistingBlockchain { main.clone(); } - return blockchainUtils.getApplicationExecuted(engine, transaction); + return utils.getApplicationExecuted(engine, transaction); + }, + ); + } + + private postPersist(): ApplicationExecuted { + return this.vm.withApplicationEngine( + { + trigger: TriggerType.PostPersist, + container: undefined, + gas: common.TWENTY_FIXED8, + snapshot: 'main', + }, + (engine) => { + engine.loadScript({ script: this.postPersistNativeContractScript }); + const result = engine.execute(); + if (result !== VMState.HALT) { + throw new PostPersistError(); + } + + return utils.getApplicationExecuted(engine); }, ); } diff --git a/packages/neo-one-node-blockchain/src/StorageCache.ts b/packages/neo-one-node-blockchain/src/StorageCache.ts index 5bbb482069..893cc8e818 100644 --- a/packages/neo-one-node-blockchain/src/StorageCache.ts +++ b/packages/neo-one-node-blockchain/src/StorageCache.ts @@ -1,4 +1,4 @@ -import { AddChange, Change, ChangeSet, DeleteChange, ReadMetadataStorage, ReadStorage } from '@neo-one/node-core'; +import { AddChange, Change, ChangeSet, DeleteChange, ReadStorage } from '@neo-one/node-core'; interface DeleteMetadataTrackedChanged { readonly type: 'delete'; @@ -16,10 +16,6 @@ interface AddTrackedChange extends AddMetadataTrackedChange { readonly key: Key; } -type TrackedMetadataChange = Value extends undefined - ? DeleteMetadataTrackedChanged - : AddMetadataTrackedChange; - type TrackedChange = DeleteTrackedChange | AddTrackedChange; const isDeleteTrackedChange = (value: TrackedChange): value is DeleteTrackedChange => diff --git a/packages/neo-one-node-blockchain/src/__tests__/headerIndexCache.test.ts b/packages/neo-one-node-blockchain/src/__tests__/headerIndexCache.test.ts new file mode 100644 index 0000000000..20535bdfdb --- /dev/null +++ b/packages/neo-one-node-blockchain/src/__tests__/headerIndexCache.test.ts @@ -0,0 +1,37 @@ +// tslint:disable: readonly-keyword no-object-mutation +import { crypto } from '@neo-one/client-common'; +import { Storage } from '@neo-one/node-core'; +import { storage as levelStorage } from '@neo-one/node-storage-levelup'; +import { randomBytes } from 'crypto'; +import LevelUp from 'levelup'; +import _ from 'lodash'; +import MemDown from 'memdown'; +import { HeaderIndexCache } from '../HeaderIndexCache'; + +const getUInt = () => crypto.hash256(randomBytes(32)); +const getUInts = (n: number) => _.range(n).map(getUInt); + +describe('headerIndexCache tests', () => { + const context = { messageMagic: 1951352142, validatorsCount: 7 }; + + let storage: Storage; + let db: any; + beforeEach(() => { + db = new LevelUp(new MemDown()); + storage = levelStorage({ db, context }); + }); + + test('headerCacheIndex push 2500 hashes -- retrieves something from different headerHashLists', async () => { + const cache = new HeaderIndexCache({ storage, initCurrentHeaderHashes: [], initStorageCount: 0 }); + const testHashes = getUInts(2500); + // tslint:disable-next-line: no-loop-statement + for (const hash of testHashes) { + await cache.push(hash); + } + + const [listZeroHash, listTwoThousandHash] = await Promise.all([cache.get(11), cache.get(2005)]); + + expect(listZeroHash.equals(testHashes[11])).toEqual(true); + expect(listTwoThousandHash.equals(testHashes[2005])).toEqual(true); + }); +}); diff --git a/packages/neo-one-node-blockchain/src/errors.ts b/packages/neo-one-node-blockchain/src/errors.ts index 2a4b1f6544..9d0f8c3884 100644 --- a/packages/neo-one-node-blockchain/src/errors.ts +++ b/packages/neo-one-node-blockchain/src/errors.ts @@ -28,6 +28,10 @@ export const PersistNativeContractsError = makeErrorWithCode( 'PERSIST_NATIVE_CONTRACTS_FAILED', () => 'Engine state !== HALT when persisting native contract scripts', ); +export const PostPersistError = makeErrorWithCode( + 'POST_PERSIST_SCRIPT_FAILED', + () => 'Engine state !== HALT when running post persist scripts', +); export const ContractStateFetchError = makeErrorWithCode( 'FETCH_CONTRACT_FAILED', (hash: string) => `failed to fetch contract state with hash: ${hash} from storage.`, diff --git a/packages/neo-one-node-blockchain/src/getNep5UpdateOptions.ts b/packages/neo-one-node-blockchain/src/getNep17UpdateOptions.ts similarity index 70% rename from packages/neo-one-node-blockchain/src/getNep5UpdateOptions.ts rename to packages/neo-one-node-blockchain/src/getNep17UpdateOptions.ts index ba2ce55b4f..caa21f5c62 100644 --- a/packages/neo-one-node-blockchain/src/getNep5UpdateOptions.ts +++ b/packages/neo-one-node-blockchain/src/getNep17UpdateOptions.ts @@ -4,35 +4,35 @@ import { Block, isByteStringStackItem, isIntegerStackItem, - Nep5BalanceKey, - Nep5Transfer, - Nep5TransferKey, + Nep17BalanceKey, + Nep17Transfer, + Nep17TransferKey, } from '@neo-one/node-core'; import { utils } from './utils'; -export interface Nep5TransferReturn { - readonly key: Nep5TransferKey; - readonly value: Nep5Transfer; +export interface Nep17TransferReturn { + readonly key: Nep17TransferKey; + readonly value: Nep17Transfer; } -export interface Nep5UpdateOptions { - readonly assetKeys: readonly Nep5BalanceKey[]; - readonly transfersSent: readonly Nep5TransferReturn[]; - readonly transfersReceived: readonly Nep5TransferReturn[]; +export interface Nep17UpdateOptions { + readonly assetKeys: readonly Nep17BalanceKey[]; + readonly transfersSent: readonly Nep17TransferReturn[]; + readonly transfersReceived: readonly Nep17TransferReturn[]; } -export function getNep5UpdateOptions({ +export function getNep17UpdateOptions({ applicationsExecuted, block, }: { readonly applicationsExecuted: readonly ApplicationExecuted[]; readonly block: Block; -}): Nep5UpdateOptions { +}): Nep17UpdateOptions { let transferIndex = 0; - const nep5BalancesChanged = new Set(); - const mutableNep5BalancesChangedKeys: Nep5BalanceKey[] = []; - const mutableTransfersSent: Nep5TransferReturn[] = []; - const mutableTransfersReceived: Nep5TransferReturn[] = []; + const nep17BalancesChanged = new Set(); + const mutableNep17BalancesChangedKeys: Nep17BalanceKey[] = []; + const mutableTransfersSent: Nep17TransferReturn[] = []; + const mutableTransfersReceived: Nep17TransferReturn[] = []; applicationsExecuted.forEach((appExecuted) => { if (appExecuted.state === VMState.FAULT) { @@ -77,26 +77,26 @@ export function getNep5UpdateOptions({ if (fromBytes !== undefined) { from = common.bufferToUInt160(fromBytes); - const fromKey = new Nep5BalanceKey({ userScriptHash: from, assetScriptHash: scriptHash }); + const fromKey = new Nep17BalanceKey({ userScriptHash: from, assetScriptHash: scriptHash }); const fromKeyString = fromKey.serializeWire().toString('hex'); // No need to check address balance for every single transfer. Just need to check new balances for // addresses that we know have transferred assets - // mutableNep5BalancesChangedKeys will be used to run scripts to check storage for new balance of each address + // mutableNep17BalancesChangedKeys will be used to run scripts to check storage for new balance of each address // whose balance we know has changed based on the transfers we know happened - if (!nep5BalancesChanged.has(fromKeyString)) { - nep5BalancesChanged.add(fromKeyString); - mutableNep5BalancesChangedKeys.push(fromKey); + if (!nep17BalancesChanged.has(fromKeyString)) { + nep17BalancesChanged.add(fromKeyString); + mutableNep17BalancesChangedKeys.push(fromKey); } } if (toBytes !== undefined) { to = common.bufferToUInt160(toBytes); - const toKey = new Nep5BalanceKey({ userScriptHash: to, assetScriptHash: scriptHash }); + const toKey = new Nep17BalanceKey({ userScriptHash: to, assetScriptHash: scriptHash }); const toKeyString = toKey.serializeWire().toString('hex'); - if (!nep5BalancesChanged.has(toKeyString)) { - nep5BalancesChanged.add(toKeyString); - mutableNep5BalancesChangedKeys.push(toKey); + if (!nep17BalancesChanged.has(toKeyString)) { + nep17BalancesChanged.add(toKeyString); + mutableNep17BalancesChangedKeys.push(toKey); } } @@ -106,13 +106,13 @@ export function getNep5UpdateOptions({ if (!from.equals(common.ZERO_UINT160)) { mutableTransfersSent.push({ - key: new Nep5TransferKey({ + key: new Nep17TransferKey({ userScriptHash: from, timestampMS: block.timestamp, assetScriptHash: scriptHash, blockTransferNotificationIndex: transferIndex, }), - value: new Nep5Transfer({ + value: new Nep17Transfer({ userScriptHash: to, txHash, blockIndex: block.index, @@ -123,13 +123,13 @@ export function getNep5UpdateOptions({ if (!to.equals(common.ZERO_UINT160)) { mutableTransfersReceived.push({ - key: new Nep5TransferKey({ + key: new Nep17TransferKey({ userScriptHash: to, timestampMS: block.timestamp, assetScriptHash: scriptHash, blockTransferNotificationIndex: transferIndex, }), - value: new Nep5Transfer({ + value: new Nep17Transfer({ userScriptHash: from, txHash, blockIndex: block.index, @@ -144,7 +144,7 @@ export function getNep5UpdateOptions({ }); return { - assetKeys: mutableNep5BalancesChangedKeys, + assetKeys: mutableNep17BalancesChangedKeys, transfersSent: mutableTransfersSent, transfersReceived: mutableTransfersReceived, }; diff --git a/packages/neo-one-node-blockchain/src/utils.ts b/packages/neo-one-node-blockchain/src/utils.ts index a789c885d2..bf06e6a596 100644 --- a/packages/neo-one-node-blockchain/src/utils.ts +++ b/packages/neo-one-node-blockchain/src/utils.ts @@ -10,20 +10,22 @@ import { Verifiable, VM, } from '@neo-one/node-core'; -import { BN } from 'bn.js'; import { ScriptVerifyError } from './errors'; const hashListBatchSize = 2000; const getOnPersistNativeContractScript = coreUtils.lazy(() => { - const hashes = [common.nativeHashes.GAS, common.nativeHashes.NEO]; - const script = new ScriptBuilder(); - hashes.forEach((hash) => { - script.emitAppCall(hash, 'onPersist'); - script.emitOp('DROP'); - }); - - return script.build(); + const builder = new ScriptBuilder(); + builder.emitSysCall('System.Contract.NativeOnPersist'); + + return builder.build(); +}); + +const getPostPersistNativeContractScript = coreUtils.lazy(() => { + const builder = new ScriptBuilder(); + builder.emitSysCall('System.Contract.NativePostPersist'); + + return builder.build(); }); // tslint:disable-next-line: no-any @@ -48,36 +50,32 @@ const getCallReceipt = (engine: ApplicationEngine, container?: Verifiable) => ({ }); const verifyContract = async (contract: ContractState, vm: VM, transaction: Transaction) => { - const verify = contract.manifest.abi.getMethod('verify'); - if (verify === undefined) { - throw new InvalidFormatError(`the smart contract ${contract.scriptHash} does not have a verify method`); - } - - const init = contract.manifest.abi.getMethod('_initialize'); const gas = vm.withApplicationEngine( { trigger: TriggerType.Verification, container: transaction, snapshot: 'clone', - gas: new BN(0), - testMode: true, + gas: common.TWENTY_FIXED8, }, (engine) => { - engine.loadScript(contract.script, CallFlags.None); - engine.setInstructionPointer(verify.offset); - if (init !== undefined) { - engine.setInstructionPointer(init.offset); + const loaded = engine.loadContract({ + hash: contract.hash, + flags: CallFlags.None, + method: 'verify', + packParameters: true, + }); + + if (!loaded) { + throw new InvalidFormatError(`contract with hash: ${contract.hash} does not have a verify method.`); } - engine.loadScript(Buffer.from([]), CallFlags.None); const result = engine.execute(); - if (result === VMState.FAULT) { - throw new ScriptVerifyError(`contract ${contract.scriptHash} returned FAULT state`); + throw new ScriptVerifyError(`contract ${contract.hash} returned FAULT state`); } if (engine.resultStack.length !== 1 || !engine.resultStack[0].getBoolean()) { - throw new ScriptVerifyError(`contract ${contract.scriptHash} returns false`); + throw new ScriptVerifyError(`contract ${contract.hash} returns false`); } return engine.gasConsumed; @@ -105,6 +103,7 @@ export const utils = { getApplicationExecuted, getCallReceipt, getOnPersistNativeContractScript, + getPostPersistNativeContractScript, verifyContract, blockComparator, isTransaction, diff --git a/packages/neo-one-node-blockchain/src/verify.ts b/packages/neo-one-node-blockchain/src/verify.ts index f460561cbc..d3ab3b7a74 100644 --- a/packages/neo-one-node-blockchain/src/verify.ts +++ b/packages/neo-one-node-blockchain/src/verify.ts @@ -1,127 +1,27 @@ -import { common, TriggerType, UInt160, VMState } from '@neo-one/client-common'; +import { crypto, TriggerType, UInt160, VMState } from '@neo-one/client-common'; import { - BlockchainStorage, CallFlags, - ContractMethodDescriptor, + ContractState, ExecuteScriptResult, - NativeContainer, - SerializableContainer, - Verifiable, - VM, + maxVerificationGas, + VerifyWitnessesOptions, + VerifyWitnessOptions, } from '@neo-one/node-core'; import { BN } from 'bn.js'; -import { ContractMethodError, ContractStateFetchError, WitnessVerifyError } from './errors'; - -const maxVerificationGas = common.fixed8FromDecimal('0.5'); - -interface ApplicationEngineVerifyOptions { - readonly verification: Buffer; - readonly offset: number; - readonly init?: ContractMethodDescriptor; -} - -const getApplicationEngineVerifyOptions = async ( - verification: Buffer, - storage: BlockchainStorage, - hash: UInt160, - scriptHash: UInt160, -): Promise => { - if (verification.length === 0) { - const contractState = await storage.contracts.tryGet(hash); - if (contractState === undefined) { - throw new ContractStateFetchError(common.uInt160ToHex(hash)); - } - const methodDescriptor = contractState.manifest.abi.getMethod('verify'); - if (methodDescriptor === undefined) { - throw new ContractMethodError('verify', common.uInt160ToHex(hash)); - } - - return { - verification: contractState.script, - offset: methodDescriptor.offset, - init: contractState.manifest.abi.getMethod('_initialize'), - }; - } - - // tslint:disable-next-line: possible-timing-attack TODO: look into this `possible-timing-attack` warning - if (!hash.equals(scriptHash)) { - throw new WitnessVerifyError(); - } - - return { - verification, - offset: 0, - }; -}; - -export const verifyWithApplicationEngine = ( - vm: VM, - verifiable: Verifiable & SerializableContainer, - verification: Buffer, - index: number, - gas: BN, - offset: number, - init?: ContractMethodDescriptor, -): ExecuteScriptResult => - vm.withApplicationEngine( - { trigger: TriggerType.Verification, container: verifiable, snapshot: 'clone', gas, testMode: false }, - (engine) => { - engine.loadScript(verification, CallFlags.None); - engine.setInstructionPointer(offset); - if (init !== undefined) { - engine.setInstructionPointer(init.offset); - } - engine.loadScript(verifiable.witnesses[index].invocation, CallFlags.None); - - const state = engine.execute(); - if (state === VMState.FAULT) { - return { result: false, gas }; - } - - const stack = engine.resultStack; - if (stack.length !== 1 || !stack[0].getBoolean()) { - return { result: false, gas }; - } - - return { result: true, gas: gas.sub(engine.gasConsumed) }; - }, - ); - -export const tryVerifyHash = async ( - vm: VM, - hash: UInt160, - index: number, - storage: BlockchainStorage, - verifiable: Verifiable & SerializableContainer, - gas: BN, -): Promise => { - const { verification: verificationScript, scriptHash } = verifiable.witnesses[index]; - try { - const { verification, offset, init } = await getApplicationEngineVerifyOptions( - verificationScript, - storage, - hash, - scriptHash, - ); - - return verifyWithApplicationEngine(vm, verifiable, verification, index, gas, offset, init); - } catch { - return { gas, result: false }; - } -}; -export const verifyWitnesses = async ( - vm: VM, - verifiable: Verifiable & SerializableContainer, - storage: BlockchainStorage, - native: NativeContainer, - gasIn: BN, -): Promise => { +export const verifyWitnesses = async ({ + vm, + verifiable, + storage, + native, + gas: gasIn, + snapshot, +}: VerifyWitnessesOptions): Promise => { if (gasIn.ltn(0)) { return false; } - const gas = gasIn.gt(maxVerificationGas) ? maxVerificationGas : gasIn; + let gas = gasIn.gt(maxVerificationGas) ? maxVerificationGas : gasIn; let hashes: readonly UInt160[]; try { @@ -134,28 +34,97 @@ export const verifyWitnesses = async ( return false; } - const { next, previous } = await hashes - .slice(1) - .reduce; readonly previous: boolean }>>( - async (acc, hash, index) => { - const { next: accNext, previous: accPrevious } = await acc; - const { result, gas: newGas } = await accNext; - if (!result) { - return { - next: Promise.resolve({ result: false, gas: newGas }), - previous: false, - }; + // tslint:disable-next-line: no-loop-statement + for (let i = 0; i < hashes.length; i += 1) { + const { result, gas: gasCost } = await verifyWitness({ + vm, + verifiable, + storage, + native, + hash: hashes[i], + snapshot, + witness: verifiable.witnesses[i], + gas, + }); + + if (!result) { + return false; + } + + gas = gas.sub(gasCost); + } + + return true; +}; + +export const verifyWitness = async ({ + vm, + verifiable, + snapshot: snapshotIn, + storage, + native, + hash, + witness, + gas, +}: VerifyWitnessOptions): Promise => { + const initFee = new BN(0); + const { verification, invocation } = witness; + const callFlags = !crypto.isStandardContract(verification) ? CallFlags.ReadStates : CallFlags.None; + + let contract: ContractState | undefined; + if (verification.length === 0) { + contract = await native.Management.getContract(storage, hash); + if (contract === undefined) { + return { result: false, gas: initFee }; + } + } + + const snapshot = snapshotIn ?? 'clone'; + vm.withSnapshots(({ main }) => { + if (snapshotIn === 'clone') { + main.clone(); + } + }); + + return vm.withApplicationEngine( + { + trigger: TriggerType.Verification, + container: verifiable, + snapshot, + gas, + }, + async (engine) => { + if (contract !== undefined) { + const loadContractResult = engine.loadContract({ + hash, + method: 'verify', + flags: callFlags, + packParameters: true, + }); + if (!loadContractResult) { + return { result: false, gas: initFee }; + } + } else { + // tslint:disable-next-line: possible-timing-attack + if (native.isNative(hash) || hash !== witness.scriptHash) { + return { result: false, gas: initFee }; } - return { - next: tryVerifyHash(vm, hash, index, storage, verifiable, newGas), - previous: accPrevious && result, - }; - }, - Promise.resolve({ next: tryVerifyHash(vm, hashes[0], 0, storage, verifiable, gas), previous: true }), - ); + engine.loadScript({ script: verification, flags: callFlags, scriptHash: hash, initialPosition: 0 }); + } - const { result: finalResult } = await next; + engine.loadScript({ script: invocation, flags: CallFlags.None }); + const result = engine.execute(); - return finalResult && previous; + if (result === VMState.FAULT) { + return { result: false, gas: initFee }; + } + + if (engine.resultStack.length !== 1 || !engine.resultStack[0].getBoolean()) { + return { result: false, gas: initFee }; + } + + return { result: true, gas: engine.gasConsumed }; + }, + ); }; diff --git a/packages/neo-one-node-consensus/src/Consensus.ts b/packages/neo-one-node-consensus/src/Consensus.ts index 894cb88321..2b73bba8b8 100644 --- a/packages/neo-one-node-consensus/src/Consensus.ts +++ b/packages/neo-one-node-consensus/src/Consensus.ts @@ -27,8 +27,6 @@ export interface InternalOptions { readonly privateNet: boolean; } -const MS_IN_SECOND = 1000; - export class Consensus { private mutableQueue: ConsensusQueue; private mutableTimer: number | undefined; diff --git a/packages/neo-one-node-consensus/src/__tests__/consensus/handleConsensusPayload.test.ts b/packages/neo-one-node-consensus/src/__tests__/consensus/handleConsensusPayload.test.ts index 815c289ff1..64fbfc58b5 100644 --- a/packages/neo-one-node-consensus/src/__tests__/consensus/handleConsensusPayload.test.ts +++ b/packages/neo-one-node-consensus/src/__tests__/consensus/handleConsensusPayload.test.ts @@ -1,12 +1,11 @@ // wallaby.skip -import { common, crypto, Op, PrivateKey, ScriptBuilder, UInt256Hex, WitnessScopeModel } from '@neo-one/client-common'; +import { common, crypto, Op, ScriptBuilder, UInt256Hex, WitnessScopeModel } from '@neo-one/client-common'; import { Block, ChangeViewConsensusMessage, ChangeViewReason, ConsensusContext, ConsensusData, - Contract, Signer, Transaction, TransactionVerificationContext, @@ -45,6 +44,7 @@ describe('handleConsensusPayload', () => { knownHashes = new Set(); }); + // TODO: fixup test('updates the expected view on new view number', async () => { const payload = await makeSignedPayload({ node, @@ -77,17 +77,13 @@ describe('handleConsensusPayload', () => { timerContext, }); - - console.log(result); }); + // TODO: fixup test.only('settings help', () => { const privateKeyString = 'e35fa5d1652c4c65e296c86e63a3da6939bc471b741845be636e2daa320dc770'; const privateKey = common.stringToPrivateKey(privateKeyString); const publicKey = crypto.privateKeyToPublicKey(privateKey); - const publicKeyString = common.ecPointToHex(publicKey); - - // console.log({ privateKey: privateKeyString, publicKey: publicKeyString }); const standbyValidators = [publicKey]; @@ -98,27 +94,6 @@ describe('handleConsensusPayload', () => { verification: Buffer.from([Op.PUSH1]), }); - const scriptBuilder = new ScriptBuilder(); - scriptBuilder.emitSysCall('Neo.Native.Deploy'); - const script = scriptBuilder.build(); - - const deployTransaction = new Transaction({ - version: 0, - script, - systemFee: new BN(0), - networkFee: new BN(0), - signers: [ - new Signer({ - account: crypto.hash160(Buffer.from([Op.PUSH1])), - scopes: WitnessScopeModel.None, - }), - ], - attributes: [], - witnesses: [deployWitness], - validUntilBlock: 0, - messageMagic: 7630401, - }); - const consensusData = new ConsensusData({ primaryIndex: 0, nonce: new BN(2083236893), @@ -131,14 +106,8 @@ describe('handleConsensusPayload', () => { nextConsensus: consensusAddress, witness: deployWitness, consensusData, - transactions: [deployTransaction], + transactions: [], messageMagic: 7630401, }); - - console.log(deployTransaction.hash); - console.log(consensusData); - - const serializedBlock = genesisBlock.serializeWire(); - console.log(serializedBlock.toString('hex')); }); }); diff --git a/packages/neo-one-node-consensus/src/context/common.ts b/packages/neo-one-node-consensus/src/context/common.ts index 195722e46c..72727e6015 100644 --- a/packages/neo-one-node-consensus/src/context/common.ts +++ b/packages/neo-one-node-consensus/src/context/common.ts @@ -197,6 +197,21 @@ export const ensureMaxBlockLimitation = async ( }; }; +const validatorsChanged = (blockchain: Blockchain) => { + if (blockchain.currentBlockIndex === 0) { + return false; + } + + const currentBlock = blockchain.currentBlock; + const prevBlock = blockchain.previousBlock; + + if (prevBlock === undefined) { + throw new Error('expected previous block'); + } + + return !currentBlock.nextConsensus.equals(prevBlock.nextConsensus); +}; + export const reset = async ({ blockchain, privateKey, @@ -217,7 +232,7 @@ export const reset = async ({ const initialBlockOptions = { previousHash: blockchain.currentBlock.hash, index: blockchain.currentBlockIndex + 1, - nextConsensus: crypto.getConsensusAddress(validators), + nextConsensus: crypto.getConsensusAddress(blockchain.shouldRefreshCommittee(1) ? validators : nextValidators), merkleRoot: undefined, messageMagic: blockchain.settings.messageMagic, }; @@ -239,9 +254,24 @@ export const reset = async ({ const changeViewPayloads = _.range(nextValidators.length).map(() => undefined); const lastChangeViewPayloads = _.range(nextValidators.length).map(() => undefined); const commitPayloads = _.range(nextValidators.length).map(() => undefined); - let lastSeenMessage = context.lastSeenMessage; - if (context.lastSeenMessage.length === 0) { - lastSeenMessage = _.range(nextValidators.length).map(() => -1); + const prevLastSeenMessage = context.lastSeenMessage; + let lastSeenMessage = prevLastSeenMessage; + if (validatorsChanged(blockchain) || Object.values(context.lastSeenMessage).length === 0) { + lastSeenMessage = nextValidators.reduce<{ readonly [k: string]: number | undefined }>((acc, validator) => { + const validatorHex = common.ecPointToHex(validator); + const prevIndex = prevLastSeenMessage[validatorHex]; + if (prevIndex !== undefined) { + return { + ...acc, + validatorHex: prevIndex, + }; + } + + return { + ...acc, + validatorHex: blockchain.currentBlockIndex, + }; + }, {}); } const publicKey = crypto.privateKeyToPublicKey(privateKey); @@ -260,55 +290,52 @@ export const reset = async ({ }), }; } - { - const mutableLastChangeViewPayloads = [...context.lastChangeViewPayloads]; - _.range(mutableLastChangeViewPayloads.length).forEach((i) => { - // tslint:disable-next-line: prefer-conditional-expression - if ( - (context.changeViewPayloads[i]?.getDeserializedMessage().newViewNumber ?? -1) >= - viewNumber - ) { - mutableLastChangeViewPayloads[i] = context.changeViewPayloads[i]; - } else { - mutableLastChangeViewPayloads[i] = undefined; - } - }); - - const primaryIndex = getPrimaryIndex({ context, viewNumber }); - const newBlockOptions = { - consensusData: { - primaryIndex, - }, - merkleRoot: undefined, - timestamp: new BN(0), - transactions: undefined, - }; + const mutableLastChangeViewPayloads = [...context.lastChangeViewPayloads]; + _.range(mutableLastChangeViewPayloads.length).forEach((i) => { + // tslint:disable-next-line: prefer-conditional-expression + if ( + (context.changeViewPayloads[i]?.getDeserializedMessage().newViewNumber ?? -1) >= + viewNumber + ) { + mutableLastChangeViewPayloads[i] = context.changeViewPayloads[i]; + } else { + mutableLastChangeViewPayloads[i] = undefined; + } + }); - const preparationPayloads = _.range(context.validators.length).map(() => undefined); + const primaryIndex = getPrimaryIndex({ context, viewNumber }); + const newBlockOptions = { + consensusData: { + primaryIndex, + }, + merkleRoot: undefined, + timestamp: new BN(0), + transactions: undefined, + }; - const mutableLastSeenMessage = [...context.lastSeenMessage]; - if (context.myIndex >= 0) { - mutableLastSeenMessage[context.myIndex] = utils.nullthrows(context.blockBuilder?.index); - } + const preparationPayloads = _.range(context.validators.length).map(() => undefined); - return { - context: context.clone({ - viewNumber, - lastChangeViewPayloads: mutableLastChangeViewPayloads, - blockOptions: newBlockOptions, - transactionHashes: undefined, - preparationPayloads, - lastSeenMessage: mutableLastSeenMessage, - }), - }; + const mutableLastSeenMessage = { ...context.lastSeenMessage }; + if (context.myIndex >= 0) { + mutableLastSeenMessage[context.myIndex] = utils.nullthrows(context.blockBuilder?.index); } + + return { + context: context.clone({ + viewNumber, + lastChangeViewPayloads: mutableLastChangeViewPayloads, + blockOptions: newBlockOptions, + transactionHashes: undefined, + preparationPayloads, + lastSeenMessage: mutableLastSeenMessage, + }), + }; }; -// TODO: not convinced we need to reload this that often -export const saveContext = async (context: ConsensusContext) => {}; +export const saveContext = async (_context: ConsensusContext) => { + throw new Error('not implemented'); +}; -// TODO: pickup here, everything SHOULD be set up correctly, now we just need to -// sort out how it all goes together and then starts export const getInitialContext = ({ blockchain, publicKey, diff --git a/packages/neo-one-node-core/src/BlockBase.ts b/packages/neo-one-node-core/src/BlockBase.ts index 94a1ab7f97..96ce026760 100644 --- a/packages/neo-one-node-core/src/BlockBase.ts +++ b/packages/neo-one-node-core/src/BlockBase.ts @@ -13,7 +13,7 @@ import { } from '@neo-one/client-common'; import { BN } from 'bn.js'; import { Equals, EquatableKey } from './Equatable'; -import { UnsignedBlockError } from './errors'; +import { InvalidPreviousHeaderError, UnsignedBlockError } from './errors'; import { createSerializeWire, DeserializeWireBaseOptions, @@ -47,14 +47,18 @@ export const getBlockScriptHashesForVerifying = async ( storage: BlockchainStorage, ) => { if (block.previousHash === common.ZERO_UINT256) { - return [block.witness?.scriptHash]; + const witnessHash = block.witness?.scriptHash; + if (witnessHash === undefined) { + throw new InvalidFormatError('Always expect a witness on the genesis block'); + } + + return [witnessHash]; } const prevBlock = await storage.blocks.tryGet({ hashOrIndex: block.previousHash }); const prevHeader = prevBlock?.header; if (prevHeader === undefined) { - // TODO: implement this as a makeError InvalidStorageHeaderFetch - throw new Error(`previous header hash ${block.previousHash} did not return a valid Header from storage`); + throw new InvalidPreviousHeaderError(common.uInt256ToHex(block.previousHash)); } return [prevHeader.nextConsensus]; @@ -113,20 +117,7 @@ export abstract class BlockBase implements EquatableKey, SerializableContainer, public readonly nextConsensus: UInt160; public readonly messageMagic: number; public readonly getScriptHashesForVerifying = utils.lazyAsync( - async (context: { readonly storage: BlockchainStorage }) => { - if (this.previousHash === common.ZERO_UINT256) { - return [this.witness.scriptHash]; - } - - const prevBlock = await context.storage.blocks.tryGet({ hashOrIndex: this.previousHash }); - const prevHeader = prevBlock?.header; - if (prevHeader === undefined) { - // TODO: implement this as a makeError InvalidStorageHeaderFetch - throw new Error(`previous header hash ${this.previousHash} did not return a valid Header from storage`); - } - - return [prevHeader.nextConsensus]; - }, + async (context: { readonly storage: BlockchainStorage }) => getBlockScriptHashesForVerifying(this, context.storage), ); // tslint:disable-next-line no-any public readonly equals: Equals = utils.equals(this.constructor as any, this, (other: any) => @@ -261,7 +252,13 @@ export abstract class BlockBase implements EquatableKey, SerializableContainer, return false; } - return verifyWitnesses(vm, this, storage, native, utils.ONE); + return verifyWitnesses({ + vm, + verifiable: this, + storage, + native, + gas: utils.ONE, + }); } protected readonly sizeExclusive: () => number = () => 0; diff --git a/packages/neo-one-node-core/src/Blockchain.ts b/packages/neo-one-node-core/src/Blockchain.ts index cc3ff5fefb..38e8f5e4f4 100644 --- a/packages/neo-one-node-core/src/Blockchain.ts +++ b/packages/neo-one-node-core/src/Blockchain.ts @@ -53,6 +53,7 @@ export interface Blockchain extends BlockchainStorage { readonly getMaxBlockSystemFee: () => Promise; readonly getMaxTransactionsPerBlock: () => Promise; readonly getFeePerByte: () => Promise; + readonly shouldRefreshCommittee: (offset?: number) => boolean; readonly invokeScript: (script: Buffer, signers?: Signers) => CallReceipt; readonly testTransaction: (transaction: Transaction) => CallReceipt; diff --git a/packages/neo-one-node-core/src/CallFlags.ts b/packages/neo-one-node-core/src/CallFlags.ts index f372b4cc7f..47c7fd88a5 100644 --- a/packages/neo-one-node-core/src/CallFlags.ts +++ b/packages/neo-one-node-core/src/CallFlags.ts @@ -1,11 +1,10 @@ export enum CallFlags { None = 0, - AllowStates = 0b00000001, - AllowModifyStates = 0b00000010, + ReadStates = 0b00000001, + WriteStates = 0b00000010, AllowCall = 0b00000100, AllowNotify = 0b00001000, - // tslint:disable-next-line: no-bitwise - ReadOnly = AllowStates | AllowCall | AllowNotify, - // tslint:disable-next-line: no-bitwise - All = AllowStates | AllowModifyStates | AllowCall | AllowNotify, + States = ReadStates | WriteStates, + ReadOnly = ReadStates | AllowCall, + All = States | AllowCall | AllowNotify, } diff --git a/packages/neo-one-node-core/src/ConsensusData.ts b/packages/neo-one-node-core/src/ConsensusData.ts index 0f04065c41..30815dcd0b 100644 --- a/packages/neo-one-node-core/src/ConsensusData.ts +++ b/packages/neo-one-node-core/src/ConsensusData.ts @@ -1,6 +1,5 @@ import { BinaryWriter, ConsensusDataJSON, crypto, IOHelper, JSONHelper, UInt256 } from '@neo-one/client-common'; import { BN } from 'bn.js'; -import { DEFAULT_VALIDATORS_COUNT } from './constants'; import { createSerializeWire, DeserializeWireBaseOptions, diff --git a/packages/neo-one-node-core/src/ContractState.ts b/packages/neo-one-node-core/src/ContractState.ts index d01606648e..c496c0c73e 100644 --- a/packages/neo-one-node-core/src/ContractState.ts +++ b/packages/neo-one-node-core/src/ContractState.ts @@ -1,19 +1,13 @@ -import { - common, - ContractJSON, - createSerializeWire, - crypto, - IOHelper, - JSONHelper, - UInt160, -} from '@neo-one/client-common'; +import { common, ContractJSON, IOHelper, JSONHelper, UInt160 } from '@neo-one/client-common'; import { ContractStateModel } from '@neo-one/client-full-common'; import { ContractManifest } from './manifest'; -import { DeserializeWireBaseOptions, DeserializeWireOptions } from './Serializable'; -import { BinaryReader, utils } from './utils'; +import { assertArrayStackItem, StackItem } from './StackItems'; +import { utils } from './utils'; export interface ContractStateAdd { readonly id: number; + readonly updateCounter: number; + readonly hash: UInt160; readonly script: Buffer; readonly manifest: ContractManifest; } @@ -21,37 +15,32 @@ export interface ContractStateAdd { export type ContractKey = UInt160; export class ContractState extends ContractStateModel { - public static deserializeWireBase(options: DeserializeWireBaseOptions): ContractState { - const { reader } = options; - const id = reader.readInt32LE(); - const script = reader.readVarBytesLE(); - const manifest = ContractManifest.deserializeWireBase(options); + public static fromStackItem(stackItem: StackItem): ContractState { + const { array } = assertArrayStackItem(stackItem); + const id = array[0].getInteger().toNumber(); + const updateCounter = array[1].getInteger().toNumber(); + const hash = common.bufferToUInt160(array[2].getBuffer()); + const script = array[3].getBuffer(); + const manifest = ContractManifest.parseBytes(array[4].getBuffer()); - return new this({ + return new ContractState({ id, + updateCounter, + hash, script, manifest, }); } - public static deserializeWire(options: DeserializeWireOptions): ContractState { - return this.deserializeWireBase({ - context: options.context, - reader: new BinaryReader(options.buffer), - }); - } - - public readonly serializeWire = createSerializeWire(this.serializeWireBase.bind(this)); - - private readonly scriptHashInternal = utils.lazy(() => common.asUInt160(crypto.hash160(this.script))); private readonly sizeInternal = utils.lazy( - () => IOHelper.sizeOfUInt32LE + IOHelper.sizeOfVarBytesLE(this.script) + this.manifest.size, + () => + IOHelper.sizeOfUInt32LE + + +IOHelper.sizeOfUInt16LE + + IOHelper.sizeOfUInt160 + + IOHelper.sizeOfVarBytesLE(this.script) + + this.manifest.size, ); - public get scriptHash() { - return this.scriptHashInternal(); - } - public get size() { return this.sizeInternal(); } @@ -59,15 +48,22 @@ export class ContractState extends ContractStateModel { public clone() { return new ContractState({ id: this.id, + updateCounter: this.updateCounter, + hash: this.hash, script: this.script, manifest: this.manifest, }); } + public canCall(manifest: ContractManifest, method: string) { + return this.manifest.permissions.some((permission) => permission.isAllowed(manifest, method)); + } + public serializeJSON(): ContractJSON { return { id: this.id, - hash: JSONHelper.writeUInt160(this.scriptHash), + updatecounter: this.updateCounter, + hash: JSONHelper.writeUInt160(this.hash), script: JSONHelper.writeBase64Buffer(this.script), manifest: this.manifest.serializeJSON(), }; diff --git a/packages/neo-one-node-core/src/DesignationRole.ts b/packages/neo-one-node-core/src/DesignationRole.ts new file mode 100644 index 0000000000..720e58f1a9 --- /dev/null +++ b/packages/neo-one-node-core/src/DesignationRole.ts @@ -0,0 +1,4 @@ +export enum DesignationRole { + StateValidator = 4, + Oracle = 8, +} diff --git a/packages/neo-one-node-core/src/Native.ts b/packages/neo-one-node-core/src/Native.ts index c63820a9bd..ed2e92fab7 100644 --- a/packages/neo-one-node-core/src/Native.ts +++ b/packages/neo-one-node-core/src/Native.ts @@ -1,9 +1,14 @@ import { ECPoint, UInt160 } from '@neo-one/client-common'; import { BN } from 'bn.js'; +import { ContractState } from './ContractState'; +import { DesignationRole } from './DesignationRole'; +import { OracleRequest } from './OracleRequest'; import { ReadFindStorage } from './Storage'; import { StorageItem } from './StorageItem'; import { StorageKey } from './StorageKey'; +export type OracleRequestResults = ReadonlyArray; + export interface NativeContractStorageContext { readonly storages: ReadFindStorage; } @@ -13,7 +18,7 @@ export interface NativeContract { readonly name: string; } -export interface NEP5NativeContract extends NativeContract { +export interface NEP17NativeContract extends NativeContract { readonly symbol: string; readonly decimals: number; @@ -21,14 +26,16 @@ export interface NEP5NativeContract extends NativeContract { readonly balanceOf: (storage: NativeContractStorageContext, account: UInt160) => Promise; } -export interface GASContract extends NEP5NativeContract {} +export interface GASContract extends NEP17NativeContract {} export interface PolicyContract extends NativeContract { readonly getMaxTransactionsPerBlock: (storage: NativeContractStorageContext) => Promise; readonly getMaxBlockSize: (storage: NativeContractStorageContext) => Promise; readonly getMaxBlockSystemFee: (storage: NativeContractStorageContext) => Promise; readonly getFeePerByte: (storage: NativeContractStorageContext) => Promise; - readonly getBlockedAccounts: (storage: NativeContractStorageContext) => Promise; + readonly getExecFeeFactor: (storage: NativeContractStorageContext) => Promise; + readonly getStoragePrice: (storage: NativeContractStorageContext) => Promise; + readonly isBlocked: (storage: NativeContractStorageContext, account: UInt160) => Promise; } export interface Candidate { @@ -36,21 +43,45 @@ export interface Candidate { readonly votes: BN; } -export interface NEOContract extends NEP5NativeContract { +export interface NEOContract extends NEP17NativeContract { readonly totalAmount: BN; readonly effectiveVoterTurnout: number; readonly totalSupply: () => Promise; readonly getCandidates: (storage: NativeContractStorageContext) => Promise; - readonly getValidators: (storage: NativeContractStorageContext) => Promise; readonly getCommittee: (storage: NativeContractStorageContext) => Promise; readonly getCommitteeAddress: (storage: NativeContractStorageContext) => Promise; readonly unclaimedGas: (storage: NativeContractStorageContext, account: UInt160, end: number) => Promise; readonly getNextBlockValidators: (storage: NativeContractStorageContext) => Promise; + readonly computeNextBlockValidators: (storage: NativeContractStorageContext) => Promise; +} + +export interface ManagementContract extends NativeContract { + readonly getContract: (storage: NativeContractStorageContext, hash: UInt160) => Promise; + readonly listContracts: (storage: NativeContractStorageContext) => Promise; +} + +export interface DesignationContract extends NativeContract { + readonly getDesignatedByRole: ( + storage: NativeContractStorageContext, + role: DesignationRole, + height: number, + index: number, + ) => Promise; +} + +export interface OracleContract extends NativeContract { + readonly getRequest: (storage: NativeContractStorageContext, id: BN) => Promise; + readonly getRequests: (storage: NativeContractStorageContext) => Promise; + readonly getRequestsByUrl: (storage: NativeContractStorageContext, url: string) => Promise; } export interface NativeContainer { readonly GAS: GASContract; readonly NEO: NEOContract; readonly Policy: PolicyContract; + readonly Management: ManagementContract; + readonly Designation: DesignationContract; + readonly Oracle: OracleContract; + readonly isNative: (hash: UInt160) => boolean; } diff --git a/packages/neo-one-node-core/src/Nep5Balance.ts b/packages/neo-one-node-core/src/Nep17Balance.ts similarity index 82% rename from packages/neo-one-node-core/src/Nep5Balance.ts rename to packages/neo-one-node-core/src/Nep17Balance.ts index 12ddf4f79f..3a5b53d8e9 100644 --- a/packages/neo-one-node-core/src/Nep5Balance.ts +++ b/packages/neo-one-node-core/src/Nep17Balance.ts @@ -1,15 +1,15 @@ -import { createSerializeWire, IOHelper, Nep5BalanceModel } from '@neo-one/client-common'; +import { createSerializeWire, IOHelper, Nep17BalanceModel } from '@neo-one/client-common'; import { BN } from 'bn.js'; import { DeserializeWireBaseOptions, DeserializeWireOptions } from './Serializable'; import { BinaryReader, utils } from './utils'; -export interface Nep5BalanceAdd { +export interface Nep17BalanceAdd { readonly balanceBuffer: Buffer; readonly lastUpdatedBlock: number; } -export class Nep5Balance extends Nep5BalanceModel { - public static deserializeWireBase(options: DeserializeWireBaseOptions): Nep5Balance { +export class Nep17Balance extends Nep17BalanceModel { + public static deserializeWireBase(options: DeserializeWireBaseOptions): Nep17Balance { const { reader } = options; const balanceBuffer = reader.readVarBytesLE(512); const lastUpdatedBlock = reader.readUInt32LE(); @@ -20,7 +20,7 @@ export class Nep5Balance extends Nep5BalanceModel { }); } - public static deserializeWire(options: DeserializeWireOptions): Nep5Balance { + public static deserializeWire(options: DeserializeWireOptions): Nep17Balance { return this.deserializeWireBase({ context: options.context, reader: new BinaryReader(options.buffer), @@ -41,7 +41,7 @@ export class Nep5Balance extends Nep5BalanceModel { } public clone() { - return new Nep5Balance({ + return new Nep17Balance({ balanceBuffer: this.balanceInternal, lastUpdatedBlock: this.lastUpdatedBlock, }); diff --git a/packages/neo-one-node-core/src/Nep5BalanceKey.ts b/packages/neo-one-node-core/src/Nep17BalanceKey.ts similarity index 78% rename from packages/neo-one-node-core/src/Nep5BalanceKey.ts rename to packages/neo-one-node-core/src/Nep17BalanceKey.ts index df81989843..0f7a56cbb7 100644 --- a/packages/neo-one-node-core/src/Nep5BalanceKey.ts +++ b/packages/neo-one-node-core/src/Nep17BalanceKey.ts @@ -1,14 +1,14 @@ -import { createSerializeWire, IOHelper, Nep5BalanceKeyModel, UInt160 } from '@neo-one/client-common'; +import { createSerializeWire, IOHelper, Nep17BalanceKeyModel, UInt160 } from '@neo-one/client-common'; import { DeserializeWireBaseOptions, DeserializeWireOptions } from './Serializable'; import { BinaryReader, utils } from './utils'; -export interface Nep5BalanceKeyAdd { +export interface Nep17BalanceKeyAdd { readonly userScriptHash: UInt160; readonly assetScriptHash: UInt160; } -export class Nep5BalanceKey extends Nep5BalanceKeyModel { - public static deserializeWireBase(options: DeserializeWireBaseOptions): Nep5BalanceKey { +export class Nep17BalanceKey extends Nep17BalanceKeyModel { + public static deserializeWireBase(options: DeserializeWireBaseOptions): Nep17BalanceKey { const { reader } = options; const userScriptHash = reader.readUInt160(); const assetScriptHash = reader.readUInt160(); @@ -19,7 +19,7 @@ export class Nep5BalanceKey extends Nep5BalanceKeyModel { }); } - public static deserializeWire(options: DeserializeWireOptions): Nep5BalanceKey { + public static deserializeWire(options: DeserializeWireOptions): Nep17BalanceKey { return this.deserializeWireBase({ context: options.context, reader: new BinaryReader(options.buffer), @@ -34,7 +34,7 @@ export class Nep5BalanceKey extends Nep5BalanceKeyModel { } public clone() { - return new Nep5BalanceKey({ + return new Nep17BalanceKey({ userScriptHash: this.userScriptHash, assetScriptHash: this.assetScriptHash, }); diff --git a/packages/neo-one-node-core/src/Nep5Transfer.ts b/packages/neo-one-node-core/src/Nep17Transfer.ts similarity index 84% rename from packages/neo-one-node-core/src/Nep5Transfer.ts rename to packages/neo-one-node-core/src/Nep17Transfer.ts index 4986c1be50..390d80aba1 100644 --- a/packages/neo-one-node-core/src/Nep5Transfer.ts +++ b/packages/neo-one-node-core/src/Nep17Transfer.ts @@ -1,17 +1,17 @@ -import { createSerializeWire, IOHelper, Nep5TransferModel, UInt160, UInt256 } from '@neo-one/client-common'; +import { createSerializeWire, IOHelper, Nep17TransferModel, UInt160, UInt256 } from '@neo-one/client-common'; import { BN } from 'bn.js'; import { DeserializeWireBaseOptions, DeserializeWireOptions } from './Serializable'; import { BinaryReader, utils } from './utils'; -export interface Nep5TransferAdd { +export interface Nep17TransferAdd { readonly userScriptHash: UInt160; readonly blockIndex: number; readonly txHash: UInt256; readonly amountBuffer: Buffer; } -export class Nep5Transfer extends Nep5TransferModel { - public static deserializeWireBase(options: DeserializeWireBaseOptions): Nep5Transfer { +export class Nep17Transfer extends Nep17TransferModel { + public static deserializeWireBase(options: DeserializeWireBaseOptions): Nep17Transfer { const { reader } = options; const userScriptHash = reader.readUInt160(); const blockIndex = reader.readUInt32LE(); @@ -26,7 +26,7 @@ export class Nep5Transfer extends Nep5TransferModel { }); } - public static deserializeWire(options: DeserializeWireOptions): Nep5Transfer { + public static deserializeWire(options: DeserializeWireOptions): Nep17Transfer { return this.deserializeWireBase({ context: options.context, reader: new BinaryReader(options.buffer), @@ -51,7 +51,7 @@ export class Nep5Transfer extends Nep5TransferModel { } public clone() { - return new Nep5Transfer({ + return new Nep17Transfer({ userScriptHash: this.userScriptHash, blockIndex: this.blockIndex, txHash: this.txHash, diff --git a/packages/neo-one-node-core/src/Nep5TransferKey.ts b/packages/neo-one-node-core/src/Nep17TransferKey.ts similarity index 83% rename from packages/neo-one-node-core/src/Nep5TransferKey.ts rename to packages/neo-one-node-core/src/Nep17TransferKey.ts index 012cc9ec5e..072746f4a9 100644 --- a/packages/neo-one-node-core/src/Nep5TransferKey.ts +++ b/packages/neo-one-node-core/src/Nep17TransferKey.ts @@ -1,17 +1,17 @@ -import { createSerializeWire, IOHelper, Nep5TransferKeyModel, UInt160 } from '@neo-one/client-common'; +import { createSerializeWire, IOHelper, Nep17TransferKeyModel, UInt160 } from '@neo-one/client-common'; import { BN } from 'bn.js'; import { DeserializeWireBaseOptions, DeserializeWireOptions } from './Serializable'; import { BinaryReader, utils } from './utils'; -export interface Nep5TransferKeyAdd { +export interface Nep17TransferKeyAdd { readonly userScriptHash: UInt160; readonly timestampMS: BN; readonly assetScriptHash: UInt160; readonly blockTransferNotificationIndex: number; } -export class Nep5TransferKey extends Nep5TransferKeyModel { - public static deserializeWireBase(options: DeserializeWireBaseOptions): Nep5TransferKey { +export class Nep17TransferKey extends Nep17TransferKeyModel { + public static deserializeWireBase(options: DeserializeWireBaseOptions): Nep17TransferKey { const { reader } = options; const userScriptHash = reader.readUInt160(); const timestampMS = reader.readUInt64LE(); @@ -26,7 +26,7 @@ export class Nep5TransferKey extends Nep5TransferKeyModel { }); } - public static deserializeWire(options: DeserializeWireOptions): Nep5TransferKey { + public static deserializeWire(options: DeserializeWireOptions): Nep17TransferKey { return this.deserializeWireBase({ context: options.context, reader: new BinaryReader(options.buffer), @@ -43,7 +43,7 @@ export class Nep5TransferKey extends Nep5TransferKeyModel { } public clone() { - return new Nep5TransferKey({ + return new Nep17TransferKey({ userScriptHash: this.userScriptHash, timestampMS: this.timestampMS, assetScriptHash: this.assetScriptHash, diff --git a/packages/neo-one-node-core/src/OracleRequest.ts b/packages/neo-one-node-core/src/OracleRequest.ts new file mode 100644 index 0000000000..77e0dfd034 --- /dev/null +++ b/packages/neo-one-node-core/src/OracleRequest.ts @@ -0,0 +1,62 @@ +import { UInt256, UInt160, common } from '@neo-one/client-common'; +import { BN } from 'bn.js'; +import { StackItem, assertArrayStackItem } from './StackItems'; + +interface OracleRequestAdd { + readonly originalTxid: UInt256; + readonly gasForResponse: BN; + readonly url: string; + readonly filter: string; + readonly callbackContract: UInt160; + readonly callbackMethod: string; + readonly userData: Buffer; +} + +export class OracleRequest { + public static fromStackItem(stackItem: StackItem): OracleRequest { + const { array } = assertArrayStackItem(stackItem); + const originalTxid = common.bufferToUInt256(array[0].getBuffer()); + const gasForResponse = array[1].getInteger(); + const url = array[2].getString(); + const filter = array[3].getString(); + const callbackContract = common.bufferToUInt160(array[4].getBuffer()); + const callbackMethod = array[5].getString(); + const userData = array[6].getBuffer(); + + return new OracleRequest({ + originalTxid, + gasForResponse, + url, + filter, + callbackContract, + callbackMethod, + userData, + }); + } + + public originalTxid: UInt256; + public gasForResponse: BN; + public url: string; + public filter: string; + public callbackContract: UInt160; + public callbackMethod: string; + public userData: Buffer; + + public constructor({ + originalTxid, + gasForResponse, + url, + filter, + callbackContract, + callbackMethod, + userData, + }: OracleRequestAdd) { + this.originalTxid = originalTxid; + this.gasForResponse = gasForResponse; + this.url = url; + this.filter = filter; + this.callbackContract = callbackContract; + this.callbackMethod = callbackMethod; + this.userData = userData; + } +} diff --git a/packages/neo-one-node-core/src/Prices.ts b/packages/neo-one-node-core/src/Prices.ts deleted file mode 100644 index 613f1346c6..0000000000 --- a/packages/neo-one-node-core/src/Prices.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { common, Op } from '@neo-one/client-common'; -import { BN } from 'bn.js'; -import { InvalidOpCodeError } from './errors'; - -export const ECDsaVerifyPrice = common.fixed8FromDecimal('.01'); - -const opCodePrices: Record = { - [Op.PUSHINT8]: new BN(30), - [Op.PUSHINT16]: new BN(30), - [Op.PUSHINT32]: new BN(30), - [Op.PUSHINT64]: new BN(30), - [Op.PUSHINT128]: new BN(120), - [Op.PUSHINT256]: new BN(120), - [Op.PUSHA]: new BN(120), - [Op.PUSHNULL]: new BN(30), - [Op.PUSHDATA1]: new BN(180), - [Op.PUSHDATA2]: new BN(13000), - [Op.PUSHDATA4]: new BN(110000), - [Op.PUSHM1]: new BN(30), - [Op.PUSH0]: new BN(30), - [Op.PUSH1]: new BN(30), - [Op.PUSH2]: new BN(30), - [Op.PUSH3]: new BN(30), - [Op.PUSH4]: new BN(30), - [Op.PUSH5]: new BN(30), - [Op.PUSH6]: new BN(30), - [Op.PUSH7]: new BN(30), - [Op.PUSH8]: new BN(30), - [Op.PUSH9]: new BN(30), - [Op.PUSH10]: new BN(30), - [Op.PUSH11]: new BN(30), - [Op.PUSH12]: new BN(30), - [Op.PUSH13]: new BN(30), - [Op.PUSH14]: new BN(30), - [Op.PUSH15]: new BN(30), - [Op.PUSH16]: new BN(30), - [Op.NOP]: new BN(30), - [Op.JMP]: new BN(70), - [Op.JMP_L]: new BN(70), - [Op.JMPIF]: new BN(70), - [Op.JMPIF_L]: new BN(70), - [Op.JMPIFNOT]: new BN(70), - [Op.JMPIFNOT_L]: new BN(70), - [Op.JMPEQ]: new BN(70), - [Op.JMPEQ_L]: new BN(70), - [Op.JMPNE]: new BN(70), - [Op.JMPNE_L]: new BN(70), - [Op.JMPGT]: new BN(70), - [Op.JMPGT_L]: new BN(70), - [Op.JMPGE]: new BN(70), - [Op.JMPGE_L]: new BN(70), - [Op.JMPLT]: new BN(70), - [Op.JMPLT_L]: new BN(70), - [Op.JMPLE]: new BN(70), - [Op.JMPLE_L]: new BN(70), - [Op.CALL]: new BN(22000), - [Op.CALL_L]: new BN(22000), - [Op.CALLA]: new BN(22000), - [Op.ABORT]: new BN(30), - [Op.ASSERT]: new BN(30), - [Op.THROW]: new BN(22000), - [Op.TRY]: new BN(100), - [Op.TRY_L]: new BN(100), - [Op.ENDTRY]: new BN(100), - [Op.ENDTRY_L]: new BN(100), - [Op.ENDFINALLY]: new BN(100), - [Op.RET]: new BN(0), - [Op.SYSCALL]: new BN(0), - [Op.DEPTH]: new BN(60), - [Op.DROP]: new BN(60), - [Op.NIP]: new BN(60), - [Op.XDROP]: new BN(400), - [Op.CLEAR]: new BN(400), - [Op.DUP]: new BN(60), - [Op.OVER]: new BN(60), - [Op.PICK]: new BN(60), - [Op.TUCK]: new BN(60), - [Op.SWAP]: new BN(60), - [Op.ROT]: new BN(60), - [Op.ROLL]: new BN(400), - [Op.REVERSE3]: new BN(60), - [Op.REVERSE4]: new BN(60), - [Op.REVERSEN]: new BN(400), - [Op.INITSSLOT]: new BN(400), - [Op.INITSLOT]: new BN(800), - [Op.LDSFLD0]: new BN(60), - [Op.LDSFLD1]: new BN(60), - [Op.LDSFLD2]: new BN(60), - [Op.LDSFLD3]: new BN(60), - [Op.LDSFLD4]: new BN(60), - [Op.LDSFLD5]: new BN(60), - [Op.LDSFLD6]: new BN(60), - [Op.LDSFLD]: new BN(60), - [Op.STSFLD0]: new BN(60), - [Op.STSFLD1]: new BN(60), - [Op.STSFLD2]: new BN(60), - [Op.STSFLD3]: new BN(60), - [Op.STSFLD4]: new BN(60), - [Op.STSFLD5]: new BN(60), - [Op.STSFLD6]: new BN(60), - [Op.STSFLD]: new BN(60), - [Op.LDLOC0]: new BN(60), - [Op.LDLOC1]: new BN(60), - [Op.LDLOC2]: new BN(60), - [Op.LDLOC3]: new BN(60), - [Op.LDLOC4]: new BN(60), - [Op.LDLOC5]: new BN(60), - [Op.LDLOC6]: new BN(60), - [Op.LDLOC]: new BN(60), - [Op.STLOC0]: new BN(60), - [Op.STLOC1]: new BN(60), - [Op.STLOC2]: new BN(60), - [Op.STLOC3]: new BN(60), - [Op.STLOC4]: new BN(60), - [Op.STLOC5]: new BN(60), - [Op.STLOC6]: new BN(60), - [Op.STLOC]: new BN(60), - [Op.LDARG0]: new BN(60), - [Op.LDARG1]: new BN(60), - [Op.LDARG2]: new BN(60), - [Op.LDARG3]: new BN(60), - [Op.LDARG4]: new BN(60), - [Op.LDARG5]: new BN(60), - [Op.LDARG6]: new BN(60), - [Op.LDARG]: new BN(60), - [Op.STARG0]: new BN(60), - [Op.STARG1]: new BN(60), - [Op.STARG2]: new BN(60), - [Op.STARG3]: new BN(60), - [Op.STARG4]: new BN(60), - [Op.STARG5]: new BN(60), - [Op.STARG6]: new BN(60), - [Op.STARG]: new BN(60), - [Op.NEWBUFFER]: new BN(80000), - [Op.MEMCPY]: new BN(80000), - [Op.CAT]: new BN(80000), - [Op.SUBSTR]: new BN(80000), - [Op.LEFT]: new BN(80000), - [Op.RIGHT]: new BN(80000), - [Op.INVERT]: new BN(100), - [Op.AND]: new BN(200), - [Op.OR]: new BN(200), - [Op.XOR]: new BN(200), - [Op.EQUAL]: new BN(200), - [Op.NOTEQUAL]: new BN(200), - [Op.SIGN]: new BN(100), - [Op.ABS]: new BN(100), - [Op.NEGATE]: new BN(100), - [Op.INC]: new BN(100), - [Op.DEC]: new BN(100), - [Op.ADD]: new BN(200), - [Op.SUB]: new BN(200), - [Op.MUL]: new BN(300), - [Op.DIV]: new BN(300), - [Op.MOD]: new BN(300), - [Op.SHL]: new BN(300), - [Op.SHR]: new BN(300), - [Op.NOT]: new BN(100), - [Op.BOOLAND]: new BN(200), - [Op.BOOLOR]: new BN(200), - [Op.NZ]: new BN(100), - [Op.NUMEQUAL]: new BN(200), - [Op.NUMNOTEQUAL]: new BN(200), - [Op.LT]: new BN(200), - [Op.LE]: new BN(200), - [Op.GT]: new BN(200), - [Op.GE]: new BN(200), - [Op.MIN]: new BN(200), - [Op.MAX]: new BN(200), - [Op.WITHIN]: new BN(200), - [Op.PACK]: new BN(7000), - [Op.UNPACK]: new BN(7000), - [Op.NEWARRAY0]: new BN(400), - [Op.NEWARRAY]: new BN(15000), - [Op.NEWARRAY_T]: new BN(15000), - [Op.NEWSTRUCT0]: new BN(400), - [Op.NEWSTRUCT]: new BN(15000), - [Op.NEWMAP]: new BN(200), - [Op.SIZE]: new BN(150), - [Op.HASKEY]: new BN(270000), - [Op.KEYS]: new BN(500), - [Op.VALUES]: new BN(7000), - [Op.PICKITEM]: new BN(270000), - [Op.APPEND]: new BN(15000), - [Op.SETITEM]: new BN(270000), - [Op.REVERSEITEMS]: new BN(500), - [Op.REMOVE]: new BN(500), - [Op.CLEARITEMS]: new BN(400), - [Op.ISNULL]: new BN(60), - [Op.ISTYPE]: new BN(60), - [Op.CONVERT]: new BN(80000), -}; - -// tslint:disable-next-line: export-name -export const getOpCodePrice = (value: Op): BN => { - const fee = opCodePrices[value]; - if (fee === undefined) { - throw new InvalidOpCodeError(value); - } - - return fee; -}; diff --git a/packages/neo-one-node-core/src/Storage.ts b/packages/neo-one-node-core/src/Storage.ts index 2cefae8ca9..8f8c1c5cb8 100644 --- a/packages/neo-one-node-core/src/Storage.ts +++ b/packages/neo-one-node-core/src/Storage.ts @@ -1,15 +1,14 @@ import { ApplicationLogJSON } from '@neo-one/client-common'; import { Observable } from 'rxjs'; import { ApplicationLog } from './ApplicationLog'; -import { ConsensusContext } from './consensus'; import { ContractIDState } from './ContractIDState'; import { ContractKey, ContractState } from './ContractState'; import { HashIndexState } from './HashIndexState'; import { HeaderHashList, HeaderKey } from './HeaderHashList'; -import { Nep5Balance } from './Nep5Balance'; -import { Nep5BalanceKey } from './Nep5BalanceKey'; -import { Nep5Transfer } from './Nep5Transfer'; -import { Nep5TransferKey } from './Nep5TransferKey'; +import { Nep17Balance } from './Nep17Balance'; +import { Nep17BalanceKey } from './Nep17BalanceKey'; +import { Nep17Transfer } from './Nep17Transfer'; +import { Nep17TransferKey } from './Nep17TransferKey'; import { StorageItem } from './StorageItem'; import { StorageKey } from './StorageKey'; import { TransactionKey, TransactionState } from './transaction'; @@ -110,15 +109,15 @@ export type AddChange = | { readonly type: 'blockHashIndex'; readonly value: HashIndexState } | { readonly type: 'headerHashIndex'; readonly value: HashIndexState } | { readonly type: 'contractID'; readonly value: ContractIDState } - | { readonly type: 'nep5Balance'; readonly key: Nep5BalanceKey; readonly value: Nep5Balance } - | { readonly type: 'nep5TransferSent'; readonly key: Nep5TransferKey; readonly value: Nep5Transfer } - | { readonly type: 'nep5TransferReceived'; readonly key: Nep5TransferKey; readonly value: Nep5Transfer } + | { readonly type: 'nep17Balance'; readonly key: Nep17BalanceKey; readonly value: Nep17Balance } + | { readonly type: 'nep17TransferSent'; readonly key: Nep17TransferKey; readonly value: Nep17Transfer } + | { readonly type: 'nep17TransferReceived'; readonly key: Nep17TransferKey; readonly value: Nep17Transfer } | { readonly type: 'applicationLog'; readonly key: TransactionKey; readonly value: ApplicationLog }; export type DeleteChange = | { readonly type: 'contract'; readonly key: ContractKey } | { readonly type: 'storage'; readonly key: StorageKey } - | { readonly type: 'nep5Balance'; readonly key: Nep5BalanceKey }; + | { readonly type: 'nep17Balance'; readonly key: Nep17BalanceKey }; export type Change = | { readonly type: 'add'; readonly change: AddChange; readonly subType: 'add' | 'update' } @@ -128,18 +127,16 @@ export type ChangeSet = readonly Change[]; export interface BlockchainStorage { readonly blocks: ReadStorage; - readonly nep5Balances: ReadAllFindStorage; - readonly nep5TransfersSent: ReadFindStorage; - readonly nep5TransfersReceived: ReadFindStorage; + readonly nep17Balances: ReadAllFindStorage; + readonly nep17TransfersSent: ReadFindStorage; + readonly nep17TransfersReceived: ReadFindStorage; readonly applicationLogs: ReadStorage; // readonly consensusState: ReadMetadataStorage; readonly transactions: ReadStorage; - readonly contracts: ReadStorage; readonly storages: ReadFindStorage; readonly headerHashList: ReadStorage; readonly blockHashIndex: ReadMetadataStorage; readonly headerHashIndex: ReadMetadataStorage; - readonly contractID: ReadMetadataStorage; } export interface Storage extends BlockchainStorage { diff --git a/packages/neo-one-node-core/src/TransactionVerificationContext.ts b/packages/neo-one-node-core/src/TransactionVerificationContext.ts index b913ec208f..1cdbc9fc1a 100644 --- a/packages/neo-one-node-core/src/TransactionVerificationContext.ts +++ b/packages/neo-one-node-core/src/TransactionVerificationContext.ts @@ -1,7 +1,7 @@ -import { common, UInt160, UInt160Hex } from '@neo-one/client-common'; +import { common, UInt160, UInt160Hex, UInt256Hex } from '@neo-one/client-common'; import { BN } from 'bn.js'; import { NativeContractStorageContext } from './Native'; -import { Transaction } from './transaction'; +import { isOracleResponse, Transaction } from './transaction'; const assertSender = (sender: UInt160 | undefined) => { if (sender === undefined) { @@ -19,13 +19,20 @@ export interface TransactionVerificationContextAdd { export class TransactionVerificationContext { private readonly getGasBalance: (storage: NativeContractStorageContext, sender: UInt160) => Promise; private readonly mutableSenderFee: Record; + private readonly mutableOracleResponses: Record; public constructor({ getGasBalance }: TransactionVerificationContextAdd) { this.getGasBalance = getGasBalance; this.mutableSenderFee = {}; + this.mutableOracleResponses = {}; } public addTransaction(tx: Transaction) { + const oracle = tx.getAttribute(isOracleResponse); + if (oracle !== undefined) { + this.mutableOracleResponses[oracle.id.toString()] = tx.hashHex; + } + const key = common.uInt160ToHex(assertSender(tx.sender)); const maybeFee = this.mutableSenderFee[key] ?? new BN(0); this.mutableSenderFee[key] = maybeFee.add(tx.systemFee).add(tx.networkFee); @@ -37,7 +44,16 @@ export class TransactionVerificationContext { const maybeFee = this.mutableSenderFee[common.uInt160ToHex(sender)] ?? new BN(0); const totalFee = maybeFee.add(tx.systemFee).add(tx.networkFee); - return balance.gte(totalFee); + if (balance.lt(totalFee)) { + return false; + } + + const oracle = tx.getAttribute(isOracleResponse); + if (oracle !== undefined && this.mutableOracleResponses[oracle.id.toString()] !== undefined) { + return false; + } + + return true; } public removeTransaction(tx: Transaction) { @@ -56,5 +72,11 @@ export class TransactionVerificationContext { } else { this.mutableSenderFee[key] = newFee; } + + const oracle = tx.getAttribute(isOracleResponse); + if (oracle !== undefined) { + // tslint:disable-next-line: no-dynamic-delete + delete this.mutableOracleResponses[oracle.id.toString()]; + } } } diff --git a/packages/neo-one-node-core/src/Verifiable.ts b/packages/neo-one-node-core/src/Verifiable.ts index cf06cbba51..c9a587ddee 100644 --- a/packages/neo-one-node-core/src/Verifiable.ts +++ b/packages/neo-one-node-core/src/Verifiable.ts @@ -1,16 +1,22 @@ -import { UInt160 } from '@neo-one/client-common'; +import { common, UInt160 } from '@neo-one/client-common'; import { BN } from 'bn.js'; import { NativeContainer } from './Native'; import { SerializableContainer } from './Serializable'; import { BlockchainStorage } from './Storage'; -import { VM } from './vm'; +import { SnapshotName, VM } from './vm'; import { Witness } from './Witness'; +export const maxVerificationGas = common.fixed8FromDecimal('0.5'); + +export interface GetScriptHashesContext { + readonly storage: BlockchainStorage; + readonly native: NativeContainer; +} + export interface Verifiable { - readonly getScriptHashesForVerifying: (context: { - readonly storage: BlockchainStorage; - readonly native: NativeContainer; - }) => Promise | readonly UInt160[]; + readonly getScriptHashesForVerifying: ( + context: GetScriptHashesContext, + ) => Promise | readonly UInt160[]; readonly witnesses: readonly Witness[]; } @@ -19,19 +25,31 @@ export interface ExecuteScriptResult { readonly result: boolean; } -export type VerifyWitnesses = ( - vm: VM, - verifiable: SerializableContainer, - storage: BlockchainStorage, - native: NativeContainer, - gasIn: BN, -) => Promise; +export interface VerifyWitnessesOptions { + readonly vm: VM; + readonly verifiable: SerializableContainer; + readonly storage: BlockchainStorage; + readonly native: NativeContainer; + readonly gas: BN; + readonly snapshot?: SnapshotName; +} + +export type VerifyWitnesses = (options: VerifyWitnessesOptions) => Promise; + +export interface VerifyWitnessOptions extends VerifyWitnessesOptions { + readonly hash: UInt160; + readonly witness: Witness; +} + +export type VerifyWitness = (options: VerifyWitnessOptions) => Promise; export interface VerifyOptions { + readonly height: number; readonly vm: VM; readonly storage: BlockchainStorage; readonly native: NativeContainer; readonly verifyWitnesses: VerifyWitnesses; + readonly verifyWitness: VerifyWitness; } /* I think all of this might be a useless abstraction of blockchain properties. */ diff --git a/packages/neo-one-node-core/src/consensus/ConsensusContext.ts b/packages/neo-one-node-core/src/consensus/ConsensusContext.ts index cc805e1aa8..ad60ea89b4 100644 --- a/packages/neo-one-node-core/src/consensus/ConsensusContext.ts +++ b/packages/neo-one-node-core/src/consensus/ConsensusContext.ts @@ -1,8 +1,4 @@ import { common, ECPoint, UInt256, UInt256Hex } from '@neo-one/client-common'; -import { BN } from 'bn.js'; -import _ from 'lodash'; -import { Block } from '../Block'; -import { ConsensusData } from '../ConsensusData'; import { ChangeViewConsensusMessage, ConsensusPayload } from '../payload'; import { DeserializeWireBaseOptions } from '../Serializable'; import { Transaction } from '../transaction'; @@ -23,7 +19,7 @@ export interface ConsensusContextAdd { readonly commitPayloads?: ReadonlyArray; readonly changeViewPayloads?: ReadonlyArray; readonly lastChangeViewPayloads?: ReadonlyArray; - readonly lastSeenMessage?: readonly number[]; + readonly lastSeenMessage?: { readonly [key: string]: number | undefined }; readonly transactions?: { readonly [hash: string]: Transaction | undefined }; readonly transactionHashes?: readonly UInt256[]; readonly witnessSize?: number; @@ -31,24 +27,8 @@ export interface ConsensusContextAdd { // tslint:disable-next-line no-any export class ConsensusContext { - public static deserializeWireBase(options: DeserializeWireBaseOptions) { - const { reader } = options; - const version = reader.readUInt32LE(); - const index = reader.readUInt32LE(); - const timestamp = reader.readUInt64LE(); - - const nextConsensusIn = reader.readUInt160(); - const nextConsensus = nextConsensusIn.equals(common.ZERO_UINT160) ? undefined : nextConsensusIn; - - const consensusData = ConsensusData.deserializeWireBase(options); - const viewNumber = reader.readInt8(); - const transactionHashes = reader.readArray(() => reader.readUInt256()); - const transactions = reader.readArray( - () => Transaction.deserializeWireBase(options), - Block.MaxTransactionsPerBlock, - ); - - // TODO: before we implement the rest of this see if reloading and saving is even necessary + public static deserializeWireBase(_options: DeserializeWireBaseOptions) { + throw new Error('not yet implemented'); } public readonly viewNumber: number; public readonly myIndex: number; @@ -59,7 +39,7 @@ export class ConsensusContext { public readonly commitPayloads: ReadonlyArray; public readonly changeViewPayloads: ReadonlyArray; public readonly lastChangeViewPayloads: ReadonlyArray; - public readonly lastSeenMessage: readonly number[]; + public readonly lastSeenMessage: { readonly [key: string]: number | undefined }; public readonly transactions: { readonly [hash: string]: Transaction | undefined }; public readonly transactionHashes?: readonly UInt256[]; public readonly transactionHashesSet: Set; @@ -77,7 +57,7 @@ export class ConsensusContext { commitPayloads = [], changeViewPayloads = [], lastChangeViewPayloads = [], - lastSeenMessage = [], + lastSeenMessage = {}, transactions = {}, transactionHashes, witnessSize = 0, @@ -124,7 +104,18 @@ export class ConsensusContext { } public get countFailed(): number { - return this.lastSeenMessage.reduce((acc, val) => acc + (val < (this.blockBuilder.index ?? 0) - 1 ? 1 : 0), 0); + if (Object.values(this.lastSeenMessage).length === 0) { + return 0; + } + + return this.validators.reduce((acc, validator) => { + const count = this.lastSeenMessage[common.ecPointToHex(validator)]; + if (count === undefined) { + return acc + 1; + } + + return acc + (count < (this.blockBuilder.index ?? 0) - 1 ? 1 : 0); + }, 0); } public get moreThanFNodesCommittedOrLost(): boolean { @@ -197,19 +188,12 @@ export class ConsensusContext { lastChangeViewPayloads: this.lastChangeViewPayloads, transactions: this.transactions, transactionHashes: this.transactionHashes, + lastSeenMessage: this.lastSeenMessage, ...rest, }); } public toJSON(): object { - return { - block: this.blockBuilder.toJSON(), - viewNumber: this.viewNumber, - myIndex: this.myIndex, - expectedView: [...this.expectedView], - validators: this.validators.map((validator) => common.ecPointToString(validator)), - blockReceivedTimeMS: this.blockReceivedTimeMS, - transactions: _.fromPairs(Object.entries(this.transactions).map(([hash, tx]) => [hash, tx?.serializeJSON()])), - }; + throw new Error('not implemented'); } } diff --git a/packages/neo-one-node-core/src/constants.ts b/packages/neo-one-node-core/src/constants.ts index dc2757deb3..4a849e4c03 100644 --- a/packages/neo-one-node-core/src/constants.ts +++ b/packages/neo-one-node-core/src/constants.ts @@ -1,6 +1,3 @@ -import { BN } from 'bn.js'; - export const MAX_TRANSACTION_SIZE = 102400; -export const DEFAULT_VALIDATORS_COUNT = new BN(7); -export const WITNESS_INVOCATION_SIZE = 663; -export const WITNESS_VERIFICATION_SIZE = 361; +export const WITNESS_INVOCATION_SIZE = 1024; +export const WITNESS_VERIFICATION_SIZE = 1024; diff --git a/packages/neo-one-node-core/src/errors.ts b/packages/neo-one-node-core/src/errors.ts index 621129b3ac..41fd200a43 100644 --- a/packages/neo-one-node-core/src/errors.ts +++ b/packages/neo-one-node-core/src/errors.ts @@ -1,3 +1,4 @@ +import { OracleResponseCode, UInt256Hex } from '@neo-one/client-common'; import { makeErrorWithCode } from '@neo-one/utils'; // tslint:disable-next-line export-name @@ -74,3 +75,17 @@ export const InvalidOpCodeError = makeErrorWithCode( 'INVALID_OP_CODE_ERROR', (value: number) => `Cannot find fee for OpCode ${value}.`, ); +export const InvalidOracleResultError = makeErrorWithCode( + 'INVALID_ORACLE_RESULT_ERROR', + (code: OracleResponseCode, resultLength: number) => + `Expected result.length to be 0 with response code ${OracleResponseCode[code]}, found: ${resultLength}`, +); +export const InvalidValidatorsError = makeErrorWithCode( + 'INVALID_VALIDATORS_ERROR', + (length: number, index: number) => + `Validator index out of range when getting scriptHashes, found length ${length} but need index: ${index}.`, +); +export const InvalidPreviousHeaderError = makeErrorWithCode( + 'INVALID_PREVIOUS_HEADER_ERROR', + (hash: UInt256Hex) => `previous header hash ${hash} did not return a valid Header from storage`, +); diff --git a/packages/neo-one-node-core/src/index.ts b/packages/neo-one-node-core/src/index.ts index 33973b27d8..c5e82c416c 100644 --- a/packages/neo-one-node-core/src/index.ts +++ b/packages/neo-one-node-core/src/index.ts @@ -16,8 +16,9 @@ export * from './HeaderHashList'; export * from './Native'; export * from './network'; export * from './Notification'; -export * from './Prices'; +export * from './OracleRequest'; export * from './payload'; +export * from './DesignationRole'; export * from './Serializable'; export * from './Settings'; export * from './Signer'; @@ -36,8 +37,8 @@ export * from './Witness'; export * from './CallFlags'; export * from './TransactionData'; export * from './Node'; -export * from './Nep5Balance'; -export * from './Nep5Transfer'; -export * from './Nep5BalanceKey'; -export * from './Nep5TransferKey'; +export * from './Nep17Balance'; +export * from './Nep17Transfer'; +export * from './Nep17BalanceKey'; +export * from './Nep17TransferKey'; export * from './ApplicationLog'; diff --git a/packages/neo-one-node-core/src/manifest/ContractManifest.ts b/packages/neo-one-node-core/src/manifest/ContractManifest.ts index 4718370ab2..ce9b413e00 100644 --- a/packages/neo-one-node-core/src/manifest/ContractManifest.ts +++ b/packages/neo-one-node-core/src/manifest/ContractManifest.ts @@ -1,45 +1,31 @@ import { ContractManifestJSON, IOHelper, JSONHelper, UInt160 } from '@neo-one/client-common'; -import { ContractManifestModel, getContractProperties } from '@neo-one/client-full-common'; -import { DeserializeWireBaseOptions, DeserializeWireOptions } from '../Serializable'; +import { ContractManifestModel } from '@neo-one/client-full-common'; import { BinaryReader, utils } from '../utils'; import { ContractABI } from './ContractABI'; import { ContractGroup } from './ContractGroup'; import { ContractPermission } from './ContractPermission'; export class ContractManifest extends ContractManifestModel { - public static deserializeWireBase(options: DeserializeWireBaseOptions): ContractManifest { - const { reader } = options; - const json = JSON.parse(reader.readVarString(this.maxLength)); + public static parseBytes(bytes: Buffer) { + const reader = new BinaryReader(bytes); - return this.deserializeJSON(json); - } - - public static deserializeWire(options: DeserializeWireOptions): ContractManifest { - return this.deserializeWireBase({ - context: options.context, - reader: new BinaryReader(options.buffer), - }); + return this.deserializeJSON(JSON.parse(reader.readVarString(this.maxLength))); } private static deserializeJSON(json: ContractManifestJSON) { const groups = json.groups.map((group) => ContractGroup.deserializeJSON(group)); - const { payable, storage: hasStorage } = json.features; - const features = getContractProperties({ payable, hasStorage }); const supportedStandards = json.supportedstandards; const abi = ContractABI.deserializeJSON(json.abi); const permissions = json.permissions.map((permission) => ContractPermission.deserializeJSON(permission)); const trusts = utils.wildCardFromJSON(json.trusts, JSONHelper.readUInt160); - const safeMethods = utils.wildCardFromJSON(json.safemethods, (input) => input); const extra = json.extra; return new this({ groups, - features, supportedStandards, abi, permissions, trusts, - safeMethods, extra, }); } @@ -54,15 +40,7 @@ export class ContractManifest extends ContractManifestModel permission.isAllowed(manifest, method)); - } - public isValid(hash: UInt160) { - if (!this.abi.hash.equals(hash)) { - return false; - } - return this.groups.every((group) => group.isValid(hash)); } } diff --git a/packages/neo-one-node-core/src/manifest/ContractMethodDescriptor.ts b/packages/neo-one-node-core/src/manifest/ContractMethodDescriptor.ts index a839d0814c..3d16c759f8 100644 --- a/packages/neo-one-node-core/src/manifest/ContractMethodDescriptor.ts +++ b/packages/neo-one-node-core/src/manifest/ContractMethodDescriptor.ts @@ -8,12 +8,14 @@ export class ContractMethodDescriptor extends ContractMethodDescriptorModel ContractParameterDefinition.deserializeJSON(param)); const offset = json.offset; const returnType = toContractParameterType(json.returntype); + const safe = json.safe; return new this({ name, parameters, offset, returnType, + safe, }); } } diff --git a/packages/neo-one-node-core/src/manifest/ContractPermissionDescriptor.ts b/packages/neo-one-node-core/src/manifest/ContractPermissionDescriptor.ts index 3ae724009b..75adb708c9 100644 --- a/packages/neo-one-node-core/src/manifest/ContractPermissionDescriptor.ts +++ b/packages/neo-one-node-core/src/manifest/ContractPermissionDescriptor.ts @@ -3,7 +3,6 @@ import { ContractPermissionDescriptorModel } from '@neo-one/client-full-common'; export class ContractPermissionDescriptor extends ContractPermissionDescriptorModel { public static deserializeJSON(json: ContractPermissionDescriptorJSON): ContractPermissionDescriptor { - // TODO: Verify this is even close to being the same const jsonString = JSON.stringify(json); if (jsonString.length === 42) { return new ContractPermissionDescriptor({ hashOrGroup: JSONHelper.readUInt160(jsonString) }); diff --git a/packages/neo-one-node-core/src/payload/ConsensusPayload.ts b/packages/neo-one-node-core/src/payload/ConsensusPayload.ts index 9cf52cce44..dd200a3f9c 100644 --- a/packages/neo-one-node-core/src/payload/ConsensusPayload.ts +++ b/packages/neo-one-node-core/src/payload/ConsensusPayload.ts @@ -1,6 +1,7 @@ import { AccountContract, BinaryWriter, + common, createSerializeWire, crypto, ECPoint, @@ -9,16 +10,14 @@ import { PrivateKey, } from '@neo-one/client-common'; import { ContractParametersContext } from '../ContractParametersContext'; -import { NativeContainer } from '../Native'; import { DeserializeWireBaseOptions, DeserializeWireOptions, SerializableContainer, SerializableContainerType, } from '../Serializable'; -import { BlockchainStorage } from '../Storage'; -import { BinaryReader, utils } from '../utils'; -import { Verifiable, VerifyOptions } from '../Verifiable'; +import { BinaryReader } from '../utils'; +import { VerifyOptions } from '../Verifiable'; import { Witness } from '../Witness'; import { ConsensusMessage } from './message'; import { UnsignedConsensusPayload, UnsignedConsensusPayloadAdd } from './UnsignedConsensusPayload'; @@ -26,19 +25,14 @@ import { UnsignedConsensusPayload, UnsignedConsensusPayloadAdd } from './Unsigne export interface ConsensusPayloadAdd extends UnsignedConsensusPayloadAdd { readonly witness: Witness; } - -export interface VerifyConsensusPayloadOptions extends VerifyOptions { - readonly height: number; -} - -export class ConsensusPayload extends UnsignedConsensusPayload { +export class ConsensusPayload extends UnsignedConsensusPayload implements SerializableContainer { public static sign( payload: UnsignedConsensusPayload, privateKey: PrivateKey, validators: readonly ECPoint[], messageMagic: number, ): ConsensusPayload { - const context = new ContractParametersContext(payload.getScriptHashesForVerifying(validators)); + const context = new ContractParametersContext(payload.getScriptHashesForVerifyingFromValidators(validators)); const hashData = getHashData(payload.serializeWire(), messageMagic); const publicKey = crypto.privateKeyToPublicKey(privateKey); const signatureContract = AccountContract.createSignatureContract(publicKey); @@ -117,11 +111,17 @@ export class ConsensusPayload extends UnsignedConsensusPayload { return this.consensusMessage as T; } - public async verify(options: VerifyConsensusPayloadOptions) { + public async verify(options: VerifyOptions) { if (this.blockIndex <= options.height) { return false; } - return options.verifyWitnesses(options.vm, this, options.storage, options.native, 0.02); + return options.verifyWitnesses({ + vm: options.vm, + verifiable: this, + storage: options.storage, + native: options.native, + gas: common.fixed8FromDecimal('0.02'), + }); } } diff --git a/packages/neo-one-node-core/src/payload/UnsignedConsensusPayload.ts b/packages/neo-one-node-core/src/payload/UnsignedConsensusPayload.ts index 656f11e693..ddc60b90bb 100644 --- a/packages/neo-one-node-core/src/payload/UnsignedConsensusPayload.ts +++ b/packages/neo-one-node-core/src/payload/UnsignedConsensusPayload.ts @@ -5,10 +5,13 @@ import { crypto, ECPoint, getHashData, + InvalidFormatError, UInt256, } from '@neo-one/client-common'; +import { InvalidValidatorsError } from '../errors'; import { DeserializeWireBaseOptions, DeserializeWireOptions, SerializableWire } from '../Serializable'; import { BinaryReader, utils } from '../utils'; +import { GetScriptHashesContext } from '../Verifiable'; import { ConsensusMessage, deserializeConsensusMessageWire } from './message/ConsensusMessage'; export interface UnsignedConsensusPayloadAdd { @@ -94,8 +97,7 @@ export class UnsignedConsensusPayload implements SerializableWire { public get consensusMessage() { if (this.consensusMessageInternal === undefined) { - // TODO: Implement an error here - throw new Error('Need a way to get consensusMessage'); + throw new InvalidFormatError('Expected to receive a ConsensusMessage but could not find one.'); } return this.consensusMessageInternal(); @@ -117,10 +119,15 @@ export class UnsignedConsensusPayload implements SerializableWire { return common.uInt256ToHex(this.hash); } - public getScriptHashesForVerifying(validators: readonly ECPoint[]) { + public async getScriptHashesForVerifying({ native, storage }: GetScriptHashesContext) { + const validators = await native.NEO.getNextBlockValidators(storage); + + return this.getScriptHashesForVerifyingFromValidators(validators); + } + + public getScriptHashesForVerifyingFromValidators(validators: readonly ECPoint[]) { if (validators.length <= this.validatorIndex) { - // TODO - throw new Error(); + throw new InvalidValidatorsError(validators.length, this.validatorIndex); } return [crypto.toScriptHash(crypto.createSignatureRedeemScript(validators[this.validatorIndex]))]; diff --git a/packages/neo-one-node-core/src/transaction/Transaction.ts b/packages/neo-one-node-core/src/transaction/Transaction.ts index cb46f4499e..9b3d4916c5 100644 --- a/packages/neo-one-node-core/src/transaction/Transaction.ts +++ b/packages/neo-one-node-core/src/transaction/Transaction.ts @@ -1,12 +1,15 @@ import { AttributeTypeModel, common, + crypto, InvalidFormatError, IOHelper, JSONHelper, MAX_TRANSACTION_SIZE, MAX_VALID_UNTIL_BLOCK_INCREMENT, + multiSignatureContractCost, scriptHashToAddress, + signatureContractCost, TransactionJSON, TransactionModel, TransactionModelAdd, @@ -26,7 +29,7 @@ import { import { Signer } from '../Signer'; import { TransactionVerificationContext } from '../TransactionVerificationContext'; import { BinaryReader, utils } from '../utils'; -import { Verifiable, VerifyOptions } from '../Verifiable'; +import { maxVerificationGas, Verifiable, VerifyOptions } from '../Verifiable'; import { Witness } from '../Witness'; import { Attribute, deserializeAttribute } from './attributes'; @@ -174,20 +177,19 @@ export class Transaction IOHelper.sizeOfArray(this.witnesses, (witness) => witness.size), ); - public async verifyForEachBlock( + public async verifyStateDependent( verifyOptions: VerifyOptions, transactionContext?: TransactionVerificationContext, ): Promise { - const { storage, native } = verifyOptions; + const { storage, native, verifyWitness, vm } = verifyOptions; const { index } = await storage.blockHashIndex.get(); - if (this.validUntilBlock < index || this.validUntilBlock > index + MAX_VALID_UNTIL_BLOCK_INCREMENT) { + if (this.validUntilBlock <= index || this.validUntilBlock > index + MAX_VALID_UNTIL_BLOCK_INCREMENT) { return VerifyResultModel.Expired; } const hashes = this.getScriptHashesForVerifying(); - const setHashes = new Set(hashes); - const blockedAccounts = await native.Policy.getBlockedAccounts(storage); - if (blockedAccounts.some((account) => setHashes.has(account))) { + const blocked = await Promise.all(hashes.map(async (hash) => native.Policy.isBlocked(storage, hash))); + if (blocked.some((bool) => bool)) { return VerifyResultModel.PolicyFail; } @@ -212,21 +214,64 @@ export class Transaction return VerifyResultModel.Invalid; } - const verifyHashes = await Promise.all( - hashes.map(async (hash, idx) => { - if (this.witnesses[idx].verification.length > 0) { - return true; + const [feePerByte, execFeeFactor] = await Promise.all([ + native.Policy.getFeePerByte(storage), + native.Policy.getExecFeeFactor(storage), + ]); + + let netFee = this.networkFee.sub(feePerByte.muln(this.size)); + // tslint:disable-next-line: no-loop-statement + for (let i = 0; i < hashes.length; i += 1) { + const witness = this.witnesses[i]; + const multiSigResult = crypto.isMultiSigContractWithResult(witness.verification); + if (multiSigResult.result) { + const { m, n } = multiSigResult; + netFee = netFee.sub(new BN(multiSignatureContractCost(m, n).toString(), 10).muln(execFeeFactor)); + } else if (crypto.isSignatureContract(witness.verification)) { + netFee = netFee.sub(new BN(signatureContractCost.toString(), 10).muln(execFeeFactor)); + } else { + const { result, gas } = await verifyWitness(vm, this, storage, native, hashes[i], witness, netFee); + if (!result) { + return VerifyResultModel.InsufficientFunds; } - const state = await storage.contracts.tryGet(hash); + netFee = netFee.sub(gas); + } + if (netFee.ltn(0)) { + return VerifyResultModel.InsufficientFunds; + } + } - return state !== undefined; - }), - ); + return VerifyResultModel.Succeed; + } - if (verifyHashes.some((value) => !value)) { + public async verifyStateIndependent(verifyOptions: VerifyOptions) { + const { storage, native, verifyWitness, vm } = verifyOptions; + if (this.size > MAX_TRANSACTION_SIZE) { + return VerifyResultModel.Invalid; + } + const hashes = this.getScriptHashesForVerifying(); + if (hashes.length !== this.witnesses.length) { return VerifyResultModel.Invalid; } + // tslint:disable-next-line: no-loop-statement + for (let i = 0; i < hashes.length; i += 1) { + if (crypto.isStandardContract(this.witnesses[i].verification)) { + const { result } = await verifyWitness( + vm, + this, + storage, + native, + hashes[i], + this.witnesses[i], + maxVerificationGas, + ); + if (!result) { + return VerifyResultModel.Invalid; + } + } + } + return VerifyResultModel.Succeed; } @@ -234,26 +279,12 @@ export class Transaction verifyOptions: VerifyOptions, verifyContext?: TransactionVerificationContext, ): Promise { - const { native, storage, verifyWitnesses, vm } = verifyOptions; - const result = await this.verifyForEachBlock(verifyOptions, verifyContext); - if (result !== VerifyResultModel.Succeed) { - return result; - } - if (this.size > MAX_TRANSACTION_SIZE) { - return VerifyResultModel.Invalid; - } - const feePerByte = await native.Policy.getFeePerByte(storage); - const netFee = this.networkFee.sub(feePerByte.muln(this.size)); - if (netFee.ltn(0)) { - return VerifyResultModel.InsufficientFunds; + const independentResult = await this.verifyStateIndependent(verifyOptions); + if (independentResult !== VerifyResultModel.Succeed) { + return independentResult; } - const witnessVerify = await verifyWitnesses(vm, this, storage, native, netFee); - if (!witnessVerify) { - return VerifyResultModel.Invalid; - } - - return VerifyResultModel.Succeed; + return this.verifyStateDependent(verifyOptions, verifyContext); } public serializeJSON(): TransactionJSON { diff --git a/packages/neo-one-node-core/src/transaction/attributes/Attribute.ts b/packages/neo-one-node-core/src/transaction/attributes/Attribute.ts index 2b041f0ea3..dc06e15031 100644 --- a/packages/neo-one-node-core/src/transaction/attributes/Attribute.ts +++ b/packages/neo-one-node-core/src/transaction/attributes/Attribute.ts @@ -1,8 +1,9 @@ import { assertAttributeType, AttributeTypeModel, InvalidFormatError } from '@neo-one/client-common'; import { DeserializeWireBaseOptions } from '../../Serializable'; import { HighPriorityAttribute } from './HighPriorityAttribute'; +import { OracleResponse } from './OracleResponse'; -export type Attribute = HighPriorityAttribute; +export type Attribute = HighPriorityAttribute | OracleResponse; export const deserializeAttribute = (options: DeserializeWireBaseOptions): Attribute => { const { reader } = options; @@ -11,8 +12,16 @@ export const deserializeAttribute = (options: DeserializeWireBaseOptions): Attri switch (type) { case AttributeTypeModel.HighPriority: - return new HighPriorityAttribute(); + return HighPriorityAttribute.deserializeWithoutType(reader); + case AttributeTypeModel.OracleResponse: + return OracleResponse.deserializeWithoutType(reader); default: throw new InvalidFormatError(`Attribute type ${type} not yet implemented`); } }; + +export const getIsAttribute = (type: AttributeTypeModel) => (attr: Attribute): attr is T => + attr.type === type; + +export const isHighPriorityAttribute = getIsAttribute(AttributeTypeModel.HighPriority); +export const isOracleResponse = getIsAttribute(AttributeTypeModel.OracleResponse); diff --git a/packages/neo-one-node-core/src/transaction/attributes/AttributeBase.ts b/packages/neo-one-node-core/src/transaction/attributes/AttributeBase.ts index cc5ccddb5d..9816f814ee 100644 --- a/packages/neo-one-node-core/src/transaction/attributes/AttributeBase.ts +++ b/packages/neo-one-node-core/src/transaction/attributes/AttributeBase.ts @@ -1,34 +1,7 @@ -import { - AttributeBaseModel, - AttributeJSON, - AttributeTypeModel, - InvalidFormatError, - IOHelper, - toJSONAttributeType, -} from '@neo-one/client-common'; -import { DeserializeWireBaseOptions, SerializableJSON } from '../../Serializable'; +import { AttributeJSON, SerializableJSON } from '@neo-one/client-common'; import { VerifyOptions } from '../../Verifiable'; import { Transaction } from '../Transaction'; -export const createDeserializeAttributeType = (type: AttributeTypeModel) => (options: DeserializeWireBaseOptions) => { - const { reader } = options; - const byte = reader.readUInt8(); - if (byte !== type) { - throw new InvalidFormatError(`Expected attribute type: ${type}, found: ${byte}`); - } - - return type; -}; - -export abstract class AttributeBase extends AttributeBaseModel implements SerializableJSON { - public serializeJSON(): AttributeJSON { - return { - type: toJSONAttributeType(this.type), - }; - } - - // Must not be implemented in C# land yet? - public async verify(_verifyOptions: VerifyOptions, _tx: Transaction) { - return Promise.resolve(true); - } +export interface AttributeBase extends SerializableJSON { + readonly verify: (verifyOptions: VerifyOptions, tx: Transaction) => Promise; } diff --git a/packages/neo-one-node-core/src/transaction/attributes/HighPriorityAttribute.ts b/packages/neo-one-node-core/src/transaction/attributes/HighPriorityAttribute.ts index 42bc09f28f..e1cd54a250 100644 --- a/packages/neo-one-node-core/src/transaction/attributes/HighPriorityAttribute.ts +++ b/packages/neo-one-node-core/src/transaction/attributes/HighPriorityAttribute.ts @@ -1,7 +1,23 @@ -import { AttributeTypeModel } from '@neo-one/client-common'; +import { HighPriorityAttributeJSON, HighPriorityAttributeModel } from '@neo-one/client-common'; +import { BinaryReader } from '../../utils'; +import { VerifyOptions } from '../../Verifiable'; +import { Transaction } from '../Transaction'; import { AttributeBase } from './AttributeBase'; -export class HighPriorityAttribute extends AttributeBase { - public readonly type = AttributeTypeModel.HighPriority; - public readonly allowMultiple = false; +export class HighPriorityAttribute + extends HighPriorityAttributeModel + implements AttributeBase { + public static deserializeWithoutType(_reader: BinaryReader) { + return new HighPriorityAttribute(); + } + + public serializeJSON(): HighPriorityAttributeJSON { + return { + type: 'HighPriority', + }; + } + + public async verify(_verifyOptions: VerifyOptions, _tx: Transaction) { + return Promise.resolve(true); + } } diff --git a/packages/neo-one-node-core/src/transaction/attributes/OracleResponse.ts b/packages/neo-one-node-core/src/transaction/attributes/OracleResponse.ts new file mode 100644 index 0000000000..7d4bb36115 --- /dev/null +++ b/packages/neo-one-node-core/src/transaction/attributes/OracleResponse.ts @@ -0,0 +1,84 @@ +import { + assertOracleResponseCode, + common, + crypto, + JSONHelper, + OracleResponseCode, + OracleResponseJSON, + OracleResponseModel, + ScriptBuilder, + utils, + WitnessScopeModel, +} from '@neo-one/client-common'; +import { DesignationRole } from '../../DesignationRole'; +import { InvalidOracleResultError } from '../../errors'; +import { BinaryReader } from '../../utils'; +import { VerifyOptions } from '../../Verifiable'; +import { Transaction } from '../Transaction'; +import { AttributeBase } from './AttributeBase'; + +const maxResultSize = 65535; + +const getFixedScript = utils.lazy(() => { + const builder = new ScriptBuilder(); + builder.emitAppCall(common.nativeHashes.Oracle, 'finish'); + + return builder.build(); +}); + +export class OracleResponse extends OracleResponseModel implements AttributeBase { + public static readonly fixedScript = getFixedScript(); + public static deserializeWithoutType(reader: BinaryReader): OracleResponse { + const id = reader.readUInt64LE(); + const code = assertOracleResponseCode(reader.readUInt8()); + const result = reader.readVarBytesLE(maxResultSize); + + if (code !== OracleResponseCode.Success && result.length > 0) { + throw new InvalidOracleResultError(code, result.length); + } + + return new OracleResponse({ + id, + code, + result, + }); + } + + public serializeJSON(): OracleResponseJSON { + return { + type: 'OracleResponse', + id: this.id.toString(), + code: this.code, + result: JSONHelper.writeBase64Buffer(this.result), + }; + } + + public async verify({ native, storage, height }: VerifyOptions, tx: Transaction) { + if (tx.signers.some((signer) => signer.scopes !== WitnessScopeModel.None)) { + return false; + } + + if (!tx.script.equals(OracleResponse.fixedScript)) { + return false; + } + + const request = await native.Oracle.getRequest(storage, this.id); + if (request === undefined) { + return false; + } + + if (!tx.networkFee.add(tx.systemFee).eq(request.gasForResponse)) { + return false; + } + + const designated = await native.Designation.getDesignatedByRole( + storage, + DesignationRole.Oracle, + height, + height + 1, + ); + const oracleAccount = crypto.getConsensusAddress(designated); + + return tx.signers.some((signer) => signer.account.equals(oracleAccount)); + } +} diff --git a/packages/neo-one-node-core/src/utils/utils.ts b/packages/neo-one-node-core/src/utils/utils.ts index 5324c96e7a..c174b23086 100644 --- a/packages/neo-one-node-core/src/utils/utils.ts +++ b/packages/neo-one-node-core/src/utils/utils.ts @@ -5,6 +5,7 @@ import { randomBytes } from 'crypto'; import _ from 'lodash'; import { StackItem } from '../StackItems'; import { StorageItem } from '../StorageItem'; +import { executionLimits } from '../vm'; import { BinaryReader } from './BinaryReader'; import { deserializeStackItem } from './deserializeStackItem'; @@ -205,7 +206,7 @@ const wildCardFromJSON = (json: WildcardContainerJSON, selector: (input: stri const getInteroperable = (item: StorageItem, fromStackItem: (item: StackItem) => T): T => { const buffer = item.value; const reader = new BinaryReader(buffer); - const deserializedStackItem = deserializeStackItem(reader, 16, 34); + const deserializedStackItem = deserializeStackItem(reader, executionLimits.maxStackSize, executionLimits.maxItemSize); return fromStackItem(deserializedStackItem); }; diff --git a/packages/neo-one-node-core/src/vm.ts b/packages/neo-one-node-core/src/vm.ts index 1ed80d7449..83b4fd0174 100644 --- a/packages/neo-one-node-core/src/vm.ts +++ b/packages/neo-one-node-core/src/vm.ts @@ -1,4 +1,4 @@ -import { TriggerType, UInt256, VMState, Log, UInt160 } from '@neo-one/client-common'; +import { TriggerType, UInt160, UInt256, VMState } from '@neo-one/client-common'; import { BN } from 'bn.js'; import { Block } from './Block'; import { CallFlags } from './CallFlags'; @@ -7,6 +7,14 @@ import { SerializableContainer } from './Serializable'; import { StackItem } from './StackItems'; import { Transaction } from './transaction'; +export const executionLimits = { + maxShift: 256, + maxStackSize: 2 * 1024, + maxItemSize: 1024 * 1024, + maxInvocationStackSize: 1024, + maxTryNestingDepth: 16, +}; + export interface VMLog { readonly containerHash?: UInt256; readonly callingScriptHash: UInt160; @@ -29,7 +37,6 @@ export interface ApplicationEngineOptions { readonly container?: SerializableContainer; readonly snapshot?: SnapshotName; readonly gas: BN; - readonly testMode: boolean; } export interface RunEngineOptions { @@ -38,10 +45,23 @@ export interface RunEngineOptions { readonly container?: SerializableContainer; readonly persistingBlock?: Block; readonly offset?: number; - readonly testMode?: boolean; readonly gas?: BN; } +export interface LoadScriptOptions { + readonly script: Buffer; + readonly flags?: CallFlags; + readonly scriptHash?: UInt160; + readonly initialPosition?: number; +} + +export interface LoadContractOptions { + readonly hash: UInt160; + readonly flags: CallFlags; + readonly method: string; + readonly packParameters?: boolean; +} + export interface ApplicationEngine { readonly trigger: TriggerType; readonly gasConsumed: BN; @@ -49,9 +69,9 @@ export interface ApplicationEngine { readonly state: VMState; readonly notifications: readonly StackItem[]; readonly logs: readonly VMLog[]; - readonly loadScript: (script: Buffer, flag?: CallFlags) => boolean; + readonly loadScript: (options: LoadScriptOptions) => boolean; + readonly loadContract: (options: LoadContractOptions) => boolean; readonly execute: () => VMState; - readonly setInstructionPointer: (position: number) => boolean; } export type SnapshotPartial = 'blocks' | 'transactions'; @@ -67,6 +87,7 @@ export interface SnapshotHandler { readonly setPersistingBlock: (block: Block) => boolean; readonly hasPersistingBlock: () => boolean; // TODO: type the returning changeSet + // tslint:disable-next-line: no-any readonly getChangeSet: () => any; readonly clone: () => void; } @@ -91,5 +112,7 @@ export interface VM { readonly withSnapshots: ( func: (snapshots: { readonly main: SnapshotHandler; readonly clone: Omit }) => T, ) => T; - readonly updateStore: (storage: ReadonlyArray<{ key: Buffer; value: Buffer }>) => void; + readonly updateStore: (storage: ReadonlyArray<{ readonly key: Buffer; readonly value: Buffer }>) => void; + // tslint:disable-next-line: no-any + readonly test: () => any; } diff --git a/packages/neo-one-node-native/src/CachedCommittee.ts b/packages/neo-one-node-native/src/CachedCommittee.ts new file mode 100644 index 0000000000..7aaa73d78a --- /dev/null +++ b/packages/neo-one-node-native/src/CachedCommittee.ts @@ -0,0 +1,31 @@ +import { common, ECPoint } from '@neo-one/client-common'; +import { assertArrayStackItem, assertStructStackItem, StackItem } from '@neo-one/node-core'; +import { BN } from 'bn.js'; + +interface CachedCommitteeElement { + readonly publicKey: ECPoint; + readonly votes: BN; +} + +export class CachedCommittee { + public static fromStackItem(item: StackItem) { + const arrayItem = assertArrayStackItem(item); + + const members = arrayItem.array.map((element) => { + const structItem = assertStructStackItem(element); + + return { + publicKey: common.bufferToECPoint(structItem.array[0].getBuffer()), + votes: structItem.array[1].getInteger(), + }; + }); + + return new CachedCommittee(members); + } + + public readonly members: readonly CachedCommitteeElement[]; + + public constructor(members: readonly CachedCommitteeElement[]) { + this.members = members; + } +} diff --git a/packages/neo-one-node-native/src/DesignationContract.ts b/packages/neo-one-node-native/src/DesignationContract.ts new file mode 100644 index 0000000000..9381150802 --- /dev/null +++ b/packages/neo-one-node-native/src/DesignationContract.ts @@ -0,0 +1,68 @@ +import { common, ECPoint } from '@neo-one/client-common'; +import { + assertArrayStackItem, + DesignationRole as Role, + NativeContractStorageContext, + StackItem, + utils, +} from '@neo-one/node-core'; +import { map, toArray } from 'rxjs/operators'; +import { NativeContract } from './NativeContract'; + +export class DesignationContract extends NativeContract { + public constructor() { + super({ + id: -5, + name: 'DesignationContract', + }); + } + + /** + * passing in height and index is a pretty HMMM way to do this but in the vein of being + * consistent with C# code as much as possible we will do it like this. The reasoning + * being that our snapshot equivalent 'storage' doesn't have knowledge of the current height, + * that is a blockchain abstraction. In almost no situation should this first error actually throw. + */ + public async getDesignatedByRole( + { storages }: NativeContractStorageContext, + role: Role, + height: number, + index: number, + ): Promise { + if (height + 1 < index) { + throw new Error(`index: ${index} out of range for getDesignatedByRole.`); + } + + const key = this.createStorageKey(Buffer.from([role])) + .addUInt32BE(index) + .toSearchPrefix(); + const boundary = this.createStorageKey(Buffer.from([role])).toSearchPrefix(); + + const range = await storages + .find$(boundary, key) + .pipe( + map(({ value }) => utils.getInteroperable(value, NodeList.fromStackItem).members), + toArray(), + ) + .toPromise(); + + const publicKeys = range.length === 0 ? undefined : range[range.length - 1]; + + return publicKeys ?? []; + } +} + +class NodeList { + public static fromStackItem(stackItem: StackItem): NodeList { + const arrayItem = assertArrayStackItem(stackItem); + const members = arrayItem.array.map((item) => common.bufferToECPoint(item.getBuffer())); + + return new NodeList(members); + } + + public readonly members: readonly ECPoint[]; + + public constructor(members: readonly ECPoint[]) { + this.members = members; + } +} diff --git a/packages/neo-one-node-native/src/GASToken.ts b/packages/neo-one-node-native/src/GASToken.ts index 64ac350091..b67ec8b229 100644 --- a/packages/neo-one-node-native/src/GASToken.ts +++ b/packages/neo-one-node-native/src/GASToken.ts @@ -1,12 +1,12 @@ -import { NEP5NativeContract } from './Nep5'; +import { NEP17NativeContract } from './Nep17'; -export class GASToken extends NEP5NativeContract { +export class GASToken extends NEP17NativeContract { public static readonly decimals: number = 8; public constructor() { super({ id: -2, - name: 'GAS', - symbol: 'gas', + name: 'GasToken', + symbol: 'GAS', decimals: 8, }); } diff --git a/packages/neo-one-node-native/src/KeyBuilder.ts b/packages/neo-one-node-native/src/KeyBuilder.ts index 03750d6348..c06eb9ebd6 100644 --- a/packages/neo-one-node-native/src/KeyBuilder.ts +++ b/packages/neo-one-node-native/src/KeyBuilder.ts @@ -1,4 +1,5 @@ import { SerializableWire, StorageKey } from '@neo-one/node-core'; +import { BN } from 'bn.js'; export class KeyBuilder { private readonly id: number; @@ -26,6 +27,19 @@ export class KeyBuilder { return this; } + public addUInt32BE(value: number): this { + const buffer = Buffer.alloc(4); + buffer.writeUInt32BE(value); + + return this.addBuffer(buffer); + } + + public addUInt64LE(value: BN): this { + const buffer = value.toArrayLike(Buffer, 'le', 8); + + return this.addBuffer(buffer); + } + public toSearchPrefix(): Buffer { return StorageKey.createSearchPrefix(this.id, this.mutableBuffer); } diff --git a/packages/neo-one-node-native/src/ManagementContract.ts b/packages/neo-one-node-native/src/ManagementContract.ts new file mode 100644 index 0000000000..b7aaa0d742 --- /dev/null +++ b/packages/neo-one-node-native/src/ManagementContract.ts @@ -0,0 +1,43 @@ +import { UInt160 } from '@neo-one/client-common'; +import { ContractState, NativeContractStorageContext, utils } from '@neo-one/node-core'; +import { map, toArray } from 'rxjs/operators'; +import { NativeContract } from './NativeContract'; + +export class ManagementContract extends NativeContract { + private readonly prefixes = { + minimumDeploymentFee: Buffer.from([20]), + nextAvailableId: Buffer.from([15]), + contract: Buffer.from([8]), + }; + + public constructor() { + super({ + id: 0, + name: 'ManagementContract', + }); + } + + public async getContract({ storages }: NativeContractStorageContext, hash: UInt160) { + const maybeContract = await storages.tryGet( + this.createStorageKey(this.prefixes.contract).addBuffer(hash).toStorageKey(), + ); + + if (maybeContract === undefined) { + return undefined; + } + + return utils.getInteroperable(maybeContract, ContractState.fromStackItem); + } + + public async listContracts({ storages }: NativeContractStorageContext) { + const searchPrefix = this.createStorageKey(this.prefixes.contract).toSearchPrefix(); + + return storages + .find$(searchPrefix) + .pipe( + map(({ value }) => utils.getInteroperable(value, ContractState.fromStackItem)), + toArray(), + ) + .toPromise(); + } +} diff --git a/packages/neo-one-node-native/src/NEOToken.ts b/packages/neo-one-node-native/src/NEOToken.ts index 426b0f1887..0d11c4e309 100644 --- a/packages/neo-one-node-native/src/NEOToken.ts +++ b/packages/neo-one-node-native/src/NEOToken.ts @@ -1,13 +1,37 @@ -import { common, crypto, ECPoint, UInt160 } from '@neo-one/client-common'; +import { common, crypto, ECPoint, InvalidFormatError, UInt160 } from '@neo-one/client-common'; import { BlockchainSettings, Candidate, NativeContractStorageContext, utils } from '@neo-one/node-core'; import { BN } from 'bn.js'; import _ from 'lodash'; import { filter, map, toArray } from 'rxjs/operators'; import { CandidateState, NEOAccountState } from './AccountStates'; -import { GASToken } from './GASToken'; -import { NEP5NativeContract } from './Nep5'; +import { CachedCommittee } from './CachedCommittee'; +import { NEP17NativeContract } from './Nep17'; -export class NEOToken extends NEP5NativeContract { +type Storages = NativeContractStorageContext['storages']; + +interface CalculateBonusOptions { + readonly storages: Storages; + readonly vote?: ECPoint; + readonly value: BN; + readonly start: number; + readonly end: number; +} + +interface GasRecord { + readonly index: number; + readonly gasPerBlock: BN; +} + +const candidateSort = (a: Candidate, b: Candidate) => { + const voteComp = a.votes.cmp(b.votes); + if (voteComp !== 0) { + return voteComp; + } + + return a.publicKey.compare(b.publicKey); +}; + +export class NEOToken extends NEP17NativeContract { public readonly totalAmount: BN; // TODO: investigate this usage, its a strange decimal value in C# world. `0.2M`. Something to do with rounding. public readonly effectiveVoterTurnout = 0.2; @@ -16,14 +40,22 @@ export class NEOToken extends NEP5NativeContract { private readonly prefixes = { votersCount: Buffer.from([1]), candidate: Buffer.from([33]), - nextValidators: Buffer.from([14]), + committee: Buffer.from([14]), + gasPerBlock: Buffer.from([29]), + voterRewardPerCommittee: Buffer.from([23]), + }; + + private readonly ratios = { + neoHolderReward: 10, + committeeReward: 10, + voterReward: 10, }; public constructor(settings: BlockchainSettings) { super({ id: -1, - name: 'NEO', - symbol: 'neo', + name: 'NeoToken', + symbol: 'NEO', decimals: 0, }); @@ -46,108 +78,145 @@ export class NEOToken extends NEP5NativeContract { state: utils.getInteroperable(value, CandidateState.fromStackItem), })), filter((value) => value.state.registered), - // tslint:disable-next-line: no-useless-cast map(({ point, state }) => ({ publicKey: point, votes: state.votes })), toArray(), ) .toPromise(); } - public async getValidators(storage: NativeContractStorageContext): Promise { - const members = await this.getCommitteeMembers(storage); - - return members.slice(0, this.settings.validatorsCount).sort(common.ecPointCompare); - } - public async getCommittee(storage: NativeContractStorageContext): Promise { - // tslint:disable-next-line: prefer-immediate-return - const members = await this.getCommitteeMembers(storage); + const cache = await this.getCommitteeFromCache(storage); - return members.slice().sort(common.ecPointCompare); + return cache.members.map(({ publicKey }) => publicKey).sort(common.ecPointCompare); } public async getCommitteeAddress(storage: NativeContractStorageContext): Promise { - const committees = await this.getCommittee(storage); + const committee = await this.getCommittee(storage); return crypto.toScriptHash( - crypto.createMultiSignatureRedeemScript(committees.length - (committees.length - 1) / 2, committees), + crypto.createMultiSignatureRedeemScript(committee.length - (committee.length - 1) / 2, committee), ); } - public async unclaimedGas({ storages }: NativeContractStorageContext, account: UInt160, end: number) { - const storage = await storages.tryGet(this.createStorageKey(this.accountPrefix).addBuffer(account).toStorageKey()); - if (storage === undefined) { - return new BN(0); - } + public async getCommitteeFromCache({ storages }: NativeContractStorageContext): Promise { + const item = await storages.get(this.createStorageKey(this.prefixes.committee).toStorageKey()); - const state = utils.getInteroperable(storage, NEOAccountState.fromStackItem); + return utils.getInteroperable(item, CachedCommittee.fromStackItem); + } + + public async computeNextBlockValidators(storage: NativeContractStorageContext): Promise { + const committeeMembers = await this.computeCommitteeMembers(storage); - return this.calculateBonus(state.balance, state.balanceHeight.toNumber(), end); + return _.take( + committeeMembers.map(({ publicKey }) => publicKey), + this.settings.validatorsCount, + ) + .slice() + .sort(common.ecPointCompare); } - public async getNextBlockValidators({ storages }: NativeContractStorageContext): Promise { - const key = this.createStorageKey(this.prefixes.nextValidators).toStorageKey(); - const storage = await storages.tryGet(key); + public async unclaimedGas({ storages }: NativeContractStorageContext, account: UInt160, end: number) { + const storage = await storages.tryGet( + this.createStorageKey(this.basePrefixes.account).addBuffer(account).toStorageKey(), + ); if (storage === undefined) { - return this.settings.standbyValidators; + return new BN(0); } - return utils.getSerializableArrayFromStorageItem(storage, (reader) => reader.readECPoint()); - } + const state = utils.getInteroperable(storage, NEOAccountState.fromStackItem); - private async getCommitteeMembers(storage: NativeContractStorageContext): Promise { - const item = await storage.storages.get(this.createStorageKey(this.prefixes.votersCount).toStorageKey()); - const votersCount = new BN(item.value, 'le').toNumber(); - const voterTurnout = votersCount / this.totalAmount.toNumber(); - if (voterTurnout < this.effectiveVoterTurnout) { - return this.settings.standbyCommittee; - } + return this.calculateBonus({ + storages, + vote: state.voteTo, + value: state.balance, + start: state.balanceHeight.toNumber(), + end, + }); + } - const candidates = await this.getCandidates(storage); - if (candidates.length < this.settings.committeeMembersCount) { - return this.settings.standbyCommittee; - } + public async getNextBlockValidators(storage: NativeContractStorageContext): Promise { + const committeeCache = await this.getCommitteeFromCache(storage); - return _.sortBy(candidates, ['votes', ({ publicKey }) => common.ecPointToHex(publicKey)]) + return _.take(committeeCache.members, this.settings.validatorsCount) .map(({ publicKey }) => publicKey) - .slice(0, this.settings.committeeMembersCount); + .sort(common.ecPointCompare); } - private calculateBonus(value: BN, start: number, end: number) { + private async calculateBonus({ storages, vote, value, start, end }: CalculateBonusOptions) { if (value.isZero() || start >= end) { return new BN(0); } if (value.ltn(0)) { - // TODO: create a real error for here - throw new Error('negative value not supported'); + throw new InvalidFormatError('negative value not supported'); } - let amount = new BN(0); - let ustart = Math.floor(start / this.settings.decrementInterval); - if (ustart < this.settings.generationAmount.length) { - let istart = start % this.settings.decrementInterval; - let uend = Math.floor(end / this.settings.decrementInterval); - let iend = end % this.settings.decrementInterval; - if (uend >= this.settings.generationAmount.length) { - uend = this.settings.generationAmount.length; - iend = 0; - } - if (iend === 0) { - uend -= 1; - iend = this.settings.decrementInterval; - } - // tslint:disable-next-line: no-loop-statement - while (ustart < uend) { - amount = amount.addn((this.settings.decrementInterval - istart) * this.settings.generationAmount[ustart]); - ustart += 1; - istart = 0; + const neoHolderReward = await this.calculateNeoHolderReward(storages, value, start, end); + if (vote === undefined) { + return neoHolderReward; + } + + const border = this.createStorageKey(this.prefixes.voterRewardPerCommittee).addBuffer(vote).toSearchPrefix(); + const keyStart = this.createStorageKey(this.prefixes.voterRewardPerCommittee) + .addBuffer(vote) + .addUInt32BE(start) + .toSearchPrefix(); + const startRange = await storages.find$(border, keyStart).pipe(toArray()).toPromise(); + const startItem = startRange.length === 0 ? undefined : startRange[startRange.length - 1].value; + const startRewardPerNeo = startItem === undefined ? new BN(0) : new BN(startItem.value, 'le'); + + const keyEnd = this.createStorageKey(this.prefixes.voterRewardPerCommittee) + .addBuffer(vote) + .addUInt32BE(end) + .toSearchPrefix(); + const endRange = await storages.find$(border, keyEnd).pipe(toArray()).toPromise(); + const endItem = endRange.length === 0 ? undefined : endRange[endRange.length - 1].value; + const endRewardPerNeo = endItem === undefined ? new BN(0) : new BN(endItem.value, 'le'); + + return neoHolderReward.add(value.mul(endRewardPerNeo.sub(startRewardPerNeo)).div(this.totalAmount)); + } + + private async calculateNeoHolderReward(storages: Storages, value: BN, start: number, end: number) { + let sum = new BN(0); + const sortedGasRecords = await this.getSortedGasRecords(storages, end); + // tslint:disable-next-line: no-loop-statement + for (const { index, gasPerBlock } of sortedGasRecords) { + if (index > start) { + sum = sum.add(gasPerBlock.muln(end - index)); + } else { + sum = sum.add(gasPerBlock.muln(end - index)); + break; } - amount = amount.addn((iend - istart) * this.settings.generationAmount[ustart]); } - return common - .fixedFromDecimal(amount.mul(value), GASToken.decimals) - .mul(new BN(10 ** GASToken.decimals)) - .div(this.totalAmount); + return value.mul(sum).muln(this.ratios.neoHolderReward).divn(100).div(this.totalAmount); + } + + private async getSortedGasRecords(storages: Storages, end: number): Promise { + const prefix = this.createStorageKey(this.prefixes.gasPerBlock).addUInt32BE(end).toSearchPrefix(); + const boundary = this.createStorageKey(this.prefixes.gasPerBlock).toSearchPrefix(); + const range = await storages.find$(prefix, boundary).pipe(toArray()).toPromise(); + + return range + .map(({ key, value }) => ({ + index: key.key.readUInt32BE(4), + gasPerBlock: new BN(value.value, 'le'), + })) + .reverse(); + } + + private async computeCommitteeMembers({ storages }: NativeContractStorageContext): Promise { + const item = await storages.get(this.createStorageKey(this.prefixes.votersCount).toStorageKey()); + const votersCount = new BN(item.value).toNumber(); + const voterTurnout = votersCount / this.totalAmount.toNumber(); + const candidates = await this.getCandidates({ storages }); + + if (voterTurnout < this.effectiveVoterTurnout || candidates.length < this.settings.committeeMembersCount) { + return this.settings.standbyCommittee.map((member) => ({ + publicKey: member, + votes: candidates.find((candidate) => candidate.publicKey.equals(member))?.votes ?? new BN(0), + })); + } + + return _.take(candidates.slice().sort(candidateSort), this.settings.committeeMembersCount); } } diff --git a/packages/neo-one-node-native/src/NativeContainer.ts b/packages/neo-one-node-native/src/NativeContainer.ts index 3f17aa255e..144de0a2cd 100644 --- a/packages/neo-one-node-native/src/NativeContainer.ts +++ b/packages/neo-one-node-native/src/NativeContainer.ts @@ -1,16 +1,39 @@ +import { UInt160 } from '@neo-one/client-common'; import { BlockchainSettings } from '@neo-one/node-core'; +import { DesignationContract } from './DesignationContract'; import { GASToken } from './GASToken'; +import { ManagementContract } from './ManagementContract'; import { NEOToken } from './NEOToken'; +import { OracleContract } from './OracleContract'; import { PolicyContract } from './Policy'; export class NativeContainer { + public readonly Management: ManagementContract; public readonly NEO: NEOToken; public readonly GAS: GASToken; public readonly Policy: PolicyContract; + public readonly Oracle: OracleContract; + public readonly Designation: DesignationContract; + public readonly nativeHashes: readonly UInt160[]; public constructor(settings: BlockchainSettings) { + this.Management = new ManagementContract(); this.NEO = new NEOToken(settings); this.GAS = new GASToken(); this.Policy = new PolicyContract(); + this.Oracle = new OracleContract(); + this.Designation = new DesignationContract(); + this.nativeHashes = [ + this.Management.hash, + this.NEO.hash, + this.GAS.hash, + this.Policy.hash, + this.Oracle.hash, + this.Designation.hash, + ]; + } + + public isNative(hash: UInt160) { + return this.nativeHashes.some((nativeHash) => hash.equals(nativeHash)); } } diff --git a/packages/neo-one-node-native/src/NativeContract.ts b/packages/neo-one-node-native/src/NativeContract.ts index 363d4ea68d..8b46fc560c 100644 --- a/packages/neo-one-node-native/src/NativeContract.ts +++ b/packages/neo-one-node-native/src/NativeContract.ts @@ -1,4 +1,4 @@ -import { crypto, ScriptBuilder, UInt160 } from '@neo-one/client-common'; +import { common, crypto, ScriptBuilder, UInt160 } from '@neo-one/client-common'; import { KeyBuilder } from './KeyBuilder'; export interface NativeContractAdd { @@ -11,6 +11,8 @@ export abstract class NativeContract { public readonly script: Buffer; public readonly hash: UInt160; public readonly id: number; + // newly added property will see if it is relevant on our end + // public readonly activeBlockIndex: number; public constructor({ name, id }: NativeContractAdd) { this.name = name; @@ -18,10 +20,10 @@ export abstract class NativeContract { const builder = new ScriptBuilder(); builder.emitPushString(this.name); - builder.emitSysCall('Neo.Native.Call'); + builder.emitSysCall('System.Contract.CallNative'); this.script = builder.build(); - this.hash = crypto.toScriptHash(this.script); + this.hash = crypto.getContractHash(common.ZERO_UINT160, this.script); } protected createStorageKey(prefix: Buffer) { diff --git a/packages/neo-one-node-native/src/Nep5.ts b/packages/neo-one-node-native/src/Nep17.ts similarity index 66% rename from packages/neo-one-node-native/src/Nep5.ts rename to packages/neo-one-node-native/src/Nep17.ts index 642c6fbcb8..c653725c41 100644 --- a/packages/neo-one-node-native/src/Nep5.ts +++ b/packages/neo-one-node-native/src/Nep17.ts @@ -3,20 +3,22 @@ import { NativeContractStorageContext } from '@neo-one/node-core'; import { BN } from 'bn.js'; import { NativeContract, NativeContractAdd } from './NativeContract'; -export interface NEP5NativeContractAdd extends NativeContractAdd { +export interface NEP17NativeContractAdd extends NativeContractAdd { readonly symbol: string; readonly decimals: number; } -export abstract class NEP5NativeContract extends NativeContract { +export abstract class NEP17NativeContract extends NativeContract { public readonly symbol: string; public readonly decimals: number; public readonly factor: BN; - protected readonly totalSupplyPrefix = Buffer.from([11]); - protected readonly accountPrefix = Buffer.from([20]); + protected readonly basePrefixes = { + totalSupply: Buffer.from([11]), + account: Buffer.from([20]), + }; - public constructor(options: NEP5NativeContractAdd) { + public constructor(options: NEP17NativeContractAdd) { super(options); this.symbol = options.symbol; this.decimals = options.decimals; @@ -24,7 +26,7 @@ export abstract class NEP5NativeContract extends NativeContract { } public async totalSupply({ storages }: NativeContractStorageContext): Promise { - const storage = await storages.tryGet(this.createStorageKey(this.totalSupplyPrefix).toStorageKey()); + const storage = await storages.tryGet(this.createStorageKey(this.basePrefixes.totalSupply).toStorageKey()); if (storage === undefined) { return new BN(0); } @@ -33,7 +35,9 @@ export abstract class NEP5NativeContract extends NativeContract { } public async balanceOf({ storages }: NativeContractStorageContext, account: UInt160): Promise { - const storage = await storages.tryGet(this.createStorageKey(this.accountPrefix).addBuffer(account).toStorageKey()); + const storage = await storages.tryGet( + this.createStorageKey(this.basePrefixes.totalSupply).addBuffer(account).toStorageKey(), + ); if (storage === undefined) { return new BN(0); } diff --git a/packages/neo-one-node-native/src/OracleContract.ts b/packages/neo-one-node-native/src/OracleContract.ts new file mode 100644 index 0000000000..0f42a77488 --- /dev/null +++ b/packages/neo-one-node-native/src/OracleContract.ts @@ -0,0 +1,96 @@ +import { crypto } from '@neo-one/client-common'; +import { + assertArrayStackItem, + NativeContractStorageContext, + OracleRequest, + OracleRequestResults, + StackItem, + utils, +} from '@neo-one/node-core'; +import { BN } from 'bn.js'; +import { map, toArray } from 'rxjs/operators'; +import { NativeContract } from './NativeContract'; + +export class OracleContract extends NativeContract { + private readonly prefixes = { + requestId: Buffer.from([9]), + request: Buffer.from([7]), + idList: Buffer.from([6]), + }; + + // applicationEngine constants that might be used later + // private maxUrlLength = 256 as const; + // private maxFilterLength = 128 as const; + // private maxCallbackLength = 32 as const; + // private maxUserDataLength = 512 as const; + // private oracleRequestPrice = common.fixed8FromDecimal('.5'); + + public constructor() { + super({ + id: -4, + name: 'OracleContract', + }); + } + + public async getRequest({ storages }: NativeContractStorageContext, id: BN) { + const item = await storages.tryGet(this.createStorageKey(this.prefixes.request).addUInt64LE(id).toStorageKey()); + + if (item === undefined) { + return undefined; + } + + return utils.getInteroperable(item, OracleRequest.fromStackItem); + } + + public async getRequests({ storages }: NativeContractStorageContext): Promise { + return storages + .find$(this.createStorageKey(this.prefixes.request).toSearchPrefix()) + .pipe( + map( + ({ key, value }) => + // tslint:disable-next-line: no-useless-cast + [new BN(key.key.slice(1), 'le'), utils.getInteroperable(value, OracleRequest.fromStackItem)] as const, + ), + toArray(), + ) + .toPromise(); + } + + public async getRequestsByUrl({ storages }: NativeContractStorageContext, url: string) { + const maybeListItem = await storages.tryGet( + this.createStorageKey(this.prefixes.idList).addBuffer(this.getUrlHash(url)).toStorageKey(), + ); + if (maybeListItem === undefined) { + return []; + } + + const { list } = utils.getInteroperable(maybeListItem, IdList.fromStackItem); + + return Promise.all( + list.map(async (id) => { + const request = await storages.get(this.createStorageKey(this.prefixes.request).addUInt64LE(id).toStorageKey()); + + return utils.getInteroperable(request, OracleRequest.fromStackItem); + }), + ); + } + + private getUrlHash(url: string) { + return crypto.hash160(Buffer.from(url, 'utf8')); + } +} + +class IdList { + public static fromStackItem(stackItem: StackItem): IdList { + const { array } = assertArrayStackItem(stackItem); + const list = array.map((item) => item.getInteger()); + + return new IdList(list); + } + + public readonly list: readonly BN[]; + + public constructor(list: readonly BN[]) { + this.list = list; + } +} diff --git a/packages/neo-one-node-native/src/Policy.ts b/packages/neo-one-node-native/src/Policy.ts index 9951682782..1fcd3fc696 100644 --- a/packages/neo-one-node-native/src/Policy.ts +++ b/packages/neo-one-node-native/src/Policy.ts @@ -1,5 +1,5 @@ import { common, UInt160 } from '@neo-one/client-common'; -import { NativeContractStorageContext, utils } from '@neo-one/node-core'; +import { NativeContractStorageContext } from '@neo-one/node-core'; import { BN } from 'bn.js'; import { GASToken } from './GASToken'; import { NativeContract } from './NativeContract'; @@ -9,15 +9,20 @@ export class PolicyContract extends NativeContract { private readonly prefixes = { maxTransactionsPerBlock: Buffer.from([23]), feePerByte: Buffer.from([10]), - blockedAccounts: Buffer.from([15]), + blockedAccount: Buffer.from([15]), maxBlockSize: Buffer.from([12]), maxBlockSystemFee: Buffer.from([17]), + execFeeFactor: Buffer.from([18]), + storagePrice: Buffer.from([19]), }; + private readonly defaultExecFeeFactor = 30; + private readonly defaultStoragePrice = 100000; + public constructor() { super({ id: -3, - name: 'Policy', + name: 'PolicyContract', }); } @@ -57,12 +62,29 @@ export class PolicyContract extends NativeContract { return new BN(item.value); } - public async getBlockedAccounts({ storages }: NativeContractStorageContext): Promise { - const item = await storages.tryGet(this.createStorageKey(this.prefixes.blockedAccounts).toStorageKey()); - if (item !== undefined) { - return utils.getSerializableArrayFromStorageItem(item, (reader) => reader.readUInt160()); + public async getExecFeeFactor({ storages }: NativeContractStorageContext) { + const item = await storages.tryGet(this.createStorageKey(this.prefixes.execFeeFactor).toStorageKey()); + if (item === undefined) { + return this.defaultExecFeeFactor; + } + + return new BN(item.value).toNumber(); + } + + public async getStoragePrice({ storages }: NativeContractStorageContext) { + const item = await storages.tryGet(this.createStorageKey(this.prefixes.storagePrice).toStorageKey()); + if (item === undefined) { + return this.defaultStoragePrice; } - return []; + return new BN(item.value).toNumber(); + } + + public async isBlocked({ storages }: NativeContractStorageContext, account: UInt160) { + const item = await storages.tryGet( + this.createStorageKey(this.prefixes.blockedAccount).addBuffer(account).toStorageKey(), + ); + + return item !== undefined; } } diff --git a/packages/neo-one-node-native/src/index.ts b/packages/neo-one-node-native/src/index.ts index 5a77ae007d..0c91f79dd7 100644 --- a/packages/neo-one-node-native/src/index.ts +++ b/packages/neo-one-node-native/src/index.ts @@ -4,5 +4,5 @@ export * from './KeyBuilder'; export * from './NativeContainer'; export * from './NativeContract'; export * from './NEOToken'; -export * from './Nep5'; +export * from './Nep17'; export * from './Policy'; diff --git a/packages/neo-one-node-neo-settings/src/common.ts b/packages/neo-one-node-neo-settings/src/common.ts index 2a9aca577f..e08ecdb87e 100644 --- a/packages/neo-one-node-neo-settings/src/common.ts +++ b/packages/neo-one-node-neo-settings/src/common.ts @@ -1,5 +1,5 @@ -import { common as clientCommon, crypto, Op, ScriptBuilder, UInt160, WitnessScopeModel } from '@neo-one/client-common'; -import { Block, ConsensusData, Signer, Transaction, Witness } from '@neo-one/node-core'; +import { common as clientCommon, Op, UInt160 } from '@neo-one/client-common'; +import { Block, ConsensusData, Witness } from '@neo-one/node-core'; import { BN } from 'bn.js'; export const GENERATION_AMOUNT: readonly number[] = [6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; @@ -16,79 +16,11 @@ interface Options { readonly messageMagic: number; } -// const ONE_HUNDRED_MILLION = clientCommon.fixed8FromDecimal(100000000); - -// const getGoverningToken = () => { -// const scriptBuilder = new ScriptBuilder(); -// scriptBuilder.emitOp('PUSH1'); -// const admin = crypto.toScriptHash(scriptBuilder.build()); - -// return new RegisterTransaction({ -// asset: { -// type: AssetType.GoverningToken, -// name: '[{"lang":"zh-CN","name":"小蚁股"},{"lang":"en","name":"AntShare"}]', -// amount: ONE_HUNDRED_MILLION, -// precision: 0, -// owner: clientCommon.ECPOINT_INFINITY, -// admin, -// }, -// }); -// }; - -// const getUtilityToken = () => { -// const scriptBuilder = new ScriptBuilder(); -// scriptBuilder.emitOp('PUSH0'); -// const admin = crypto.toScriptHash(scriptBuilder.build()); - -// return new RegisterTransaction({ -// asset: { -// type: AssetType.UtilityToken, -// name: '[{"lang":"zh-CN","name":"小蚁币"},{"lang":"en","name":"AntCoin"}]', -// amount: ONE_HUNDRED_MILLION, -// precision: 8, -// owner: clientCommon.ECPOINT_INFINITY, -// admin, -// }, -// }); -// }; - -// interface GensisBlockOptions { -// readonly privateNet?: boolean; -// readonly governingToken: RegisterTransaction; -// readonly utilityToken: RegisterTransaction; -// readonly address: UInt160; -// readonly consensusAddress: UInt160; -// } - const getDeployWitness = () => new Witness({ invocation: Buffer.from([]), verification: Buffer.from([Op.PUSH1]), }); - -const getDeployNativeContracts = (messageMagic: number) => { - const scriptBuilder = new ScriptBuilder(); - scriptBuilder.emitSysCall('Neo.Native.Deploy'); - const script = scriptBuilder.build(); - - return new Transaction({ - version: 0, - script, - systemFee: new BN(0), - networkFee: new BN(0), - signers: [ - new Signer({ - account: crypto.hash160(Buffer.from([Op.PUSH1])), - scopes: WitnessScopeModel.None, - }), - ], - attributes: [], - witnesses: [getDeployWitness()], - validUntilBlock: 0, - messageMagic, - }); -}; - interface GetGenesisBlockOptions { readonly consensusAddress: UInt160; readonly messageMagic: number; @@ -106,7 +38,7 @@ const getGenesisBlock = ({ consensusAddress, messageMagic }: GetGenesisBlockOpti primaryIndex: 0, nonce: new BN(2083236893), }), - transactions: [getDeployNativeContracts(messageMagic)], + transactions: [], messageMagic, }); @@ -122,77 +54,3 @@ export const common = ({ privateNet, consensusAddress, messageMagic }: Options) maxTransactionsPerBlock: MAX_TRANSACTION_PER_BLOCK, memoryPoolMaxTransactions: 50000, }); - -// const getGenesisBlock = ({ privateNet, governingToken, utilityToken, address, consensusAddress }: GensisBlockOptions) => -// new Block({ -// previousHash: clientCommon.ZERO_UINT256, -// timestamp: 1468595301, -// index: 0, -// consensusData: new BN(2083236893), -// nextConsensus: consensusAddress, -// script: new Witness({ -// invocation: Buffer.from([]), -// verification: Buffer.from([Op.PUSH1]), -// }), -// transactions: [ -// new MinerTransaction({ nonce: 2083236893 }), -// governingToken, -// utilityToken, -// new IssueTransaction({ -// version: 0, -// outputs: [ -// new Output({ -// asset: governingToken.hash, -// value: governingToken.asset.amount, -// address, -// }), -// ], -// scripts: [ -// new Witness({ -// invocation: Buffer.from([]), -// verification: Buffer.from([Op.PUSH1]), -// }), -// ], -// }), -// privateNet -// ? new IssueTransaction({ -// outputs: [ -// new Output({ -// asset: utilityToken.hash, -// value: ISSUE_AMOUNT_PRIVATE, -// address, -// }), -// ], - -// scripts: [ -// new Witness({ -// invocation: Buffer.from([]), -// verification: Buffer.from([Op.PUSH0]), -// }), -// ], -// }) -// : undefined, -// ].filter(utils.notNull), -// }); - -// export const common = ({ privateNet, address, consensusAddress }: Options) => { -// const utilityToken = getUtilityToken(); -// const governingToken = getGoverningToken(); - -// return { -// genesisBlock: getGenesisBlock({ -// privateNet, -// governingToken, -// utilityToken, -// address, -// consensusAddress, -// }), -// governingToken, -// utilityToken, -// decrementInterval: DECREMENT_INTERVAL, -// generationAmount: privateNet ? GENERATION_AMOUNT_PRIVATE : GENERATION_AMOUNT, -// secondsPerBlock: SECONDS_PER_BLOCK, -// maxTransactionsPerBlock: MAX_TRANSACTION_PER_BLOCK, -// memPoolSize: 50000, -// }; -// }; diff --git a/packages/neo-one-node-protocol/src/Message.ts b/packages/neo-one-node-protocol/src/Message.ts index 735106bd7e..17da7813d8 100644 --- a/packages/neo-one-node-protocol/src/Message.ts +++ b/packages/neo-one-node-protocol/src/Message.ts @@ -32,6 +32,19 @@ import { VersionPayload, } from './payload'; +const tryCompression = ({ command }: MessageValue) => { + return ( + command === Command.Block || + command === Command.Consensus || + command === Command.Transaction || + command === Command.Headers || + command === Command.Addr || + command === Command.MerkleBlock || + command === Command.FilterLoad || + command === Command.FilterAdd + ); +}; + export type MessageValue = | { readonly command: Command.Addr; readonly payload: AddrPayload } | { readonly command: Command.Block; readonly payload: Block } @@ -188,7 +201,7 @@ export class Message implements SerializableWire { public static create(value: MessageValue): Message { // tslint:disable-next-line: no-any const payloadBuffer = (value as any)?.payload?.serializeWire() ?? Buffer.alloc(0); - if (payloadBuffer.length > compressionMinSize) { + if (tryCompression(value) && payloadBuffer.length > compressionMinSize) { const compressed = lz4Helper.compress(payloadBuffer); if (compressed.length < payloadBuffer.length - compressionThreshold) { return new Message({ diff --git a/packages/neo-one-node-protocol/src/Node.ts b/packages/neo-one-node-protocol/src/Node.ts index 43d5f16961..326ecab1e8 100644 --- a/packages/neo-one-node-protocol/src/Node.ts +++ b/packages/neo-one-node-protocol/src/Node.ts @@ -135,11 +135,6 @@ const compareTransactionAndFees = (val1: TransactionAndFee, val2: TransactionAnd return val1.transaction.hash.compare(val2.transaction.hash); }; -// TODO: We should note what some of these settings used to be, I've made them more aggressive -// while we are testing syncing; and then testnet can be a bit slow. -// const GET_BLOCKS_THROTTLE_MS = 1000; -// const GET_BLOCKS_TIME_MS = 5000; - const MEM_POOL_SIZE = 5000; const GET_BLOCKS_COUNT = 500; // Assume that we get 500 back, but if not, at least request every 10 seconds @@ -162,6 +157,8 @@ export class Node implements INode { public readonly getNewVerificationContext: () => TransactionVerificationContext; // tslint:disable-next-line readonly-keyword private mutableMemPool: { [hash: string]: Transaction }; + // tslint:disable-next-line: readonly-keyword + private readonly mutableSentCommands: { [k: number]: boolean } = {}; private readonly transactionVerificationContext: TransactionVerificationContext; private readonly network: Network; private readonly options: Options; @@ -498,6 +495,7 @@ export class Node implements INode { private sendMessage(peer: Peer | ConnectedPeer, message: Message): void { peer.write(message.serializeWire()); + this.mutableSentCommands[message.value.command] = true; } private readonly negotiate = async (peer: Peer): Promise> => { this.sendMessage( @@ -847,6 +845,11 @@ export class Node implements INode { } private onAddrMessageReceived(addr: AddrPayload): void { + if (!this.mutableSentCommands[Command.GetAddr]) { + return; + } + this.mutableSentCommands[Command.GetAddr] = false; + addr.addressList .filter((address) => !LOCAL_HOST_ADDRESSES.has(address.address)) .filter((address) => address.port > 0) diff --git a/packages/neo-one-node-rpc-handler/src/createHandler.ts b/packages/neo-one-node-rpc-handler/src/createHandler.ts index 35324bebd5..40ec10568e 100644 --- a/packages/neo-one-node-rpc-handler/src/createHandler.ts +++ b/packages/neo-one-node-rpc-handler/src/createHandler.ts @@ -9,7 +9,6 @@ import { toVMStateJSON, TransactionJSON, TransactionReceiptJSON, - utils as commonUtils, VerboseTransactionJSON, VerifyResultModel, } from '@neo-one/client-common'; @@ -20,10 +19,9 @@ import { CallReceipt, getEndpointConfig, NativeContainer, - Nep5Transfer, - Nep5TransferKey, + Nep17Transfer, + Nep17TransferKey, Node, - Signer, Signers, StackItem, stackItemToJSON, @@ -115,9 +113,9 @@ const RPC_METHODS: { readonly [key: string]: string } = { sendmany: 'sendmany', sendtoaddress: 'sendtoaddress', - // NEP5 - getnep5transfers: 'getnep5transfers', - getnep5balances: 'getnep5balances', + // NEP17 + getnep17transfers: 'getnep17transfers', + getnep17balances: 'getnep17balances', // TODO: I want to say both of these can be removed since you can make changes to policy contract storage updatesettings: 'updatesettings', @@ -125,6 +123,7 @@ const RPC_METHODS: { readonly [key: string]: string } = { // NEO•ONE getfeeperbyte: 'getfeeperbyte', + getexecfeefactor: 'getexecfeefactor', getverificationcost: 'getverificationcost', relaytransaction: 'relaytransaction', getallstorage: 'getallstorage', @@ -141,7 +140,7 @@ const RPC_METHODS: { readonly [key: string]: string } = { INVALID: 'INVALID', }; -const mapToTransfers = ({ key, value }: { readonly key: Nep5TransferKey; readonly value: Nep5Transfer }) => ({ +const mapToTransfers = ({ key, value }: { readonly key: Nep17TransferKey; readonly value: Nep17Transfer }) => ({ timestamp: key.timestampMS.toNumber(), assethash: common.uInt160ToString(key.assetScriptHash), transferaddress: scriptHashToAddress(common.uInt160ToString(value.userScriptHash)), @@ -306,6 +305,7 @@ export const createHandler = ({ try { const stack = stackIn.map((item: StackItem) => stackItemToJSON(item, undefined)); + return { script: script.toString('hex'), state: toVMStateJSON(state), @@ -415,7 +415,7 @@ export const createHandler = ({ }, [RPC_METHODS.getcontractstate]: async (args) => { const hash = JSONHelper.readUInt160(args[0]); - const contract = await blockchain.contracts.tryGet(hash); + const contract = await native.Management.getContract({ storages: blockchain.storages }, hash); if (contract === undefined) { throw new JSONRPCError(-100, 'Unknown contract'); } @@ -463,7 +463,7 @@ export const createHandler = ({ }, [RPC_METHODS.getstorage]: async (args) => { const hash = JSONHelper.readUInt160(args[0]); - const state = await blockchain.contracts.tryGet(hash); + const state = await native.Management.getContract({ storages: blockchain.storages }, hash); if (state === undefined) { return undefined; } @@ -481,7 +481,7 @@ export const createHandler = ({ }, [RPC_METHODS.getvalidators]: async () => { const [validators, candidates] = await Promise.all([ - native.NEO.getValidators({ storages: blockchain.storages }), + native.NEO.computeNextBlockValidators({ storages: blockchain.storages }), native.NEO.getCandidates({ storages: blockchain.storages }), ]); @@ -659,8 +659,8 @@ export const createHandler = ({ throw new JSONRPCError(-101, 'Not implemented'); }, - // Nep5 - [RPC_METHODS.getnep5transfers]: async (args) => { + // Nep17 + [RPC_METHODS.getnep17transfers]: async (args) => { const addressVersion = blockchain.settings.addressVersion; const { address, scriptHash } = getScriptHashAndAddress(args[0], addressVersion); @@ -678,11 +678,11 @@ export const createHandler = ({ const gte = Buffer.concat([scriptHash, startTimeBytes]); const lte = Buffer.concat([scriptHash, endTimeBytes]); - const sentPromise = blockchain.nep5TransfersSent + const sentPromise = blockchain.nep17TransfersSent .find$(gte, lte) .pipe(take(1000), map(mapToTransfers), toArray()) .toPromise(); - const receivedPromise = blockchain.nep5TransfersReceived + const receivedPromise = blockchain.nep17TransfersReceived .find$(gte, lte) .pipe(take(1000), map(mapToTransfers), toArray()) .toPromise(); @@ -695,13 +695,16 @@ export const createHandler = ({ address, }; }, - [RPC_METHODS.getnep5balances]: async (args) => { + [RPC_METHODS.getnep17balances]: async (args) => { const addressVersion = blockchain.settings.addressVersion; const { address, scriptHash } = getScriptHashAndAddress(args[0], addressVersion); - const storedBalances = await blockchain.nep5Balances.find$(scriptHash).pipe(toArray()).toPromise(); + const storedBalances = await blockchain.nep17Balances.find$(scriptHash).pipe(toArray()).toPromise(); const validBalances = await Promise.all( storedBalances.map(async ({ key, value }) => { - const assetStillExists = await blockchain.contracts.tryGet(key.assetScriptHash); + const assetStillExists = await native.Management.getContract( + { storages: blockchain.storages }, + key.assetScriptHash, + ); if (!assetStillExists) { return undefined; } @@ -779,7 +782,7 @@ export const createHandler = ({ if (hash.equals(NEO) || hash.equals(GAS) || hash.equals(Policy)) { throw new Error("Can't get all storage for native contracts."); } - const contract = await blockchain.contracts.tryGet(hash); + const contract = await native.Management.getContract({ storages: blockchain.storages }, hash); if (contract === undefined) { return []; } @@ -886,6 +889,7 @@ export const createHandler = ({ return feePerByte.toString(); }, + [RPC_METHODS.getexecfeefactor]: async () => native.Policy.getExecFeeFactor({ storages: blockchain.storages }), [RPC_METHODS.getverificationcost]: async (args) => { const hash = JSONHelper.readUInt160(args[0]); const transaction = Transaction.deserializeWire({ diff --git a/packages/neo-one-node-storage-common/src/keys.ts b/packages/neo-one-node-storage-common/src/keys.ts index 94986eb14b..4bc05d799e 100644 --- a/packages/neo-one-node-storage-common/src/keys.ts +++ b/packages/neo-one-node-storage-common/src/keys.ts @@ -1,5 +1,5 @@ import { common, InvalidFormatError, UInt160, UInt256 } from '@neo-one/client-common'; -import { BlockKey, Nep5BalanceKey, Nep5TransferKey, StorageKey, StreamOptions } from '@neo-one/node-core'; +import { BlockKey, Nep17BalanceKey, Nep17TransferKey, StorageKey, StreamOptions } from '@neo-one/node-core'; import { BN } from 'bn.js'; export enum Prefix { @@ -12,9 +12,9 @@ export enum Prefix { CurrentHeader = 0xc1, ContractID = 0xc2, ConsensusState = 0xf4, - Nep5Balance = 0xf8, - Nep5TransferSent = 0xf9, - Nep5TransferReceived = 0xfa, + Nep17Balance = 0xf8, + Nep17TransferSent = 0xf9, + Nep17TransferReceived = 0xfa, ApplicationLog = 0xfb, // Custom internal prefix. Can be changed. // NEO•ONE prefix, watch out for future collisions with https://github.com/neo-project/neo/blob/master/src/neo/Persistence/Prefixes.cs @@ -101,19 +101,19 @@ const createStorageKey = getCreateKey({ prefix: Prefix.Storage, }); -const createNep5BalanceKey = getCreateKey({ +const createNep17BalanceKey = getCreateKey({ serializeKey: (key) => key.serializeWire(), - prefix: Prefix.Nep5Balance, + prefix: Prefix.Nep17Balance, }); -const createNep5TransferSentKey = getCreateKey({ +const createNep17TransferSentKey = getCreateKey({ serializeKey: (key) => key.serializeWire(), - prefix: Prefix.Nep5TransferSent, + prefix: Prefix.Nep17TransferSent, }); -const createNep5TransferReceivedKey = getCreateKey({ +const createNep17TransferReceivedKey = getCreateKey({ serializeKey: (key) => key.serializeWire(), - prefix: Prefix.Nep5TransferReceived, + prefix: Prefix.Nep17TransferReceived, }); const createHeaderHashListKey = getCreateKey({ @@ -142,14 +142,14 @@ const maxBlockKey = createBlockKey({ hashOrIndex: common.MAX_UINT256 }); const getStorageSearchRange = createGetSearchRange(Prefix.Storage); -const getAllNep5BalanceSearchRange = { - gte: Buffer.from([Prefix.Nep5Balance]), - lte: Buffer.from([Prefix.Nep5TransferSent]), +const getAllNep17BalanceSearchRange = { + gte: Buffer.from([Prefix.Nep17Balance]), + lte: Buffer.from([Prefix.Nep17TransferSent]), }; -const getNep5BalanceSearchRange = createGetSearchRange(Prefix.Nep5Balance); -const getNep5TransferReceivedSearchRange = createGetSearchRange(Prefix.Nep5TransferReceived); -const getNep5TransferSentSearchRange = createGetSearchRange(Prefix.Nep5TransferSent); +const getNep17BalanceSearchRange = createGetSearchRange(Prefix.Nep17Balance); +const getNep17TransferReceivedSearchRange = createGetSearchRange(Prefix.Nep17TransferReceived); +const getNep17TransferSentSearchRange = createGetSearchRange(Prefix.Nep17TransferSent); const createApplicationLogKey = getCreateKey({ serializeKey: (key) => key, @@ -158,18 +158,18 @@ const createApplicationLogKey = getCreateKey({ export const keys = { createBlockKey, - createNep5BalanceKey, - createNep5TransferSentKey, - createNep5TransferReceivedKey, + createNep17BalanceKey, + createNep17TransferSentKey, + createNep17TransferReceivedKey, createApplicationLogKey, createTransactionKey, createContractKey, createStorageKey, getStorageSearchRange, - getNep5BalanceSearchRange, - getAllNep5BalanceSearchRange, - getNep5TransferReceivedSearchRange, - getNep5TransferSentSearchRange, + getNep17BalanceSearchRange, + getAllNep17BalanceSearchRange, + getNep17TransferReceivedSearchRange, + getNep17TransferSentSearchRange, createHeaderHashListKey, blockHashIndexKey, headerHashIndexKey, diff --git a/packages/neo-one-node-storage-levelup/src/__tests__/levelUpStorage.test.ts b/packages/neo-one-node-storage-levelup/src/__tests__/levelUpStorage.test.ts index f12af4ca80..32c0dac66d 100644 --- a/packages/neo-one-node-storage-levelup/src/__tests__/levelUpStorage.test.ts +++ b/packages/neo-one-node-storage-levelup/src/__tests__/levelUpStorage.test.ts @@ -2,8 +2,8 @@ import { common } from '@neo-one/client-common'; import { AddChange, DeleteChange, - Nep5Balance, - Nep5BalanceKey, + Nep17Balance, + Nep17BalanceKey, Storage, StorageItem, StorageKey, @@ -16,7 +16,7 @@ import { storage as levelUpStorage } from '../'; describe('levelUpStorage', () => { let storage: Storage; beforeEach(async () => { - storage = levelUpStorage({ db: LevelUp(MemDown()), context: { messageMagic: 1953787457 } }); + storage = levelUpStorage({ db: LevelUp(MemDown()), context: { messageMagic: 1953787457, validatorsCount: 7 } }); }); test('deleted items are undefined', async () => { const hash = common.bufferToUInt160(Buffer.from('3775292229eccdf904f16fff8e83e7cffdc0f0ce', 'hex')); @@ -51,23 +51,23 @@ describe('levelUpStorage', () => { expect(thirdGet).toEqual(undefined); }); - test('Can add and retrieve Nep5Balance', async () => { - const value = new Nep5Balance({ balanceBuffer: new BN(10).toBuffer('le'), lastUpdatedBlock: 1 }); - const key = new Nep5BalanceKey({ + test('Can add and retrieve Nep17Balance', async () => { + const value = new Nep17Balance({ balanceBuffer: new BN(10).toBuffer('le'), lastUpdatedBlock: 1 }); + const key = new Nep17BalanceKey({ userScriptHash: common.bufferToUInt160(Buffer.from('3775292229eccdf904f16fff8e83e7cffdc0f0ce', 'hex')), assetScriptHash: common.bufferToUInt160(Buffer.from('3775292229eccdf904f16fff8e83e7cffdc0f0ce', 'hex')), }); const addChange: AddChange = { - type: 'nep5Balance', + type: 'nep17Balance', key, value, }; - const firstGet = await storage.nep5Balances.tryGet(key); + const firstGet = await storage.nep17Balances.tryGet(key); expect(firstGet).toBeUndefined(); await storage.commit([{ type: 'add', change: addChange, subType: 'add' }]); - const secondGet = await storage.nep5Balances.tryGet(key); + const secondGet = await storage.nep17Balances.tryGet(key); expect(secondGet).toBeDefined(); expect(secondGet?.balance.toString()).toEqual('10'); expect(secondGet?.lastUpdatedBlock).toEqual(1); diff --git a/packages/neo-one-node-storage-levelup/src/__tests__/rocksDBStorage.test.ts b/packages/neo-one-node-storage-levelup/src/__tests__/rocksDBStorage.test.ts index 0e4192a7f5..5bccba3b13 100644 --- a/packages/neo-one-node-storage-levelup/src/__tests__/rocksDBStorage.test.ts +++ b/packages/neo-one-node-storage-levelup/src/__tests__/rocksDBStorage.test.ts @@ -8,7 +8,7 @@ describe('levelUpStorage', () => { let storage: Storage; beforeEach(async () => { const rocks = new RocksDB('/Users/danielbyrne/Desktop/test-location'); - storage = levelUpStorage({ db: LevelUp(rocks), context: { messageMagic: 1953787457 } }); + storage = levelUpStorage({ db: LevelUp(rocks), context: { messageMagic: 1953787457, validatorsCount: 7 } }); }); test('deleted items are undefined', async () => { const hash = common.bufferToUInt160(Buffer.from('3775292229eccdf904f16fff8e83e7cffdc0f0ce', 'hex')); @@ -16,7 +16,6 @@ describe('levelUpStorage', () => { const value = Buffer.from('5f8d70', 'hex'); const firstGet = await storage.storages.tryGet(key); - console.log(firstGet); expect(firstGet).toEqual(undefined); const storageItem = new StorageItem({ @@ -31,7 +30,6 @@ describe('levelUpStorage', () => { await storage.commit([{ type: 'add', change: addChange, subType: 'add' }]); const secondGet = await storage.storages.tryGet(key); - console.log(secondGet); expect(JSON.stringify(secondGet)).toEqual(JSON.stringify(storageItem)); const deleteChange: DeleteChange = { diff --git a/packages/neo-one-node-storage-levelup/src/convertChange.ts b/packages/neo-one-node-storage-levelup/src/convertChange.ts index d02fbfa2b5..affc29c401 100644 --- a/packages/neo-one-node-storage-levelup/src/convertChange.ts +++ b/packages/neo-one-node-storage-levelup/src/convertChange.ts @@ -12,29 +12,29 @@ import { UnknownChangeTypeError, UnknownTypeError } from './errors'; const convertAddChange = (change: AddChange): readonly PutBatch[] => { switch (change.type) { - case 'nep5Balance': + case 'nep17Balance': return [ { type: 'put', - key: keys.createNep5BalanceKey(change.key), + key: keys.createNep17BalanceKey(change.key), value: change.value.serializeWire(), }, ]; - case 'nep5TransferReceived': + case 'nep17TransferReceived': return [ { type: 'put', - key: keys.createNep5TransferReceivedKey(change.key), + key: keys.createNep17TransferReceivedKey(change.key), value: change.value.serializeWire(), }, ]; - case 'nep5TransferSent': + case 'nep17TransferSent': return [ { type: 'put', - key: keys.createNep5TransferSentKey(change.key), + key: keys.createNep17TransferSentKey(change.key), value: change.value.serializeWire(), }, ]; @@ -140,10 +140,10 @@ const convertDeleteChange = (change: DeleteChange): DelBatch => { key: keys.createStorageKey(change.key), }; - case 'nep5Balance': + case 'nep17Balance': return { type: 'del', - key: keys.createNep5BalanceKey(change.key), + key: keys.createNep17BalanceKey(change.key), }; default: diff --git a/packages/neo-one-node-storage-levelup/src/levelUpStorage.ts b/packages/neo-one-node-storage-levelup/src/levelUpStorage.ts index 3d14a4f9a0..3ad334f426 100644 --- a/packages/neo-one-node-storage-levelup/src/levelUpStorage.ts +++ b/packages/neo-one-node-storage-levelup/src/levelUpStorage.ts @@ -9,10 +9,10 @@ import { HashIndexState, HeaderHashList, // TransactionData, - Nep5Balance, - Nep5BalanceKey, - Nep5Transfer, - Nep5TransferKey, + Nep17Balance, + Nep17BalanceKey, + Nep17Transfer, + Nep17TransferKey, Storage, StorageItem, StorageKey, @@ -86,41 +86,41 @@ export const levelUpStorage = ({ db, context }: LevelUpStorageOptions): Storage return { blocks, - nep5Balances: read.createReadAllFindStorage({ + nep17Balances: read.createReadAllFindStorage({ db, - searchRange: keys.getAllNep5BalanceSearchRange, - getSearchRange: keys.getNep5BalanceSearchRange, - serializeKey: keys.createNep5BalanceKey, + searchRange: keys.getAllNep17BalanceSearchRange, + getSearchRange: keys.getNep17BalanceSearchRange, + serializeKey: keys.createNep17BalanceKey, deserializeValue: (buffer) => - Nep5Balance.deserializeWire({ + Nep17Balance.deserializeWire({ context, buffer, }), - deserializeKey: (buffer) => Nep5BalanceKey.deserializeWire({ context, buffer }), + deserializeKey: (buffer) => Nep17BalanceKey.deserializeWire({ context, buffer }), }), - nep5TransfersReceived: read.createReadFindStorage({ + nep17TransfersReceived: read.createReadFindStorage({ db, - getSearchRange: keys.getNep5TransferReceivedSearchRange, - serializeKey: keys.createNep5TransferReceivedKey, + getSearchRange: keys.getNep17TransferReceivedSearchRange, + serializeKey: keys.createNep17TransferReceivedKey, deserializeValue: (buffer) => - Nep5Transfer.deserializeWire({ + Nep17Transfer.deserializeWire({ context, buffer, }), - deserializeKey: (buffer) => Nep5TransferKey.deserializeWire({ context, buffer }), + deserializeKey: (buffer) => Nep17TransferKey.deserializeWire({ context, buffer }), }), - nep5TransfersSent: read.createReadFindStorage({ + nep17TransfersSent: read.createReadFindStorage({ db, - getSearchRange: keys.getNep5TransferSentSearchRange, - serializeKey: keys.createNep5TransferSentKey, + getSearchRange: keys.getNep17TransferSentSearchRange, + serializeKey: keys.createNep17TransferSentKey, deserializeValue: (buffer) => - Nep5Transfer.deserializeWire({ + Nep17Transfer.deserializeWire({ context, buffer, }), - deserializeKey: (buffer) => Nep5TransferKey.deserializeWire({ context, buffer }), + deserializeKey: (buffer) => Nep17TransferKey.deserializeWire({ context, buffer }), }), applicationLogs: read.createReadStorage({ diff --git a/packages/neo-one-node-storage-levelup/src/read.ts b/packages/neo-one-node-storage-levelup/src/read.ts index a99fe265bb..df5afd964a 100644 --- a/packages/neo-one-node-storage-levelup/src/read.ts +++ b/packages/neo-one-node-storage-levelup/src/read.ts @@ -35,32 +35,32 @@ export function createTryGet({ }; } -// TODO: we haven't implemented any `tryGetLatest` functions, should we reimplement for something? -// export function createTryGetLatest({ -// db, -// latestKey, -// deserializeResult, -// get, -// }: { -// readonly db: LevelUp; -// readonly latestKey: string; -// readonly deserializeResult: (latestResult: Buffer) => Key; -// readonly get: (key: Key) => Promise; -// }): () => Promise { -// return async (): Promise => { -// try { -// const result = await db.get(latestKey); -// const value = await get(deserializeResult(result as Buffer)); +// Keeping this around if we find a use for it +export function createTryGetLatest({ + db, + latestKey, + deserializeResult, + get, +}: { + readonly db: LevelUp; + readonly latestKey: string; + readonly deserializeResult: (latestResult: Buffer) => Key; + readonly get: (key: Key) => Promise; +}): () => Promise { + return async (): Promise => { + try { + const result = await db.get(latestKey); + const value = await get(deserializeResult(result as Buffer)); -// return value; -// } catch (error) { -// if (error.notFound || error.code === 'KEY_NOT_FOUND') { -// return undefined; -// } -// throw error; -// } -// }; -// } + return value; + } catch (error) { + if (error.notFound || error.code === 'KEY_NOT_FOUND') { + return undefined; + } + throw error; + } + }; +} export function createReadStorage({ db, @@ -209,39 +209,6 @@ export function createReadAllFindStorage({ }; } -// TODO: do we need to reimplement this for something? -// export function createReadGetAllStorage({ -// db, -// serializeKey, -// getMinKey, -// getMaxKey, -// deserializeValue, -// }: { -// readonly db: LevelUp; -// readonly serializeKey: SerializeKey; -// readonly getMinKey: (keys: Keys) => string; -// readonly getMaxKey: (keys: Keys) => string; -// readonly deserializeValue: (value: Buffer) => Value; -// }): ReadGetAllStorage { -// const readStorage = createReadStorage({ -// db, -// serializeKey, -// deserializeValue, -// }); - -// return { -// get: readStorage.get, -// tryGet: readStorage.tryGet, -// getAll$: (keys: Keys) => -// createAll$({ -// db, -// minKey: getMinKey(keys), -// maxKey: getMaxKey(keys), -// deserializeValue, -// }), -// }; -// } - export function createTryGetMetadata({ get, }: { diff --git a/packages/neo-one-node-vm/lib/Dispatcher.Engine.cs b/packages/neo-one-node-vm/lib/Dispatcher.Engine.cs index 030f70bc37..682f00b790 100644 --- a/packages/neo-one-node-vm/lib/Dispatcher.Engine.cs +++ b/packages/neo-one-node-vm/lib/Dispatcher.Engine.cs @@ -1,10 +1,12 @@ using System; using System.IO; using System.Linq; +using Neo; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; +using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; @@ -65,6 +67,7 @@ private enum EngineMethod create, execute, loadscript, + loadcontract, setinstructionpointer, getvmstate, getresultstack, @@ -82,25 +85,41 @@ private dynamic dispatchEngineMethod(EngineMethod method, dynamic args) case EngineMethod.create: TriggerType trigger = (TriggerType)args.trigger; long gas = long.Parse((string)args.gas); - bool testMode = (bool)args.testMode; IVerifiable container = null; if (args.container != null) { container = deserializeContainer(args.container); } - return this._create(trigger, container, this.selectSnapshot(args.snapshot, false), gas, testMode); + return this._create(trigger, container, this.selectSnapshot(args.snapshot, false), gas); case EngineMethod.execute: return this._execute(); case EngineMethod.loadscript: Script script = new Script((byte[])args.script); - CallFlags flag = (CallFlags)((byte)args.flag); - return this._loadScript(script, flag); + CallFlags flags = (CallFlags)((byte)args.flags); + UInt160 scriptHash = null; + int initialPosition = 0; - case EngineMethod.setinstructionpointer: - int position = (int)args.position; - return this._setInstructionPointer(position); + if (args.scriptHash != null) + { + scriptHash = UInt160.Parse((string)args.scriptHash); + } + + if (args.initialPosition != null) + { + initialPosition = (int)args.initialPosition; + } + + return this._loadScript(script, flags, scriptHash, initialPosition); + + case EngineMethod.loadcontract: + UInt160 contractHash = new UInt160((byte[])args.hash); + string contractMethod = (string)args.method; + CallFlags contractFlags = (CallFlags)((byte)args.flags); + bool packParameters = (bool)args.packParameters; + + return this._loadContract(contractHash, contractMethod, contractFlags, packParameters); case EngineMethod.getvmstate: return this._getVMState(); @@ -139,10 +158,10 @@ private void disposeEngine() } } - private bool _create(TriggerType trigger, IVerifiable container, StoreView snapshot, long gas, bool testMode = false) + private bool _create(TriggerType trigger, IVerifiable container, StoreView snapshot, long gas) { this.disposeEngine(); - this.engine = ApplicationEngine.Create(trigger, container, snapshot, gas, testMode); + this.engine = ApplicationEngine.Create(trigger, container, snapshot, gas); return true; } @@ -153,17 +172,20 @@ private VMState _execute() return this.engine.Execute(); } - private bool _loadScript(Script script, CallFlags flag) + private bool _loadScript(Script script, CallFlags flags, UInt160 hash = null, int initialPosition = 0) { this.isEngineInitialized(); - this.engine.LoadScript(script, flag); + this.engine.LoadScript(script, flags, hash, initialPosition); return true; } - private bool _setInstructionPointer(int initialPosition) + private bool _loadContract(UInt160 hash, string method, CallFlags flags, bool packParameters) { this.isEngineInitialized(); - this.engine.CurrentContext.InstructionPointer = initialPosition; + ContractState cs = NativeContract.Management.GetContract(this.snapshot, hash); + if (cs is null) return false; + + this.engine.LoadContract(cs, method, flags, packParameters); return true; } diff --git a/packages/neo-one-node-vm/lib/Dispatcher.Snapshot.cs b/packages/neo-one-node-vm/lib/Dispatcher.Snapshot.cs index 77c9d5f0ff..aecb8920ad 100644 --- a/packages/neo-one-node-vm/lib/Dispatcher.Snapshot.cs +++ b/packages/neo-one-node-vm/lib/Dispatcher.Snapshot.cs @@ -6,6 +6,8 @@ using Neo.Persistence; using Neo.VM; using NEOONE.Storage; +using System.Threading; +using Neo.IO.Caching; namespace NEOONE { @@ -44,8 +46,6 @@ private dynamic dispatchSnapshotMethod(SnapshotMethod method, dynamic args) block.Deserialize(reader); } - var tmp = block.Hash.ToString(); - return this._snapshotBlocksAdd(this.selectSnapshot(args.snapshot), blockHash, block); case SnapshotMethod.snapshot_transactions_add: diff --git a/packages/neo-one-node-vm/lib/Dispatcher.cs b/packages/neo-one-node-vm/lib/Dispatcher.cs index 1c8b40de6d..7c327baa8e 100644 --- a/packages/neo-one-node-vm/lib/Dispatcher.cs +++ b/packages/neo-one-node-vm/lib/Dispatcher.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Neo.Persistence; using Microsoft.Extensions.Configuration; +using Neo.SmartContract.Native; namespace NEOONE { @@ -124,7 +125,7 @@ private bool _dispose() private dynamic _test() { - return ApplicationEngine.Neo_Crypto_CheckMultisigWithECDsaSecp256r1.Hash.ToString(); + return NativeContract.NEO.Name; } private NEOONE.ReturnHelpers.ProtocolSettingsReturn _getConfig() diff --git a/packages/neo-one-node-vm/lib/Dispatcher.csproj b/packages/neo-one-node-vm/lib/Dispatcher.csproj index 85c5d6c56f..04a1502e7b 100644 --- a/packages/neo-one-node-vm/lib/Dispatcher.csproj +++ b/packages/neo-one-node-vm/lib/Dispatcher.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/packages/neo-one-node-vm/lib/ReturnHelpers.cs b/packages/neo-one-node-vm/lib/ReturnHelpers.cs index 4e26a05a73..aadc41c8ff 100644 --- a/packages/neo-one-node-vm/lib/ReturnHelpers.cs +++ b/packages/neo-one-node-vm/lib/ReturnHelpers.cs @@ -174,6 +174,8 @@ public class ProtocolSettingsReturn public string[] seedList; public int millisecondsPerBlock; public int memoryPoolMaxTransactions; + public int maxTraceableBlocks; + public dynamic nativeActivations; public ProtocolSettingsReturn(ProtocolSettings value) { @@ -185,6 +187,8 @@ public ProtocolSettingsReturn(ProtocolSettings value) this.seedList = value.SeedList; this.millisecondsPerBlock = Convert.ToInt32(value.MillisecondsPerBlock); this.memoryPoolMaxTransactions = value.MemoryPoolMaxTransactions; + this.maxTraceableBlocks = Convert.ToInt32(value.MaxTraceableBlocks); + this.nativeActivations = value.MaxTraceableBlocks; } } } diff --git a/packages/neo-one-node-vm/lib/SnapshotHelpers.cs b/packages/neo-one-node-vm/lib/SnapshotHelpers.cs index 79cfe7c76c..319a24f873 100644 --- a/packages/neo-one-node-vm/lib/SnapshotHelpers.cs +++ b/packages/neo-one-node-vm/lib/SnapshotHelpers.cs @@ -23,11 +23,6 @@ public ChangeSet(StoreView snapshot) { 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)); 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 007ca64de5..619249b03f 100644 --- a/packages/neo-one-node-vm/lib/Storage/RocksDB/Snapshot.cs +++ b/packages/neo-one-node-vm/lib/Storage/RocksDB/Snapshot.cs @@ -3,7 +3,8 @@ using RocksDbSharp; using System; using System.Collections.Generic; - +using System.IO; +using Neo; namespace NEOONE.Storage.RocksDB { internal class Snapshot : ISnapshot @@ -48,10 +49,12 @@ public void Put(byte table, byte[] key, byte[] value) if (direction == SeekDirection.Forward) for (it.Seek(fullKey); it.Valid() && it.Key()[0] == table; it.Next()) - yield return (it.Key(), it.Value()); + yield return (it.Key()[1..], it.Value()); else for (it.SeekForPrev(fullKey); it.Valid() && it.Key()[0] == table; it.Prev()) - yield return (it.Key(), it.Value()); + { + yield return (it.Key()[1..], it.Value()); + } } public bool Contains(byte table, byte[] key) @@ -63,7 +66,6 @@ public bool Contains(byte table, byte[] key) 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); } 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 b6eb31f2d8..b14d9592c0 100644 --- a/packages/neo-one-node-vm/lib/Storage/RocksDB/Store.cs +++ b/packages/neo-one-node-vm/lib/Storage/RocksDB/Store.cs @@ -52,11 +52,11 @@ public byte[] getFullKey(byte table, byte[] keyOrPrefix) 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()); + for (it.Seek(fullKey); it.Valid() && it.Key()[0] == table; it.Next()) + yield return (it.Key()[1..], it.Value()); else - for (it.SeekForPrev(fullKey); it.Valid(); it.Prev()) - yield return (it.Key(), it.Value()); + for (it.SeekForPrev(fullKey); it.Valid() && it.Key()[0] == table; it.Prev()) + yield return (it.Key()[1..], it.Value()); } public bool Contains(byte table, byte[] key) diff --git a/packages/neo-one-node-vm/src/ApplicationEngine.ts b/packages/neo-one-node-vm/src/ApplicationEngine.ts index 38296b05c1..44b400e9a4 100644 --- a/packages/neo-one-node-vm/src/ApplicationEngine.ts +++ b/packages/neo-one-node-vm/src/ApplicationEngine.ts @@ -1,8 +1,15 @@ -import { TriggerType, VMState } from '@neo-one/client-common'; -import { CallFlags, SerializableContainer, serializeScriptContainer, SnapshotName } from '@neo-one/node-core'; +import { common, TriggerType, VMState } from '@neo-one/client-common'; +import { + CallFlags, + LoadContractOptions, + LoadScriptOptions, + SerializableContainer, + serializeScriptContainer, + SnapshotName, +} from '@neo-one/node-core'; import { BN } from 'bn.js'; import _ from 'lodash'; -import { parseStackItems, convertLog } from './converters'; +import { convertLog, parseStackItems } from './converters'; import { EngineMethods } from './Methods'; import { DispatcherFunc } from './types'; @@ -11,7 +18,6 @@ export interface CreateOptions { readonly container?: SerializableContainer; readonly snapshot?: SnapshotName; readonly gas: BN; - readonly testMode: boolean; } interface ApplicationEngineDispatcher { @@ -77,7 +83,7 @@ export class ApplicationEngine { }).map(convertLog); } - public create({ trigger, container, gas, snapshot, testMode }: CreateOptions) { + public create({ trigger, container, gas, snapshot }: CreateOptions) { return this.dispatch({ method: 'create', args: { @@ -85,7 +91,6 @@ export class ApplicationEngine { container: container ? serializeScriptContainer(container) : undefined, gas: gas.toString(), snapshot, - testMode, }, }); } @@ -98,26 +103,33 @@ export class ApplicationEngine { ]; } - public loadScript(script: Buffer, flag = CallFlags.All) { + public loadScript({ script, flags = CallFlags.All, scriptHash, initialPosition }: LoadScriptOptions) { return this.dispatch({ method: 'loadscript', args: { script, - flag, + flags, + scriptHash: scriptHash ? common.uInt160ToHex(scriptHash) : undefined, + initialPosition, }, }); } - public checkScript() { + public loadContract({ hash, method, flags, packParameters = false }: LoadContractOptions) { return this.dispatch({ - method: 'checkscript', + method: 'loadcontract', + args: { + hash, + method, + flags, + packParameters, + }, }); } - public setInstructionPointer(position: number) { + public checkScript() { return this.dispatch({ - method: 'setinstructionpointer', - args: { position }, + method: 'checkscript', }); } } diff --git a/packages/neo-one-node-vm/src/Methods/EngineMethods.ts b/packages/neo-one-node-vm/src/Methods/EngineMethods.ts index 79fd4e3b75..f75a4aadbc 100644 --- a/packages/neo-one-node-vm/src/Methods/EngineMethods.ts +++ b/packages/neo-one-node-vm/src/Methods/EngineMethods.ts @@ -1,5 +1,5 @@ -import { TriggerType, VMState } from '@neo-one/client-common'; -import { CallFlags, SerializedScriptContainer, SnapshotName } from '@neo-one/node-core'; +import { TriggerType, VMState, UInt160Hex } from '@neo-one/client-common'; +import { CallFlags, SerializedScriptContainer, SnapshotName, LoadContractOptions } from '@neo-one/node-core'; import { StackItemReturn, LogReturn } from '../converters'; import { DefaultMethods, DispatchMethod } from '../types'; @@ -8,7 +8,13 @@ interface CreateEngineArgs { readonly container?: SerializedScriptContainer; readonly gas: string; readonly snapshot?: SnapshotName; - readonly testMode: boolean; +} + +interface LoadScriptVMArgs { + readonly script: Buffer; + readonly flags: CallFlags; + readonly scriptHash?: UInt160Hex; + readonly initialPosition?: number; } export interface EngineMethods extends DefaultMethods { @@ -23,6 +29,6 @@ export interface EngineMethods extends DefaultMethods { readonly getlogs: DispatchMethod; // methods readonly execute: DispatchMethod; - readonly setinstructionpointer: DispatchMethod; - readonly loadscript: DispatchMethod; + readonly loadscript: DispatchMethod; + readonly loadcontract: DispatchMethod; } diff --git a/packages/neo-one-node-vm/src/__tests__/ApplicationEngine.test.ts b/packages/neo-one-node-vm/src/__tests__/ApplicationEngine.test.ts index 0b7096f0e7..cb727cec1a 100644 --- a/packages/neo-one-node-vm/src/__tests__/ApplicationEngine.test.ts +++ b/packages/neo-one-node-vm/src/__tests__/ApplicationEngine.test.ts @@ -1,4 +1,4 @@ -import { TriggerType } from '@neo-one/client-common'; +import { TriggerType, common, VMState } from '@neo-one/client-common'; import { ApplicationEngine } from '../ApplicationEngine'; import { Dispatcher } from '../Dispatcher'; @@ -11,10 +11,9 @@ describe('ApplicationEngine test', () => { const engine = new ApplicationEngine(dispatcher); engine.create({ trigger: TriggerType.Application, - gas: 0, - testMode: true, + gas: common.TWENTY_FIXED8, }); - expect(engine.state).toEqual('BREAK'); + expect(engine.state).toEqual(VMState.BREAK); }); }); diff --git a/packages/neo-one-node-vm/src/__tests__/Dispatcher.test.ts b/packages/neo-one-node-vm/src/__tests__/Dispatcher.test.ts index e426b3b672..39398edc01 100644 --- a/packages/neo-one-node-vm/src/__tests__/Dispatcher.test.ts +++ b/packages/neo-one-node-vm/src/__tests__/Dispatcher.test.ts @@ -13,7 +13,6 @@ describe('Dispatcher Tests', () => { const result = dispatcher.withApplicationEngine( { trigger: TriggerType.Application, - testMode: true, gas: common.ONE_HUNDRED_FIXED8, }, (engine) => { @@ -22,7 +21,7 @@ describe('Dispatcher Tests', () => { const script = new ScriptBuilder(); script.emitOp('PUSHNULL'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); engine.execute(); @@ -103,7 +102,7 @@ describe('Dispatcher Tests', () => { expect(dispatcher.getConfig()).toBeDefined(); }); - test.only('test command', () => { + test.only('test output only', () => { console.log(dispatcher.test()); }); }); diff --git a/packages/neo-one-node-vm/src/__tests__/snapshot.test.ts b/packages/neo-one-node-vm/src/__tests__/snapshot.test.ts new file mode 100644 index 0000000000..e76db1da0c --- /dev/null +++ b/packages/neo-one-node-vm/src/__tests__/snapshot.test.ts @@ -0,0 +1,17 @@ +import { TriggerType, common, VMState } from '@neo-one/client-common'; +import { ApplicationEngine } from '../ApplicationEngine'; +import { Dispatcher } from '../Dispatcher'; + +describe('ApplicationEngine test', () => { + const dispatcher = new Dispatcher(); + beforeEach(() => { + dispatcher.reset(); + }); + test('withApplicationEngine -- NOP Script -- Halt', () => { + dispatcher.withSnapshots(({ main }) => { + main.changeBlockHashIndex(1, common.ZERO_UINT256); + + console.log(dispatcher.test()); + }); + }); +}); diff --git a/packages/neo-one-node-vm/src/__tests__/storage.test.ts b/packages/neo-one-node-vm/src/__tests__/storage.test.ts index 8c6b16c574..abba6462c1 100644 --- a/packages/neo-one-node-vm/src/__tests__/storage.test.ts +++ b/packages/neo-one-node-vm/src/__tests__/storage.test.ts @@ -1,4 +1,4 @@ -import { common, ScriptBuilder, TriggerType, UInt256, WitnessScopeModel } from '@neo-one/client-common'; +import { common, ScriptBuilder, TriggerType, UInt256, WitnessScopeModel, VMState } from '@neo-one/client-common'; import { assertArrayStackItem, Block, ConsensusData, Signer, Transaction, Witness } from '@neo-one/node-core'; import { BN } from 'bn.js'; import { Dispatcher } from '../Dispatcher'; @@ -11,7 +11,9 @@ const createGetBlockScript = (hash: UInt256) => { return script.build(); }; +// if these tests break when we go to mainnet it is likely the message magic describe('TS <--> C# Storage Test', () => { + const messageMagic = 5195086; test('add a block to the snapshot -- returns Block StackItems -- memory', () => { const dispatcher = new Dispatcher(); const tx = new Transaction({ @@ -29,6 +31,7 @@ describe('TS <--> C# Storage Test', () => { invocation: Buffer.from([]), }), ], + messageMagic, }); const block = new Block({ @@ -43,6 +46,7 @@ describe('TS <--> C# Storage Test', () => { nextConsensus: common.ZERO_UINT160, consensusData: new ConsensusData({ nonce: new BN(1), primaryIndex: 1 }), transactions: [tx], + messageMagic, }); const script = createGetBlockScript(block.hash); @@ -55,13 +59,12 @@ describe('TS <--> C# Storage Test', () => { { trigger: TriggerType.Application, snapshot: 'main', - gas: 0, - testMode: true, + gas: common.TWENTY_FIXED8, }, (engine) => { - engine.loadScript(script); + engine.loadScript({ script }); const newState = engine.execute(); - expect(newState).toEqual('HALT'); + expect(newState).toEqual(VMState.HALT); const resultStack = engine.resultStack; const arr = assertArrayStackItem(resultStack[0]).array; @@ -89,6 +92,7 @@ describe('TS <--> C# Storage Test', () => { invocation: Buffer.from([]), }), ], + messageMagic, }); const block = new Block({ @@ -103,6 +107,7 @@ describe('TS <--> C# Storage Test', () => { nextConsensus: common.ZERO_UINT160, consensusData: new ConsensusData({ nonce: new BN(1), primaryIndex: 1 }), transactions: [tx], + messageMagic, }); const script = createGetBlockScript(block.hash); @@ -115,11 +120,10 @@ describe('TS <--> C# Storage Test', () => { { trigger: TriggerType.Application, snapshot: 'main', - gas: 0, - testMode: true, + gas: common.TWENTY_FIXED8, }, (engine) => { - engine.loadScript(script); + engine.loadScript({ script }); const newState = engine.execute(); expect(newState).toEqual('HALT'); diff --git a/packages/neo-one-node-vm/src/__tests__/syscalls.test.ts b/packages/neo-one-node-vm/src/__tests__/syscalls.test.ts index 7120e9eb88..644daa8e76 100644 --- a/packages/neo-one-node-vm/src/__tests__/syscalls.test.ts +++ b/packages/neo-one-node-vm/src/__tests__/syscalls.test.ts @@ -13,8 +13,7 @@ describe('Application Engine SysCall Tests', () => { dispatcher.reset(); engine.create({ trigger: TriggerType.Application, - gas: 0, - testMode: true, + gas: common.TWENTY_FIXED8, }); script = new ScriptBuilder(); @@ -27,7 +26,7 @@ describe('Application Engine SysCall Tests', () => { script.emitPushString('null'); script.emitSysCall('System.Json.Deserialize'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('HALT'); @@ -40,7 +39,7 @@ describe('Application Engine SysCall Tests', () => { script.emitPushString('***'); script.emitSysCall('System.Json.Deserialize'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('FAULT'); expect(engine.resultStack.length).toEqual(0); @@ -50,7 +49,7 @@ describe('Application Engine SysCall Tests', () => { script.emitPushString('123.45'); script.emitSysCall('System.Json.Deserialize'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('FAULT'); expect(engine.resultStack.length).toEqual(0); @@ -75,7 +74,7 @@ describe('Application Engine SysCall Tests', () => { script.emitOp('SETITEM'); script.emitSysCall('System.Json.Serialize'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('HALT'); @@ -93,7 +92,7 @@ describe('Application Engine SysCall Tests', () => { script.emitSysCall('System.Storage.GetContext'); script.emitSysCall('System.Json.Serialize'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('FAULT'); expect(engine.resultStack.length).toEqual(0); @@ -103,8 +102,7 @@ describe('Application Engine SysCall Tests', () => { test('System.Callback.Invoke -- Halt', () => { engine.create({ trigger: TriggerType.Application, - gas: 1, - testMode: false, + gas: common.TWENTY_FIXED8, }); script = new ScriptBuilder(); @@ -124,7 +122,7 @@ describe('Application Engine SysCall Tests', () => { script.emitOp('SUB'); script.emitOp('RET'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('HALT'); @@ -136,8 +134,7 @@ describe('Application Engine SysCall Tests', () => { test('System.Callback.CreateFromSyscall -- Halt', () => { engine.create({ trigger: TriggerType.Application, - gas: 1, - testMode: false, + gas: common.TWENTY_FIXED8, }); script = new ScriptBuilder(); @@ -149,7 +146,7 @@ describe('Application Engine SysCall Tests', () => { script.emitSysCall('System.Callback.CreateFromSyscall'); script.emitSysCall('System.Callback.Invoke'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('HALT'); @@ -171,19 +168,19 @@ describe('Application Engine SysCall Tests', () => { nextConsensus: common.ZERO_UINT160, consensusData: new ConsensusData({ nonce: new BN(1), primaryIndex: 1 }), transactions: [], + messageMagic: 1951352142, }); test('With Snapshot option -- Halt', () => { engine.create({ trigger: TriggerType.Application, - gas: 0, - testMode: true, + gas: common.TWENTY_FIXED8, snapshot: 'main', }); script.emitPushUInt256(block.hash); script.emitSysCall('System.Blockchain.GetBlock'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('HALT'); @@ -193,11 +190,11 @@ describe('Application Engine SysCall Tests', () => { }); test('Without Snapshot option -- Fault', () => { - engine.create({ trigger: TriggerType.Application, gas: 0, testMode: true }); + engine.create({ trigger: TriggerType.Application, gas: common.TWENTY_FIXED8 }); script.emitPushUInt256(block.hash); script.emitSysCall('System.Blockchain.GetBlock'); - engine.loadScript(script.build()); + engine.loadScript({ script: script.build() }); const state = engine.execute(); expect(state).toEqual('FAULT'); }); diff --git a/packages/neo-one-node-vm/src/converters/changes.ts b/packages/neo-one-node-vm/src/converters/changes.ts index c8f6ee0571..dd91b9b252 100644 --- a/packages/neo-one-node-vm/src/converters/changes.ts +++ b/packages/neo-one-node-vm/src/converters/changes.ts @@ -1,7 +1,7 @@ // import { PutBatch, DeleteBatch } from '@neo-one/node-core'; type ChangeType = 'Added' | 'Changed' | 'Deleted'; -type ItemType = 'block' | 'transaction' | 'contract' | 'storage' | 'headerHashList'; +type ItemType = 'block' | 'transaction' | 'storage' | 'headerHashList'; export interface ChangeReturn { readonly type: ChangeType; @@ -14,7 +14,6 @@ export interface ChangeReturn { enum Prefix { Block = 0x01, Transaction = 0x02, - Contract = 0x50, Storage = 0x70, HeaderHashList = 0x80, CurrentBlock = 0xc0, @@ -30,8 +29,6 @@ const addPrefixToKey = (type: ItemType, key: Buffer) => { return addPrefix(Prefix.Block, key); case 'transaction': return addPrefix(Prefix.Transaction, key); - case 'contract': - return addPrefix(Prefix.Contract, key); case 'storage': return addPrefix(Prefix.Storage, key); case 'headerHashList': diff --git a/packages/neo-one-node-vm/src/utils.ts b/packages/neo-one-node-vm/src/utils.ts index 3521cd36cf..87e0c268be 100644 --- a/packages/neo-one-node-vm/src/utils.ts +++ b/packages/neo-one-node-vm/src/utils.ts @@ -22,10 +22,12 @@ export const createCSharpDispatchInvoke = ( options: EdgeOptions, ): DispatcherFunc => { if (process.env.EDGE_APP_ROOT === undefined) { + // tslint:disable-next-line: no-object-mutation process.env.EDGE_APP_ROOT = CSHARP_APP_ROOT; } if (process.env.EDGE_USE_CORECLR === undefined) { + // tslint:disable-next-line: no-object-mutation process.env.EDGE_USE_CORECLR = '1'; } diff --git a/packages/neo-one-node/src/__data__/data.ts b/packages/neo-one-node/src/__data__/data.ts index 6ab55b78de..2a676f1ac4 100644 --- a/packages/neo-one-node/src/__data__/data.ts +++ b/packages/neo-one-node/src/__data__/data.ts @@ -40,6 +40,7 @@ const convertBlock = (json: BlockJSON, messageMagic: number) => (signer) => new Signer({ account: JSONHelper.readUInt160(signer.account), + // tslint:disable-next-line: no-any scopes: (signer.scopes as any) === 'FeeOnly' ? WitnessScopeModel.None : toWitnessScope(signer.scopes), }), ), @@ -55,6 +56,7 @@ const convertBlock = (json: BlockJSON, messageMagic: number) => messageMagic, }); +// tslint:disable no-any export-name export const getData = (messageMagic: number) => ({ genesisBlock: convertBlock(genesisJSON as any, messageMagic), secondBlock: convertBlock(secondBlockJSON as any, messageMagic), diff --git a/packages/neo-one-node/src/__data__/jsonBlocks.ts b/packages/neo-one-node/src/__data__/jsonBlocks.ts index 23a08e62e5..e048e1fc48 100644 --- a/packages/neo-one-node/src/__data__/jsonBlocks.ts +++ b/packages/neo-one-node/src/__data__/jsonBlocks.ts @@ -48,55 +48,55 @@ export const genesisJSON = { }; export const secondBlockJSON = { - hash: '0x8f30c34a8a8ef997155aa4a0ef6664d872a127ecc3bb6a85b7bbc55d6d9912f5', + hash: '0x2cf3b022f8ef6b863f47746ce9a9e38e3f1762ea47f3ede3be81118c1fa1ee24', size: 700, version: 0, - previousblockhash: '0xc359030132be10fd19cfd0a27e289fe04acb0c5c4ca5254af8a2d99498c7da45', - merkleroot: '0x9ff5ba403c81151c0d031b548fe6cff82ef61a01be72c38ecc81d4bc2f1ee01c', - time: 1596537327793, + previousblockhash: '0xc4d50ea6bd8e0d1422714f8edbd4eee182b9f6958b2b0795bf6f3e9f9117ee03', + merkleroot: '0x3443a50bc7b0e475ce658c9f089656b41c8bb26abf5c4ce1df88513e63b29ab8', + time: 1608609566819, index: 1, nextconsensus: 'NgPkjjLTNcQad99iRYeXRUuowE4gxLAnDL', witnesses: [ { invocation: - 'DED/2tpnw4uN5407xKQuwAXw+Hm9L4P51hSMfjwbEcm7pN+aFdn5+d/VxT9ifDX0KQRGlbericqr6h2gQvnJYvHqDECO9w/7PAccs3K3yqAZ4zDTpsnxxDU5EyR1PNNcBUx31lFGyHtjcxhce+YNYxcJc2zOFFk+lFmCBn+ZiF1HZrkPDEBbpwRGODeexXqSws2VQPrOg/BiCNYKmAj3vPW8HvC3ypnly/Hfy8lYF9iZiIighzUALC4QzQpxdq1IdNMSH22jDEAe/l0FF8HXc6e20zT6x0D8XvrITHsKMxxe4S5hGYwojgBDjJPcMwvJbT83AF+sorbZc7SMaCGwT1XOEg+9M0G2DEDOK/OsvMqyKgyHf/gz5QFgQxPjHKuuMoN38+OLuMRP36K4Eunv7oql3KstrgpVIqZHtETEHmxMEFIFXu5hFEa8', + 'DEBOggaxB6SzzC40ATwnTZ7MibqI2otBM0q1QtKHqmXnBU6PmHXOXASYcO1evD/yptiwRW/k1b2E5I50W933JdM4DEBfe8Tz4SYWgwYxznqpjoRftsxYNErtdKrkmmQiyd01DTIuE7k96KfoEkixwtzI00fJr90NB+upKXakAdN9sauWDEDkDsyC7mCGZoEMoAIE3oLDgvYclrYu+HNtDMBpNV1cDmh7dIo/QTDS4klaGoPq6plz/y8r9WEYSU1HueXkxPGDDEBDEN06fn+gPez+jkOHNVbddWImxlwHtR4CZtQyFyA8lo0x+rg3z6gq+mr4jrRGFKyuDoDUjvlanQiyT49z2WeRDEDDpBV3R0smZ2cNn2AVwX5K5uiCSosjg1JyQQVW7s2SXSOiMlhpdHMSIM3Dn+0QgCDDfyieOqclDSxOdiKgeSzY', verification: 'FQwhAwCbdUDhDyVi5f2PrJ6uwlFmpYsm5BI0j/WoaSe/rCKiDCEDAgXpzvrqWh38WAryDI1aokaLsBSPGl5GBfxiLIDmBLoMIQIUuvDO6jpm8X5+HoOeol/YvtbNgua7bmglAYkGX0T/AQwhAj6bMuqJuU0GbmSbEk/VDjlu6RNp6OKmrhsRwXDQIiVtDCEDQI3NQWOW9keDrFh+oeFZPFfZ/qiAyKahkg6SollHeAYMIQKng0vpsy4pgdFXy1u9OstCz9EepcOxAiTXpE6YxZEPGwwhAroscPWZbzV6QxmHBYWfriz+oT4RcpYoAHcrPViKnUq9FwtBE43vrw==', }, ], consensusdata: { - primary: 1, - nonce: 'f490dfe92971e572', + primary: 6, + nonce: 'c4bea6633614241c', }, tx: [], - confirmations: 294767, - nextblockhash: '0x84fa4b26af4b00095d66714a3a2ec63acdeff7ae27ca5d4f6cbe5db2691d7fae', + confirmations: 44876, + nextblockhash: '0x1fc795278c20f9a50ce6b9a9a2617ce2bb94e05c9737b7e94a3124029f3a786f', }; export const thirdBlockJSON = { - hash: '0x84fa4b26af4b00095d66714a3a2ec63acdeff7ae27ca5d4f6cbe5db2691d7fae', + hash: '0x1fc795278c20f9a50ce6b9a9a2617ce2bb94e05c9737b7e94a3124029f3a786f', size: 700, version: 0, - previousblockhash: '0x8f30c34a8a8ef997155aa4a0ef6664d872a127ecc3bb6a85b7bbc55d6d9912f5', - merkleroot: '0x905819ef1743d2f7e522dacc90a365dd7e872ebdee74f2dfcad61d5089dc9f64', - time: 1596537468402, + previousblockhash: '0x2cf3b022f8ef6b863f47746ce9a9e38e3f1762ea47f3ede3be81118c1fa1ee24', + merkleroot: '0x89378f1c6005485d124c1eae9fc82e8639fb7a5b4e167c15305edec80c81fac0', + time: 1608609583168, index: 2, nextconsensus: 'NgPkjjLTNcQad99iRYeXRUuowE4gxLAnDL', witnesses: [ { invocation: - 'DEC3IxKTiG/yHShrtaN2aDBWLCoiLCvaUHmir1TovNGookWVKqB6NxwJb1capeJk50HtWntpz4B/k0oDd9Y33F8xDEDikpijBQ+rl4WJCHN7DPumv8OvtBhszmDKqX3wEJyaKAUL8rLsncDbsgIwJTxPyMUDIrU8BbVIEYDAH0KvB5AfDECcu9VKxSaC5mJdWvwguqw74ZU31UIG2cn5FMGg/EPMwFQeJEW/HTZNIIJNvE8lQ5i6C7wZ5QJFRLXavqDwvO+TDEBokh+09rXIVbcmzGBqddJ+Kq9mp7UAH9XTcTtDiQaeKQ39krUyySuaUjZm54kHsi70OsAqkK+hfSIpYZ4qKLeUDEA5qVvZMvHzv9NR6ekPvKpTquy32qgY+quCJt1BSeBaOGAZ4wTVs3Guw+HvFfarRPORYy186jVzQM1tAbPR+oiQ', + 'DEBvYhef2pK9W5sjqltgniuPFz7NvJ634EX2DcH5oHjh5/jxvjobPWNUZipHvp9FPkJwRxS/3+BL6UMYWxqslJ0ADEAsqKff5hGys7yVwtZUKS1oBklkJ/l9HlzglV/ETUp+f3uK1h9miqg19myjtAgM6YVdE3+4KEQ/hagfTks91DMdDEBOQbnr5gC5ADyFbHrVRHhwIgy4CaCILZAD+XghMN6yzyNCUzJMruTpcVLz6j4xO3vVhRrSYOc5Ys+G8lrXhKTyDEBOBG824/s6MgJ/pymHYoQeuaKH1owwV1UtOb0hkDiidZOS7+xrf74/MKuph0PObNY41NQcYEibOs57F7qQYkG5DECSVNLMQdDvlzBbPKx6nDUJlBIaMBf5rnwweukFFJ1wHhnNbX3d0ZnbJhjWGj/dhAkGvUvkQlciEEtMXa/HJVF1', verification: 'FQwhAwCbdUDhDyVi5f2PrJ6uwlFmpYsm5BI0j/WoaSe/rCKiDCEDAgXpzvrqWh38WAryDI1aokaLsBSPGl5GBfxiLIDmBLoMIQIUuvDO6jpm8X5+HoOeol/YvtbNgua7bmglAYkGX0T/AQwhAj6bMuqJuU0GbmSbEk/VDjlu6RNp6OKmrhsRwXDQIiVtDCEDQI3NQWOW9keDrFh+oeFZPFfZ/qiAyKahkg6SollHeAYMIQKng0vpsy4pgdFXy1u9OstCz9EepcOxAiTXpE6YxZEPGwwhAroscPWZbzV6QxmHBYWfriz+oT4RcpYoAHcrPViKnUq9FwtBE43vrw==', }, ], consensusdata: { - primary: 1, - nonce: '3b23f5541d704aaa', + primary: 2, + nonce: 'eff262be7f3dfa91', }, tx: [], - confirmations: 311250, - nextblockhash: '0xfd4d6fdf33eaec2d574590af1c8cbfcaa9d990b4ecc1cf0e60670b951be214a2', + confirmations: 44886, + nextblockhash: '0x84ba503f0a990cdaa0f1336f375d6b382f0728a63ea4c3cf7fc958d578cef8f0', }; export const debugBlockJSON = { diff --git a/packages/neo-one-node/src/__tests__/blockchain.test.ts b/packages/neo-one-node/src/__tests__/blockchain.test.ts index ad2e704c56..6e2f995042 100644 --- a/packages/neo-one-node/src/__tests__/blockchain.test.ts +++ b/packages/neo-one-node/src/__tests__/blockchain.test.ts @@ -1,41 +1,20 @@ -import { common, ScriptBuilder } from '@neo-one/client-common'; import { Blockchain } from '@neo-one/node-blockchain'; -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 LevelUp from 'levelup'; -import RocksDB from 'rocksdb'; +// tslint:disable-next-line: match-default-export-name import Memdown from 'memdown'; +import RocksDB from 'rocksdb'; import { getData } from '../__data__'; -const rawReadStreamPromise = async (db: any, options: { readonly gte: Buffer; readonly lte: Buffer }) => - new Promise((resolve, reject) => { - db.createReadStream(options) - .on('data', (data: any) => { - console.log(common.uInt160ToString(data.key.slice(1))); - console.log( - JSON.stringify( - ContractState.deserializeWire({ - context: { messageMagic: 1951352142, validatorsCount: 1 }, - buffer: data.value, - }).serializeJSON(), - undefined, - 2, - ), - ); - }) - .on('error', reject) - .on('close', resolve) - .on('end', resolve); - }); - -describe.skip('Blockchain invocation / storage tests', () => { - test('dispatcher can retrieve logs after invocation', async () => { +describe('Blockchain invocation / storage tests', () => { + test('Can persist the first 3 blocks with disk storage', async () => { const blockchainSettings = createTest(); const levelDBPath = '/Users/danielbyrne/Desktop/node-data'; const db = LevelUp(RocksDB(levelDBPath)); + const blockData = getData(blockchainSettings.messageMagic); const storage = levelupStorage({ db, @@ -59,14 +38,13 @@ describe.skip('Blockchain invocation / storage tests', () => { native, }); - const builder = new ScriptBuilder(); - builder.emitSysCall('System.Runtime.Log', 'Hello world!'); - const script = builder.build(); + await blockchain.persistBlock({ block: blockData.secondBlock }); + + expect(blockchain.currentBlockIndex).toEqual(1); - const result = blockchain.invokeScript(script); + await blockchain.persistBlock({ block: blockData.thirdBlock }); - expect(result.logs.length).toEqual(1); - expect(result.logs[0].message).toEqual('Hello world!'); + expect(blockchain.currentBlockIndex).toEqual(2); }); }); @@ -76,6 +54,23 @@ describe('VM memory store for testing', () => { const blockData = getData(settings.messageMagic); const db = LevelUp(Memdown()); + const getChanges = async () => + 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', reject); + }); + + const onPersist = async () => { + const changes = await getChanges(); + dispatcher.updateStore(changes); + }; + const storage = levelupStorage({ db, context: { @@ -95,23 +90,15 @@ describe('VM memory store for testing', () => { storage, vm: dispatcher, native, + onPersist, }); - 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); + + await blockchain.persistBlock({ block: blockData.thirdBlock }); + + expect(blockchain.currentBlockIndex).toEqual(2); }); }); diff --git a/packages/neo-one-node/src/__tests__/consensus.test.ts b/packages/neo-one-node/src/__tests__/consensus.test.ts index 3b45b2a4b0..ed262552bd 100644 --- a/packages/neo-one-node/src/__tests__/consensus.test.ts +++ b/packages/neo-one-node/src/__tests__/consensus.test.ts @@ -1,10 +1,6 @@ import { common } from '@neo-one/client-common'; import { Block } from '@neo-one/node-core'; -import { storage as levelupStorage } from '@neo-one/node-storage-levelup'; -import { blockchainSettingsToProtocolSettings, Dispatcher } from '@neo-one/node-vm'; import fs from 'fs-extra'; -import LevelUp from 'levelup'; -import RocksDB from 'rocksdb'; import { FullNode } from '../FullNode'; const timeout = 30000000; @@ -71,20 +67,4 @@ describe('Consensus Testing', () => { await Promise.all([fullNode.start(), new Promise((resolve) => setTimeout(resolve, timeout))]); }); - - test('', async () => { - const db = LevelUp(RocksDB(config.path)); - - const storage = levelupStorage({ - db, - context: { messageMagic: config.blockchain.messageMagic }, - }); - - const dispatcher = new Dispatcher({ - levelDBPath: config.path, - protocolSettings: blockchainSettingsToProtocolSettings(config.blockchain), - }); - - console.log(JSON.stringify(dispatcher.getConfig(), undefined, 2)); - }); }); diff --git a/packages/neo-one-smart-contract-codegen/src/__data__/index.ts b/packages/neo-one-smart-contract-codegen/src/__data__/index.ts index b2518440ff..fda1c47f64 100644 --- a/packages/neo-one-smart-contract-codegen/src/__data__/index.ts +++ b/packages/neo-one-smart-contract-codegen/src/__data__/index.ts @@ -1,3 +1,3 @@ -export * from './nep5'; +export * from './nep17'; export * from './testUtils'; export * from './abiFactory'; diff --git a/packages/neo-one-smart-contract-codegen/src/__data__/nep5.ts b/packages/neo-one-smart-contract-codegen/src/__data__/nep17.ts similarity index 99% rename from packages/neo-one-smart-contract-codegen/src/__data__/nep5.ts rename to packages/neo-one-smart-contract-codegen/src/__data__/nep17.ts index 8a360e73e0..6c0cd28fd0 100644 --- a/packages/neo-one-smart-contract-codegen/src/__data__/nep5.ts +++ b/packages/neo-one-smart-contract-codegen/src/__data__/nep17.ts @@ -155,6 +155,6 @@ const abi = (decimals: number): ABI => ({ ], }); -export const nep5 = { +export const nep17 = { abi, }; diff --git a/packages/neo-one-smart-contract-codegen/src/__tests__/abi/genABI.test.ts b/packages/neo-one-smart-contract-codegen/src/__tests__/abi/genABI.test.ts index 9496598a99..d4c039bfa3 100644 --- a/packages/neo-one-smart-contract-codegen/src/__tests__/abi/genABI.test.ts +++ b/packages/neo-one-smart-contract-codegen/src/__tests__/abi/genABI.test.ts @@ -1,8 +1,8 @@ -import { nep5 } from '@neo-one/client-core'; +import { nep17 } from '@neo-one/client-core'; import { genABI } from '../../abi'; describe('genABI', () => { - test('NEP5', () => { - expect(genABI('Token', nep5.abi(4))).toMatchSnapshot(); + test('NEP17', () => { + expect(genABI('Token', nep17.abi(4))).toMatchSnapshot(); }); }); diff --git a/packages/neo-one-smart-contract-codegen/src/__tests__/genFiles.test.ts b/packages/neo-one-smart-contract-codegen/src/__tests__/genFiles.test.ts index 303ae71c31..ffabfe3045 100644 --- a/packages/neo-one-smart-contract-codegen/src/__tests__/genFiles.test.ts +++ b/packages/neo-one-smart-contract-codegen/src/__tests__/genFiles.test.ts @@ -1,4 +1,4 @@ -import { nep5 } from '../__data__'; +import { nep17 } from '../__data__'; import { genFiles } from '../genFiles'; describe('genFiles', () => { @@ -11,7 +11,7 @@ describe('genFiles', () => { typesPath: '/foo/bar/one/generated/Token/types.js', abiPath: '/foo/bar/one/generated/Token/abi.js', sourceMapsPath: '/foo/bar/one/generated/sourceMaps.js', - abi: nep5.abi(4), + abi: nep17.abi(4), networksDefinition: { main: { address: 'iamahash', diff --git a/packages/neo-one-smart-contract-codegen/src/__tests__/types/genSmartContractTypes.test.ts b/packages/neo-one-smart-contract-codegen/src/__tests__/types/genSmartContractTypes.test.ts index 5c242089b1..2de1eaed02 100644 --- a/packages/neo-one-smart-contract-codegen/src/__tests__/types/genSmartContractTypes.test.ts +++ b/packages/neo-one-smart-contract-codegen/src/__tests__/types/genSmartContractTypes.test.ts @@ -1,8 +1,8 @@ -import { nep5 } from '../../__data__'; +import { nep17 } from '../../__data__'; import { genSmartContractTypes } from '../../types'; describe('genSmartContractTypes', () => { - test('NEP5', () => { - expect(genSmartContractTypes('Token', nep5.abi(4))).toMatchSnapshot(); + test('NEP17', () => { + expect(genSmartContractTypes('Token', nep17.abi(4))).toMatchSnapshot(); }); }); diff --git a/packages/neo-one-smart-contract-compiler/src/compile/sb/BaseScriptBuilder.ts b/packages/neo-one-smart-contract-compiler/src/compile/sb/BaseScriptBuilder.ts index 63a6ec1b6c..735a4cb442 100644 --- a/packages/neo-one-smart-contract-compiler/src/compile/sb/BaseScriptBuilder.ts +++ b/packages/neo-one-smart-contract-compiler/src/compile/sb/BaseScriptBuilder.ts @@ -57,7 +57,7 @@ export abstract class BaseScriptBuilder implements ScriptB private readonly mutableExportMap: { [K in string]?: Set } = {}; private mutableNextModuleIndex = 0; private mutableCurrentModuleIndex = 0; - private mutableFeatures: Features = { storage: false, dynamicInvoke: false }; + private mutableFeatures: Features = { dynamicInvoke: false }; public constructor( public readonly context: Context, @@ -365,10 +365,6 @@ export abstract class BaseScriptBuilder implements ScriptB } public emitSysCall(node: ts.Node, name: SysCallName): void { - if (name === 'Neo.Storage.Put' || name === 'Neo.Storage.Delete') { - this.mutableFeatures = { ...this.mutableFeatures, storage: true }; - } - const sysCallBuffer = Buffer.allocUnsafe(4); sysCallBuffer.writeUInt32LE(toSysCallHash(assertSysCall(name)), 0); const writer = new BinaryWriter(); diff --git a/packages/neo-one-smart-contract-compiler/src/compile/types.ts b/packages/neo-one-smart-contract-compiler/src/compile/types.ts index 025fe23dd5..3bcf4f0c66 100644 --- a/packages/neo-one-smart-contract-compiler/src/compile/types.ts +++ b/packages/neo-one-smart-contract-compiler/src/compile/types.ts @@ -33,12 +33,11 @@ export interface VisitOptions { } export interface Features { - readonly storage: boolean; + // TODO: is dynamicInvoke even a thing now? readonly dynamicInvoke: boolean; } export interface ScriptBuilderResult { readonly code: Buffer; - readonly features: Features; readonly sourceMap: Promise; } export interface CompileResult { diff --git a/packages/neo-one-smart-contract-lib/src/NEP5Token.ts b/packages/neo-one-smart-contract-lib/src/NEP17Token.ts similarity index 94% rename from packages/neo-one-smart-contract-lib/src/NEP5Token.ts rename to packages/neo-one-smart-contract-lib/src/NEP17Token.ts index 4e5ace966c..dd1ad89ccd 100644 --- a/packages/neo-one-smart-contract-lib/src/NEP5Token.ts +++ b/packages/neo-one-smart-contract-lib/src/NEP17Token.ts @@ -14,8 +14,10 @@ interface TokenPayableContract { readonly onRevokeSendTransfer: (from: Address, amount: Fixed<0>, asset: Address) => void; } -export function NEP5Token>(Base: TBase) { - abstract class NEP5TokenClass extends Base { +// TODO: this whole definition will need to be updated to actually meet the current standard +// see https://github.com/neo-project/proposals/pull/126 +export function NEP17Token>(Base: TBase) { + abstract class NEP17TokenClass extends Base { public abstract readonly name: string; public abstract readonly decimals: 8; public abstract readonly symbol: string; @@ -167,5 +169,5 @@ export function NEP5Token>(Base: TBase) } } - return NEP5TokenClass; + return NEP17TokenClass; } diff --git a/packages/neo-one-smart-contract-lib/src/index.ts b/packages/neo-one-smart-contract-lib/src/index.ts index e9d2e88062..f8aab60bc2 100644 --- a/packages/neo-one-smart-contract-lib/src/index.ts +++ b/packages/neo-one-smart-contract-lib/src/index.ts @@ -1,3 +1,3 @@ -export { NEP5Token } from './NEP5Token'; +export { NEP17Token } from './NEP17Token'; export { Ownable, Secondary } from './ownership'; export { ICO } from './ICO'; diff --git a/packages/neo-one-smart-contract-lib/tsconfig.json b/packages/neo-one-smart-contract-lib/tsconfig.json index f8773d3b9a..97e12e3fb6 100644 --- a/packages/neo-one-smart-contract-lib/tsconfig.json +++ b/packages/neo-one-smart-contract-lib/tsconfig.json @@ -24,5 +24,5 @@ "noFallthroughCasesInSwitch": true, "forceConsistentCasingInFileNames": true }, - "exclude": ["./src/index.ts", "./src/ownership/*.ts", "./src/ICO.ts", "./src/NEP5Token.ts"] + "exclude": ["./src/index.ts", "./src/ownership/*.ts", "./src/ICO.ts", "src/NEP17Token.ts"] } diff --git a/packages/neo-one-smart-contract-test/src/__data__/contracts/SimpleToken.ts b/packages/neo-one-smart-contract-test/src/__data__/contracts/SimpleToken.ts index a862c8558c..4f553204cf 100644 --- a/packages/neo-one-smart-contract-test/src/__data__/contracts/SimpleToken.ts +++ b/packages/neo-one-smart-contract-test/src/__data__/contracts/SimpleToken.ts @@ -1,7 +1,7 @@ import { Address, Fixed, SmartContract } from '@neo-one/smart-contract'; -import { NEP5Token } from '@neo-one/smart-contract-lib'; +import { NEP17Token } from '@neo-one/smart-contract-lib'; -export abstract class SimpleToken extends NEP5Token(SmartContract) { +export abstract class SimpleToken extends NEP17Token(SmartContract) { public readonly owner: Address; public readonly decimals: 8 = 8; diff --git a/packages/neo-one-smart-contract-test/src/__data__/contracts/TestICO.ts b/packages/neo-one-smart-contract-test/src/__data__/contracts/TestICO.ts index f1ab27e9c8..b53fdcf622 100644 --- a/packages/neo-one-smart-contract-test/src/__data__/contracts/TestICO.ts +++ b/packages/neo-one-smart-contract-test/src/__data__/contracts/TestICO.ts @@ -1,7 +1,7 @@ import { Address, Fixed, Integer, SmartContract } from '@neo-one/smart-contract'; -import { ICO, NEP5Token } from '@neo-one/smart-contract-lib'; +import { ICO, NEP17Token } from '@neo-one/smart-contract-lib'; -export class TestICO extends ICO(NEP5Token(SmartContract)) { +export class TestICO extends ICO(NEP17Token(SmartContract)) { public readonly name: string = 'TestToken'; public readonly decimals: 8 = 8; public readonly symbol: string = 'TT'; diff --git a/packages/neo-one-smart-contract-test/src/__data__/contracts/TestToken.ts b/packages/neo-one-smart-contract-test/src/__data__/contracts/TestToken.ts index 3888140ac7..a8c7c8b077 100644 --- a/packages/neo-one-smart-contract-test/src/__data__/contracts/TestToken.ts +++ b/packages/neo-one-smart-contract-test/src/__data__/contracts/TestToken.ts @@ -1,7 +1,7 @@ import { Address, SmartContract } from '@neo-one/smart-contract'; -import { NEP5Token } from '@neo-one/smart-contract-lib'; +import { NEP17Token } from '@neo-one/smart-contract-lib'; -export class TestToken extends NEP5Token(SmartContract) { +export class TestToken extends NEP17Token(SmartContract) { public readonly owner: Address; public readonly name: string = 'TestToken'; public readonly decimals: 8 = 8; diff --git a/packages/neo-one-smart-contract-test/src/__tests__/createNode.test.ts b/packages/neo-one-smart-contract-test/src/__tests__/createNode.test.ts index 515e62d7a8..47c4453f40 100644 --- a/packages/neo-one-smart-contract-test/src/__tests__/createNode.test.ts +++ b/packages/neo-one-smart-contract-test/src/__tests__/createNode.test.ts @@ -1,14 +1,14 @@ /// -import { createNode } from '../createNode'; +import { common } from '@neo-one/client-common'; import { - NEOONEDataProvider, - NEOONEProvider, - LocalMemoryStore, LocalKeyStore, + LocalMemoryStore, LocalUserAccountProvider, + NEOONEDataProvider, + NEOONEProvider, } from '@neo-one/client-core'; -import { common } from '@neo-one/client-common'; import BigNumber from 'bignumber.js'; +import { createNode } from '../createNode'; const secondaryKeyString = '04c1784140445016cf0f8cc86dd10ad8764e1a89c563c500e21ac19a5d905ab3';