refactor: Add common transaction fields Typescript definition (#1507)

refactor: Add common transaction fields Typescript definition (#1507)
This commit is contained in:
Nathan Nichols
2021-08-11 17:37:10 -05:00
committed by Mayukha Vadari
parent c431e70900
commit d438430100
11 changed files with 822 additions and 285 deletions

View File

@@ -38,6 +38,7 @@
"elliptic": "^6.5.4"
},
"devDependencies": {
"@types/chai": "^4.2.21",
"@types/mocha": "^9.0.0",
"@types/node": "^16.4.3",
"@typescript-eslint/eslint-plugin": "^2.3.3",
@@ -45,6 +46,7 @@
"assert": "^2.0.0",
"assert-diff": "^3.0.0",
"buffer": "^6.0.2",
"chai": "^4.3.4",
"crypto-browserify": "^3.12.0",
"doctoc": "^2.0.0",
"ejs": "^3.0.1",

View File

@@ -19,6 +19,18 @@ export interface IssuedCurrencyAmount extends IssuedCurrency {
export type Amount = IssuedCurrencyAmount | string
export interface Signer {
Account: string;
TxnSignature: string;
SigningPubKey: string;
}
export interface Memo {
MemoData?: string;
MemoType?: string;
MemoFormat?: string;
}
export type StreamType = "consensus" | "ledger" | "manifests" | "peer_status" | "transactions" | "transactions_proposed" | "server" | "validations"
interface PathStep {

View File

@@ -0,0 +1,38 @@
import { Amount } from ".";
interface CreatedNode {
CreatedNode: {
LedgerEntryType: string
LedgerIndex: string
NewFields: {[field: string]: any}
}
}
interface ModifiedNode {
ModifiedNode: {
LedgerEntryType: string
LedgerIndex: string
FinalFields: {[field: string]: any}
PreviousFields: {[field: string]: any}
PreviousTxnID?: string
PreviouTxnLgrSeq?: number
}
}
interface DeletedNode {
DeletedNode: {
LedgerEntryType: string
LedgerIndex: string
FinalFields: {[field: string]: any}
}
}
type Node = CreatedNode | ModifiedNode | DeletedNode
export default interface Metadata {
AffectedNodes: Node[]
DeliveredAmount?: Amount
delivered_amount?: Amount
TransactionIndex: number
TransactionResult: string
}

View File

@@ -1,5 +1,5 @@
import { LedgerIndex } from "../common";
import { TransactionMetadata } from "../common/transaction";
import Metadata from "../common/metadata";
import { BaseRequest, BaseResponse } from "./baseMethod";
export interface AccountTxRequest extends BaseRequest {
@@ -17,7 +17,7 @@ export interface AccountTxRequest extends BaseRequest {
interface AccountTransaction {
ledger_index: number
meta: string | TransactionMetadata
meta: string | Metadata
tx?: any // TODO: replace when transaction objects are done
tx_blob?: string
validated: boolean

View File

@@ -1,4 +1,4 @@
import { Response } from ".";
import { Request } from './../methods'
export interface BaseRequest {
id: number | string
@@ -21,6 +21,6 @@ export interface BaseResponse {
warnings?: Warning[]
forwarded?: boolean
error?: string
request?: Response
request?: Request
api_version?: number
}

View File

@@ -0,0 +1,131 @@
import { ValidationError } from "../../common/errors"
import { Memo, Signer } from "../common"
import { onlyHasFields } from "../utils"
const transactionTypes = [
"AccountSet",
"AccountDelete",
"CheckCancel",
"CheckCash",
"CheckCreate",
"DepositPreauth",
"EscrowCancel",
"EscrowCreate",
"EscrowFinish",
"OfferCancel",
"OfferCreate",
"Payment",
"PaymentChannelClaim",
"PaymentChannelCreate",
"PaymentChannelFund",
"SetRegularKey",
"SignerListSet",
"TicketCreate",
"TrustSet",
]
const isMemo = (obj: {Memo: Memo}): boolean => {
const memo = obj.Memo
const size = Object.keys(memo).length
const validData = memo.MemoData === undefined
|| typeof memo.MemoData === 'string'
const validFormat = memo.MemoFormat === undefined
|| typeof memo.MemoData === 'string'
const validType = memo.MemoType === undefined
|| typeof memo.MemoType === 'string'
return (1 <= size && size <= 3)
&& validData
&& validFormat
&& validType
&& onlyHasFields(memo, ["MemoFormat", "MemoData", "MemoType"])
}
const isSigner = (signer: Signer): boolean => {
return Object.keys(signer).length === 3
&& typeof signer.Account === 'string'
&& typeof signer.TxnSignature === 'string'
&& typeof signer.SigningPubKey === 'string'
}
export interface CommonFields {
Account: string;
TransactionType: string;
Fee?: string;
Sequence?: number;
AccountTxnID?: string;
Flags?: number;
LastLedgerSequence?: number;
Memos?: Array<{ Memo: Memo }>;
Signers?: Array<Signer>;
SourceTag?: number;
SigningPubKey?: string;
TicketSequence?: number;
TxnSignature?: string;
}
/**
* verify the common fields of a transaction. The verify functionality will be
* optional, and will check transaction form at runtime. This should be called
* any time a transaction will be verified.
*
* @param common - An interface w/ common transaction fields
* @returns - Void
* @throws - When the common param is malformed.
*/
export function verifyCommonFields(common: CommonFields): void {
if (common.Account === undefined)
throw new ValidationError("CommonFields: missing field Account")
if (typeof common.Account !== 'string')
throw new ValidationError("CommonFields: Account not string")
if (common.TransactionType === undefined)
throw new ValidationError("CommonFields: missing field TransactionType")
if (typeof common.TransactionType !== 'string')
throw new ValidationError("CommonFields: TransactionType not string")
if (!transactionTypes.includes(common.TransactionType))
throw new ValidationError("CommonFields: Unknown TransactionType")
if (common.Fee !== undefined && typeof common.Fee !== 'string')
throw new ValidationError("CommonFields: invalid Fee")
if (common.Sequence !== undefined && typeof common.Sequence !== 'number')
throw new ValidationError("CommonFields: invalid Sequence")
if (common.Flags !== undefined && typeof common.Flags !== 'number')
throw new ValidationError("CommonFields: invalid Flags")
if (common.AccountTxnID !== undefined
&& typeof common.AccountTxnID !== 'string')
throw new ValidationError("CommonFields: invalid AccountTxnID")
if (common.LastLedgerSequence !== undefined
&& typeof common.LastLedgerSequence !== 'number')
throw new ValidationError("CommonFields: invalid LastLedgerSequence")
if (common.Memos !== undefined
&& (common.Memos.length === 0 || !common.Memos.every(isMemo)))
throw new ValidationError("CommonFields: invalid Memos")
if (common.Signers !== undefined
&& (common.Signers.length === 0 || !common.Signers.every(isSigner)))
throw new ValidationError("CommonFields: invalid Signers")
if (common.SourceTag !== undefined && typeof common.SourceTag !== 'number')
throw new ValidationError("CommonFields: invalid SourceTag")
if (common.SigningPubKey !== undefined
&& typeof common.SigningPubKey !== 'string')
throw new ValidationError("CommonFields: invalid SigningPubKey")
if (common.TicketSequence !== undefined
&& typeof common.TicketSequence !== 'number')
throw new ValidationError("CommonFields: invalid TicketSequence")
if (common.TxnSignature !== undefined
&& typeof common.TxnSignature !== 'string')
throw new ValidationError("CommonFields: invalid TxnSignature")
}

View File

@@ -0,0 +1,28 @@
import Metadata from "../common/metadata";
export type Transaction = never // (For now)
// AccountSet
// | AccountDelete
// | CheckCancel
// | CheckCash
// | CheckCreate
// | DepositPreauth
// | EscrowCancel
// | EscrowCreate
// | EscrowFinish
// | OfferCancel
// | OfferCreate
// | PaymentTransaction
// | PaymentChannelClaim
// | PaymentChannelCreate
// | PaymentChannelFund
// | SetRegularKey
// | SignerListSet
// | TicketCreate
// | TrustSet
export interface TransactionAndMetadata {
transaction: Transaction;
metadata: Metadata
}

10
src/models/utils/index.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* Verify that all fields of an object are in fields
*
* @param obj Object to verify fields
* @param fields Fields to verify
* @returns True if keys in object are all in fields
*/
export function onlyHasFields(obj: object, fields: Array<string>): boolean {
return Object.keys(obj).every((key:string) => fields.includes(key))
}

View File

@@ -1,6 +1,6 @@
{
"diff": true,
"spec": ["./test/*.ts"],
"spec": ["./test/*.ts", "./test/models/**/*.ts"],
"extension": ["ts"],
"package": "../package.json",
"require": "ts-node/register",

View File

@@ -0,0 +1,240 @@
import { ValidationError } from 'ripple-api/common/errors'
import { verifyCommonFields } from '../../src/models/transactions/common'
import { assert } from 'chai'
/**
* Transaction Verification Testing
*
* Providing runtime verification testing for each specific transaction type
*/
describe('Transaction Verification', function () {
it(`Verifies all optional CommonFields`, () => {
const txJson = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
Fee: "12",
Sequence: 100,
AccountTxnID: "DEADBEEF",
Flags: 15,
LastLedgerSequence: 1383,
Memos: [
{
Memo: {
MemoType: "687474703a2f2f6578616d706c652e636f6d2f6d656d6f2f67656e65726963",
MemoData: "72656e74"
}
},
{
Memo: {
MemoFormat: "687474703a2f2f6578616d706c652e636f6d2f6d656d6f2f67656e65726963",
MemoData: "72656e74"
}
},
{
Memo: {
MemoType: "72656e74"
}
}
],
Signers: [
{
Account: "r....",
TxnSignature: "DEADBEEF",
SigningPubKey: "hex-string"
}
],
SourceTag: 31,
SigningPublicKey: "03680DD274EE55594F7244F489CD38CF3A5A1A4657122FB8143E185B2BA043DF36",
TicketSequence: 10,
TxnSignature: "3045022100C6708538AE5A697895937C758E99A595B57A16393F370F11B8D4C032E80B532002207776A8E85BB9FAF460A92113B9C60F170CD964196B1F084E0DAB65BAEC368B66"
}
assert.doesNotThrow(() => verifyCommonFields(txJson))
})
it(`Verifies only required CommonFields`, () => {
const txJson = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
}
assert.doesNotThrow(() => verifyCommonFields(txJson))
})
it (`Handles invalid Fee`, () => {
const invalidFee = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
Fee: 1000
} as any
assert.throws(
() => verifyCommonFields(invalidFee),
ValidationError,
"CommonFields: invalid Fee"
)
})
it (`Handles invalid Sequence`, () => {
const invalidSeq = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
Sequence: "145"
} as any
assert.throws(
() => verifyCommonFields(invalidSeq),
ValidationError,
"CommonFields: invalid Sequence"
)
})
it (`Handles invalid AccountTxnID`, () => {
const invalidID = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
AccountTxnID: ["WRONG"]
} as any
assert.throws(
() => verifyCommonFields(invalidID),
ValidationError,
"CommonFields: invalid AccountTxnID"
)
})
it (`Handles invalid Flags`, () => {
const invalidFlags = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
Flags: "1000"
} as any
assert.throws(
() => verifyCommonFields(invalidFlags),
ValidationError,
"CommonFields: invalid Flags"
)
})
it (`Handles invalid LastLedgerSequence`, () => {
const invalidLastLedgerSequence = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
LastLedgerSequence: "1000"
} as any
assert.throws(
() => verifyCommonFields(invalidLastLedgerSequence),
ValidationError,
"CommonFields: invalid LastLedgerSequence"
)
})
it (`Handles invalid SourceTag`, () => {
const invalidSourceTag = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
SourceTag: ["ARRAY"]
} as any
assert.throws(
() => verifyCommonFields(invalidSourceTag),
ValidationError,
"CommonFields: invalid SourceTag"
)
})
it (`Handles invalid SigningPubKey`, () => {
const invalidSigningPubKey = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
SigningPubKey: 1000
} as any
assert.throws(
() => verifyCommonFields(invalidSigningPubKey),
ValidationError,
"CommonFields: invalid SigningPubKey"
)
})
it (`Handles invalid TicketSequence`, () => {
const invalidTicketSequence = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
TicketSequence: "1000"
} as any
assert.throws(
() => verifyCommonFields(invalidTicketSequence),
ValidationError,
"CommonFields: invalid TicketSequence"
)
})
it (`Handles invalid TxnSignature`, () => {
const invalidTxnSignature = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
TxnSignature: 1000
} as any
assert.throws(
() => verifyCommonFields(invalidTxnSignature),
ValidationError,
"CommonFields: invalid TxnSignature"
)
})
it (`Handles invalid Signers`, () => {
const invalidSigners = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
Signers: []
} as any
assert.throws(
() => verifyCommonFields(invalidSigners),
ValidationError,
"CommonFields: invalid Signers"
)
const invalidSigners2 = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
Signers: [
{
"Account": "r...."
}
]
} as any
assert.throws(
() => verifyCommonFields(invalidSigners2),
ValidationError,
"CommonFields: invalid Signers"
)
})
it (`Handles invalid Memo`, () => {
const invalidMemo = {
Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe",
TransactionType: "Payment",
Memos: [{
Memo: {
MemoData: "HI",
Address: "WRONG"
}
}]
} as any
assert.throws(
() => verifyCommonFields(invalidMemo),
ValidationError,
"CommonFields: invalid Memos"
)
})
})

636
yarn.lock

File diff suppressed because it is too large Load Diff