test paring transaction validation

This commit is contained in:
einhorn_b 2024-01-27 17:23:09 +01:00
parent 8a135bd983
commit 8445c0bec2
2 changed files with 364 additions and 21 deletions

View File

@ -0,0 +1,323 @@
import { Community } from '@entity/Community'
import { Transaction } from '@entity/Transaction'
import { Decimal } from 'decimal.js-light'
import { logger } from '@/logging/logger'
import { CommunityRoot } from './proto/3_3/CommunityRoot'
import { CrossGroupType } from './proto/3_3/enum/CrossGroupType'
import { GradidoTransfer } from './proto/3_3/GradidoTransfer'
import { RegisterAddress } from './proto/3_3/RegisterAddress'
import { TransactionBody } from './proto/3_3/TransactionBody'
import { TransactionLogic } from './Transaction.logic'
import { GradidoCreation } from './proto/3_3/GradidoCreation'
import { GradidoDeferredTransfer } from './proto/3_3/GradidoDeferredTransfer'
let a: Transaction
let b: Transaction
describe('data/transaction.logic', () => {
describe('isBelongTogether', () => {
beforeEach(() => {
const now = new Date()
let body = new TransactionBody()
body.type = CrossGroupType.OUTBOUND
body.transfer = new GradidoTransfer()
body.otherGroup = 'recipient group'
a = new Transaction()
a.community = new Community()
a.communityId = 1
a.otherCommunityId = 2
a.id = 1
a.signingAccountId = 1
a.recipientAccountId = 2
a.createdAt = now
a.amount = new Decimal('100')
a.signature = Buffer.alloc(64)
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
body = new TransactionBody()
body.type = CrossGroupType.INBOUND
body.transfer = new GradidoTransfer()
body.otherGroup = 'sending group'
b = new Transaction()
b.community = new Community()
b.communityId = 1
b.otherCommunityId = 2
b.id = 2
b.signingAccountId = 1
b.recipientAccountId = 2
b.createdAt = now
b.amount = new Decimal('100')
b.signature = Buffer.alloc(64)
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
})
const spy = jest.spyOn(logger, 'info')
it('true', () => {
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(true)
})
it('false because of same id', () => {
b.id = 1
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith('id is the same, it is the same transaction!')
})
it('false because of different signing accounts', () => {
b.signingAccountId = 17
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
'transaction a and b are not pairs',
expect.objectContaining({}),
)
})
it('false because of different recipient accounts', () => {
b.recipientAccountId = 21
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
'transaction a and b are not pairs',
expect.objectContaining({}),
)
})
it('false because of different community ids', () => {
b.communityId = 6
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
'transaction a and b are not pairs',
expect.objectContaining({}),
)
})
it('false because of different other community ids', () => {
b.otherCommunityId = 3
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
'transaction a and b are not pairs',
expect.objectContaining({}),
)
})
it('false because of different createdAt', () => {
b.createdAt = new Date('2021-01-01T17:12')
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
'transaction a and b are not pairs',
expect.objectContaining({}),
)
})
describe('false because of mismatching cross group type', () => {
const body = new TransactionBody()
it('a is LOCAL', () => {
body.type = CrossGroupType.LOCAL
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenNthCalledWith(7, 'no one can be LOCAL')
expect(spy).toHaveBeenLastCalledWith(
"cross group types don't match",
expect.objectContaining({}),
)
})
it('b is LOCAL', () => {
body.type = CrossGroupType.LOCAL
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenNthCalledWith(9, 'no one can be LOCAL')
expect(spy).toHaveBeenLastCalledWith(
"cross group types don't match",
expect.objectContaining({}),
)
})
it('both are INBOUND', () => {
body.type = CrossGroupType.INBOUND
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
"cross group types don't match",
expect.objectContaining({}),
)
})
it('both are OUTBOUND', () => {
body.type = CrossGroupType.OUTBOUND
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
"cross group types don't match",
expect.objectContaining({}),
)
})
it('a is CROSS', () => {
body.type = CrossGroupType.CROSS
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
"cross group types don't match",
expect.objectContaining({}),
)
})
it('b is CROSS', () => {
body.type = CrossGroupType.CROSS
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
"cross group types don't match",
expect.objectContaining({}),
)
})
it('true with a as INBOUND and b as OUTBOUND', () => {
let body = TransactionBody.fromBodyBytes(a.bodyBytes)
body.type = CrossGroupType.INBOUND
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
body = TransactionBody.fromBodyBytes(b.bodyBytes)
body.type = CrossGroupType.OUTBOUND
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(true)
})
})
describe('false because of transaction type not suitable for cross group transactions', () => {
const body = new TransactionBody()
body.type = CrossGroupType.OUTBOUND
it('without transaction type (broken TransactionBody)', () => {
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(() => logic.isBelongTogether(b)).toThrowError("couldn't determine transaction type")
})
it('not the same transaction types', () => {
const body = new TransactionBody()
body.type = CrossGroupType.OUTBOUND
body.registerAddress = new RegisterAddress()
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
"transaction types don't match",
expect.objectContaining({}),
)
})
it('community root cannot be a cross group transaction', () => {
let body = new TransactionBody()
body.type = CrossGroupType.OUTBOUND
body.communityRoot = new CommunityRoot()
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
body = new TransactionBody()
body.type = CrossGroupType.INBOUND
body.communityRoot = new CommunityRoot()
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
"TransactionType COMMUNITY_ROOT couldn't be a CrossGroup Transaction",
)
})
it('Gradido Creation cannot be a cross group transaction', () => {
let body = new TransactionBody()
body.type = CrossGroupType.OUTBOUND
body.creation = new GradidoCreation()
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
body = new TransactionBody()
body.type = CrossGroupType.INBOUND
body.creation = new GradidoCreation()
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
"TransactionType GRADIDO_CREATION couldn't be a CrossGroup Transaction",
)
})
it('Deferred Transfer cannot be a cross group transaction', () => {
let body = new TransactionBody()
body.type = CrossGroupType.OUTBOUND
body.deferredTransfer = new GradidoDeferredTransfer()
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
body = new TransactionBody()
body.type = CrossGroupType.INBOUND
body.deferredTransfer = new GradidoDeferredTransfer()
b.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith(
"TransactionType GRADIDO_DEFERRED_TRANSFER couldn't be a CrossGroup Transaction",
)
})
})
describe('false because of wrong amount', () => {
it('amount missing on a', () => {
a.amount = undefined
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith('missing amount')
})
it('amount missing on b', () => {
b.amount = undefined
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith('missing amount')
})
it('amount not the same', () => {
a.amount = new Decimal('101')
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith('amounts mismatch', expect.objectContaining({}))
})
})
it('false because otherGroup are the same', () => {
const body = new TransactionBody()
body.type = CrossGroupType.OUTBOUND
body.transfer = new GradidoTransfer()
body.otherGroup = 'sending group'
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith('otherGroups are the same', expect.objectContaining({}))
})
it('false because of different memos', () => {
const body = new TransactionBody()
body.type = CrossGroupType.OUTBOUND
body.transfer = new GradidoTransfer()
body.otherGroup = 'recipient group'
body.memo = 'changed memo'
a.bodyBytes = Buffer.from(TransactionBody.encode(body).finish())
const logic = new TransactionLogic(a)
expect(logic.isBelongTogether(b)).toBe(false)
expect(spy).toHaveBeenLastCalledWith('memo differ', expect.objectContaining({}))
})
})
})

View File

@ -83,30 +83,26 @@ export class TransactionLogic {
this.self.recipientAccountId !== otherTransaction.recipientAccountId ||
this.self.communityId !== otherTransaction.communityId ||
this.self.otherCommunityId !== otherTransaction.otherCommunityId ||
this.self.accountBalanceOnCreation !== otherTransaction.accountBalanceOnCreation ||
this.self.createdAt.getTime() !== otherTransaction.createdAt.getTime()
) {
logger.debug('transaction a and b are not pairs', {
a: new TransactionLoggingView(this.self),
b: new TransactionLoggingView(otherTransaction),
logger.info('transaction a and b are not pairs', {
a: new TransactionLoggingView(this.self).toJSON(),
b: new TransactionLoggingView(otherTransaction).toJSON(),
})
return false
}
const body = this.getBody()
const otherBody = TransactionBody.fromBodyBytes(otherTransaction.bodyBytes)
/**
* both can be Cross
* both must be Cross or
* one can be OUTBOUND and one can be INBOUND
* no one can be LOCAL
*/
if (
(body.type === otherBody.type && body.type !== CrossGroupType.CROSS) ||
body.type === CrossGroupType.LOCAL ||
otherBody.type === CrossGroupType.LOCAL
) {
if (!this.validCrossGroupTypes(body.type, otherBody.type)) {
logger.info("cross group types don't match", {
a: new TransactionBodyLoggingView(body),
b: new TransactionBodyLoggingView(otherBody),
a: new TransactionBodyLoggingView(body).toJSON(),
b: new TransactionBodyLoggingView(otherBody).toJSON(),
})
return false
}
@ -114,14 +110,14 @@ export class TransactionLogic {
const otherType = otherBody.getTransactionType()
if (!type || !otherType) {
throw new LogError("couldn't determine transaction type", {
a: new TransactionBodyLoggingView(body),
b: new TransactionBodyLoggingView(otherBody),
a: new TransactionBodyLoggingView(body).toJSON(),
b: new TransactionBodyLoggingView(otherBody).toJSON(),
})
}
if (type !== otherType) {
logger.info("transaction types don't match", {
a: new TransactionBodyLoggingView(body),
b: new TransactionBodyLoggingView(otherBody),
a: new TransactionBodyLoggingView(body).toJSON(),
b: new TransactionBodyLoggingView(otherBody).toJSON(),
})
return false
}
@ -132,7 +128,7 @@ export class TransactionLogic {
TransactionType.GRADIDO_DEFERRED_TRANSFER,
].includes(type)
) {
logger.info(`TransactionType ${type} couldn't be a CrossGroup Transaction`)
logger.info(`TransactionType ${TransactionType[type]} couldn't be a CrossGroup Transaction`)
return false
}
if (
@ -156,21 +152,45 @@ export class TransactionLogic {
}
if (body.otherGroup === otherBody.otherGroup) {
logger.info('otherGroups are the same', {
a: new TransactionBodyLoggingView(body),
b: new TransactionBodyLoggingView(otherBody),
a: new TransactionBodyLoggingView(body).toJSON(),
b: new TransactionBodyLoggingView(otherBody).toJSON(),
})
return false
}
if (body.memo !== otherBody.memo) {
logger.info('memo differ', {
a: new TransactionBodyLoggingView(body),
b: new TransactionBodyLoggingView(otherBody),
a: new TransactionBodyLoggingView(body).toJSON(),
b: new TransactionBodyLoggingView(otherBody).toJSON(),
})
return false
}
return true
}
/**
* both must be CROSS or
* one can be OUTBOUND and one can be INBOUND
* no one can be LOCAL
* @return true if crossGroupTypes are valid
*/
protected validCrossGroupTypes(a: CrossGroupType, b: CrossGroupType): boolean {
logger.debug('compare ', {
a: CrossGroupType[a],
b: CrossGroupType[b],
})
if (a === CrossGroupType.LOCAL || b === CrossGroupType.LOCAL) {
logger.info('no one can be LOCAL')
return false
}
if (
(a === CrossGroupType.INBOUND && b === CrossGroupType.OUTBOUND) ||
(a === CrossGroupType.OUTBOUND && b === CrossGroupType.INBOUND)
) {
return true // One can be INBOUND and one can be OUTBOUND
}
return a === CrossGroupType.CROSS && b === CrossGroupType.CROSS
}
public getBody(): TransactionBody {
if (!this.transactionBody) {
this.transactionBody = TransactionBody.fromBodyBytes(this.self.bodyBytes)