-
Notifications
You must be signed in to change notification settings - Fork 57
Block service #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Block service #450
Changes from all commits
b7e28cc
0bb1832
3c9619c
ec38331
7a0d720
9ddf862
5c6ef67
c4878c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* | ||
* Copyright 2019 NEM | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { assert, expect } from 'chai'; | ||
import { ReceiptRepository } from '../../src/infrastructure/ReceiptRepository'; | ||
import { TransactionRepository } from '../../src/infrastructure/TransactionRepository'; | ||
import { Account } from '../../src/model/account/Account'; | ||
import { NetworkType } from '../../src/model/blockchain/NetworkType'; | ||
import { PlainMessage } from '../../src/model/message/PlainMessage'; | ||
import { NetworkCurrencyMosaic } from '../../src/model/mosaic/NetworkCurrencyMosaic'; | ||
import { Deadline } from '../../src/model/transaction/Deadline'; | ||
import { TransferTransaction } from '../../src/model/transaction/TransferTransaction'; | ||
import { UInt64 } from '../../src/model/UInt64'; | ||
import { BlockService } from '../../src/service/BlockService'; | ||
import { IntegrationTestHelper } from '../infrastructure/IntegrationTestHelper'; | ||
|
||
describe('BlockService', () => { | ||
const helper = new IntegrationTestHelper(); | ||
let generationHash: string; | ||
let account: Account; | ||
let account2: Account; | ||
let account3: Account; | ||
let networkType: NetworkType; | ||
let transactionHash: string; | ||
let blockService: BlockService; | ||
let transactionRepository: TransactionRepository; | ||
let receiptRepository: ReceiptRepository; | ||
|
||
before(() => { | ||
return helper.start().then(() => { | ||
account = helper.account; | ||
account2 = helper.account2; | ||
account3 = helper.account3; | ||
generationHash = helper.generationHash; | ||
networkType = helper.networkType; | ||
transactionRepository = helper.repositoryFactory.createTransactionRepository(); | ||
receiptRepository = helper.repositoryFactory.createReceiptRepository(); | ||
blockService = new BlockService(helper.repositoryFactory); | ||
}); | ||
}); | ||
before(() => { | ||
return helper.listener.open(); | ||
}); | ||
|
||
after(() => { | ||
helper.listener.close(); | ||
}); | ||
|
||
/** | ||
* ========================= | ||
* Setup test data | ||
* ========================= | ||
*/ | ||
describe('Create a transfer', () => { | ||
it('Announce TransferTransaction', () => { | ||
const transferTransaction = TransferTransaction.create( | ||
Deadline.create(), | ||
account2.address, | ||
[NetworkCurrencyMosaic.createAbsolute(1)], | ||
PlainMessage.create('test-message'), | ||
networkType, | ||
helper.maxFee, | ||
); | ||
|
||
const signedTransaction = transferTransaction.signWith(account, generationHash); | ||
transactionHash = signedTransaction.hash; | ||
return helper.announce(signedTransaction); | ||
}); | ||
}); | ||
|
||
/** | ||
* ========================= | ||
* Test | ||
* ========================= | ||
*/ | ||
|
||
describe('Validate transansaction', () => { | ||
it('call block service', async () => { | ||
const transaction = await transactionRepository.getTransaction(transactionHash).toPromise(); | ||
const transactionInfo = transaction.transactionInfo; | ||
if (transactionInfo && transactionInfo.height !== undefined) { | ||
const validationResult = await blockService.validateTransactionInBlock(transactionHash, transactionInfo.height).toPromise(); | ||
expect(validationResult).to.be.true; | ||
} else { | ||
assert(false, `Transaction (hash: ${transactionHash}) not found`); | ||
} | ||
}); | ||
}); | ||
|
||
describe('Validate receipt', () => { | ||
it('call block service', async () => { | ||
const statements = await receiptRepository.getBlockReceipts(UInt64.fromUint(1)).toPromise(); | ||
const statement = statements.transactionStatements[0]; | ||
const validationResult = await blockService.validateStatementInBlock(statement.generateHash(), UInt64.fromUint(1)).toPromise(); | ||
expect(validationResult).to.be.true; | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* Copyright 2020 NEM | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { sha3_256 } from 'js-sha3'; | ||
import { combineLatest, Observable, of } from 'rxjs'; | ||
import { catchError, map } from 'rxjs/operators'; | ||
import { BlockRepository } from '../infrastructure/BlockRepository'; | ||
import { ReceiptRepository } from '../infrastructure/ReceiptRepository'; | ||
import { RepositoryFactory } from '../infrastructure/RepositoryFactory'; | ||
import { MerklePathItem } from '../model/blockchain/MerklePathItem'; | ||
import { UInt64 } from '../model/UInt64'; | ||
|
||
/** | ||
* Transaction Service | ||
*/ | ||
export class BlockService { | ||
private readonly blockRepository: BlockRepository; | ||
private readonly receiptRepository: ReceiptRepository; | ||
|
||
/** | ||
* Constructor | ||
* @param repositoryFactory | ||
*/ | ||
constructor(public readonly repositoryFactory: RepositoryFactory) { | ||
this.blockRepository = repositoryFactory.createBlockRepository(); | ||
this.receiptRepository = repositoryFactory.createReceiptRepository(); | ||
} | ||
|
||
/** | ||
* Validate transaction hash in block | ||
* @param leaf transaction hash | ||
* @param height block height | ||
*/ | ||
public validateTransactionInBlock(leaf: string, height: UInt64): Observable<boolean> { | ||
const rootHashObservable = this.blockRepository.getBlockByHeight(height); | ||
const merklePathItemObservable = this.blockRepository.getMerkleTransaction(height, leaf); | ||
return combineLatest(rootHashObservable, merklePathItemObservable).pipe( | ||
map((combined) => this.validateInBlock(leaf, combined[1].merklePath, combined[0].blockTransactionsHash)), | ||
).pipe(catchError(() => of(false))); | ||
} | ||
|
||
/** | ||
* Validate statement hash in block | ||
* @param leaf statement hash | ||
* @param height block height | ||
*/ | ||
public validateStatementInBlock(leaf: string, height: UInt64): Observable<boolean> { | ||
const rootHashObservable = this.blockRepository.getBlockByHeight(height); | ||
const merklePathItemObservable = this.receiptRepository.getMerkleReceipts(height, leaf); | ||
return combineLatest(rootHashObservable, merklePathItemObservable).pipe( | ||
map((combined) => this.validateInBlock(leaf, combined[1].merklePath, combined[0].blockReceiptsHash)), | ||
).pipe(catchError(() => of(false))); | ||
} | ||
|
||
/** | ||
* @internal | ||
* Validate leaf against merkle tree in block | ||
* @param leaf Leaf hash in merkle tree | ||
* @param merklePathItem Merkle path item array | ||
* @param rootHash Block root hash | ||
*/ | ||
private validateInBlock(leaf: string, merklePathItem: MerklePathItem[] = [], rootHash: string): boolean { | ||
if (merklePathItem.length === 0) { | ||
return leaf.toUpperCase() === rootHash.toUpperCase(); | ||
} | ||
const rootToCompare = merklePathItem.reduce((proofHash, pathItem) => { | ||
const hasher = sha3_256.create(); | ||
// Left | ||
if (pathItem.position === 1) { | ||
return hasher.update(Buffer.from(pathItem.hash + proofHash, 'hex')).hex(); | ||
} else { | ||
// Right | ||
return hasher.update(Buffer.from(proofHash + pathItem.hash, 'hex')).hex(); | ||
} | ||
Comment on lines
+82
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The position is being changed from a number to a (left/right) string. Rest has done the change in master, we are still waiting for the open api PR symbol/symbol-openapi#77 @dgarcia360 can we move 77 PR forward? once released we can then generate the clients and update the ts and java PR related to merkle tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
}, leaf); | ||
return rootToCompare.toUpperCase() === rootHash.toUpperCase(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* Copyright 2020 NEM | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { Observable } from 'rxjs'; | ||
import { UInt64 } from '../../model/UInt64'; | ||
|
||
/** | ||
* Block Service Interface | ||
*/ | ||
export interface IBlockService { | ||
|
||
/** | ||
* Validate transaction hash in block | ||
* @param leaf transaction hash | ||
* @param height block height | ||
*/ | ||
validateTransactionInBlock(leaf: string, height: UInt64): Observable<boolean>; | ||
|
||
/** | ||
* Validate statement hash in block | ||
* @param leaf statement hash | ||
* @param height block height | ||
*/ | ||
validateStatementInBlock(leaf: string, height: UInt64): Observable<boolean>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* | ||
* Copyright 2019 NEM | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { expect } from 'chai'; | ||
import { of as observableOf } from 'rxjs'; | ||
import { deepEqual, instance, mock, when } from 'ts-mockito'; | ||
import { BlockRepository } from '../../src/infrastructure/BlockRepository'; | ||
import { ReceiptRepository } from '../../src/infrastructure/ReceiptRepository'; | ||
import { RepositoryFactory } from '../../src/infrastructure/RepositoryFactory'; | ||
import { Account } from '../../src/model/account/Account'; | ||
import { BlockInfo } from '../../src/model/blockchain/BlockInfo'; | ||
import { MerklePathItem } from '../../src/model/blockchain/MerklePathItem'; | ||
import { MerkleProofInfo } from '../../src/model/blockchain/MerkleProofInfo'; | ||
import { NetworkType } from '../../src/model/blockchain/NetworkType'; | ||
import { UInt64 } from '../../src/model/UInt64'; | ||
import { BlockService } from '../../src/service/BlockService'; | ||
import { TestingAccount } from '../conf/conf.spec'; | ||
|
||
describe('BlockService', () => { | ||
|
||
const mockBlockHash = 'D4EC16FCFE696EFDBF1820F68245C88135ACF4C6F888599C8E18BC09B9F08C7B'; | ||
const leaf = '2717C8AAB0A21896D0C56375209E761F84383C3882F37A11D9D0159007263EB2'; | ||
let blockService: BlockService; | ||
let account: Account; | ||
before(() => { | ||
account = TestingAccount; | ||
const mockBlockRepository = mock<BlockRepository>(); | ||
const mockReceiptRepository = mock<ReceiptRepository>(); | ||
const mockRepoFactory = mock<RepositoryFactory>(); | ||
|
||
when(mockBlockRepository.getBlockByHeight(deepEqual(UInt64.fromUint(1)))) | ||
.thenReturn(observableOf(mockBlockInfo())); | ||
when(mockBlockRepository.getBlockByHeight(deepEqual(UInt64.fromUint(2)))) | ||
.thenReturn(observableOf(mockBlockInfo(true))); | ||
when(mockBlockRepository.getMerkleTransaction(deepEqual(UInt64.fromUint(1)), leaf)) | ||
.thenReturn(observableOf(mockMerklePath())); | ||
when(mockBlockRepository.getMerkleTransaction(deepEqual(UInt64.fromUint(2)), leaf)) | ||
.thenReturn(observableOf(mockMerklePath())); | ||
when(mockReceiptRepository.getMerkleReceipts(deepEqual(UInt64.fromUint(1)), leaf)) | ||
.thenReturn(observableOf(mockMerklePath())); | ||
when(mockReceiptRepository.getMerkleReceipts(deepEqual(UInt64.fromUint(2)), leaf)) | ||
.thenReturn(observableOf(mockMerklePath())); | ||
const blockRepository = instance(mockBlockRepository); | ||
const receiptRepository = instance(mockReceiptRepository); | ||
|
||
when(mockRepoFactory.createBlockRepository()).thenReturn(blockRepository); | ||
when(mockRepoFactory.createReceiptRepository()).thenReturn(receiptRepository); | ||
const repoFactory = instance(mockRepoFactory); | ||
blockService = new BlockService(repoFactory); | ||
}); | ||
|
||
it('should validate transaction', async () => { | ||
const result = await blockService.validateTransactionInBlock(leaf, UInt64.fromUint(1)).toPromise(); | ||
expect(result).to.be.true; | ||
}); | ||
|
||
it('should validate transaction - wrong hash', async () => { | ||
const result = await blockService.validateTransactionInBlock(leaf, UInt64.fromUint(2)).toPromise(); | ||
expect(result).to.be.false; | ||
}); | ||
|
||
it('should validate statement', async () => { | ||
const result = await blockService.validateStatementInBlock(leaf, UInt64.fromUint(1)).toPromise(); | ||
expect(result).to.be.true; | ||
}); | ||
|
||
it('should validate statement - wrong hash', async () => { | ||
const result = await blockService.validateStatementInBlock(leaf, UInt64.fromUint(2)).toPromise(); | ||
expect(result).to.be.false; | ||
}); | ||
|
||
function mockBlockInfo(isFake: boolean = false): BlockInfo { | ||
if (isFake) { | ||
return new BlockInfo( | ||
'hash', 'generationHash', UInt64.fromNumericString('0'), 1, 'signature', account.publicAccount, | ||
NetworkType.MIJIN_TEST, 0, 0, UInt64.fromUint(1), UInt64.fromUint(0), UInt64.fromUint(0), 0, 'previousHash', | ||
'fakeHash', 'fakeHash', 'stateHash', undefined, | ||
); | ||
} | ||
return new BlockInfo( | ||
'hash', 'generationHash', UInt64.fromNumericString('0'), 1, 'signature', account.publicAccount, | ||
NetworkType.MIJIN_TEST, 0, 0, UInt64.fromUint(1), UInt64.fromUint(0), UInt64.fromUint(0), 0, 'previousHash', | ||
mockBlockHash, mockBlockHash, 'stateHash', undefined, | ||
); | ||
} | ||
|
||
function mockMerklePath(): MerkleProofInfo { | ||
return new MerkleProofInfo( | ||
[ | ||
new MerklePathItem(1, 'CDE45D740536E5361F392025A44B26546A138958E69CD6F18D22908F8F11ECF2'), | ||
new MerklePathItem(2, '4EF55DAB8FEF9711B23DA71D2ACC58EFFF3969C3D572E06ACB898F99BED4827A'), | ||
new MerklePathItem(1, '1BB95470065ED69D184948A0175EDC2EAB9E86A0CEB47B648A58A02A5445AF66'), | ||
new MerklePathItem(2, 'D96B03809B8B198EFA5824191A979F7B85C0E9B7A6623DAFF38D4B2927EFDFB5'), | ||
new MerklePathItem(2, '9981EBDBCA8E36BA4D4D4A450072026AC8C85BA6497666219E0E049BE3362E51'), | ||
], | ||
); | ||
} | ||
}); |
Uh oh!
There was an error while loading. Please reload this page.