Skip to content
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

Stricter type checks #169

Merged
merged 8 commits into from
Jun 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@types/cron": "^1.7.3",
"@types/elliptic": "^6.4.14",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.2",
"@types/node": "^16.11.6",
Expand Down
10 changes: 5 additions & 5 deletions backend/src/accounts/controllers/accounts.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class AccountsController {
@ApiQuery({ name: "timestamp", type: Number })
@Post("/polling")
@UseInterceptors(new PollingResponseInterceptor(GetPollingAccountsResponse))
async findAllNew(@Body() data) {
async findAllNew(@Body() data: unknown) {
const request = GetPollingAccountsRequest.fromJSON(data);
const accounts = await this.accountsService.findAllNewerThanTimestamp(
new Date(request.timestamp)
Expand All @@ -53,8 +53,8 @@ export class AccountsController {
@Post(":address/keys/polling")
@UseInterceptors(new PollingResponseInterceptor(GetPollingKeysResponse))
async findAllNewKeysByAccount(
@Param("address") accountAddress,
@Body() data
@Param("address") accountAddress: string,
@Body() data: unknown
) {
const request = GetPollingKeysRequest.fromJSON(data);
const keys = await this.keysService.findAllNewerThanTimestampByAccount(
Expand All @@ -68,8 +68,8 @@ export class AccountsController {
@Post(":address/storage/polling")
@UseInterceptors(new PollingResponseInterceptor(GetPollingStorageResponse))
async findAllNewStorageByAccount(
@Param("address") accountAddress,
@Body() data
@Param("address") accountAddress: string,
@Body() data: unknown
) {
const request = GetPollingStorageRequest.fromJSON(data);
const storageItems =
Expand Down
7 changes: 5 additions & 2 deletions backend/src/accounts/controllers/contracts.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class ContractsController {

@Post("contracts/polling")
@UseInterceptors(new PollingResponseInterceptor(GetPollingContractsResponse))
async findAllNew(@Body() data) {
async findAllNew(@Body() data: unknown) {
const request = GetPollingContractsRequest.fromJSON(data);
const contracts = await this.contractsService.findAllNewerThanTimestamp(
new Date(request.timestamp)
Expand All @@ -42,7 +42,10 @@ export class ContractsController {
@ApiParam({ name: "id", type: String })
@Post("/accounts/:address/contracts/polling")
@UseInterceptors(new PollingResponseInterceptor(GetPollingContractsResponse))
async findAllNewByAccount(@Param("address") accountAddress, @Body() data) {
async findAllNewByAccount(
@Param("address") accountAddress: string,
@Body() data: unknown
) {
const request = GetPollingContractsByAccountRequest.fromJSON(data);
const contracts =
await this.contractsService.findAllNewerThanTimestampByAccount(
Expand Down
64 changes: 46 additions & 18 deletions backend/src/accounts/entities/account.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { Account } from "@flowser/shared";
import { TransactionEntity } from "../../transactions/transaction.entity";
import { AccountStorageItemEntity } from "./storage-item.entity";
import { BlockContextEntity } from "../../blocks/entities/block-context.entity";
import { PollingEntityInitArguments } from "../../utils/type-utils";

type AccountEntityInitArgs = PollingEntityInitArguments<AccountEntity>;

@Entity({ name: "accounts" })
export class AccountEntity extends PollingEntity implements BlockContextEntity {
Expand All @@ -30,49 +33,74 @@ export class AccountEntity extends PollingEntity implements BlockContextEntity {
@OneToMany(() => AccountKeyEntity, (key) => key.account, {
eager: true,
})
keys: AccountKeyEntity[];
keys?: AccountKeyEntity[];

@OneToMany(() => AccountStorageItemEntity, (storage) => storage.account, {
eager: true,
})
storage: AccountStorageItemEntity[];
storage?: AccountStorageItemEntity[];

@OneToMany(() => AccountContractEntity, (contract) => contract.account, {
eager: true,
})
contracts: AccountContractEntity[];
contracts?: AccountContractEntity[];

@OneToMany(() => TransactionEntity, (key) => key.payer, {
eager: true,
})
transactions: TransactionEntity[];
transactions?: TransactionEntity[];

// Entities are also automatically initialized by TypeORM.
// In those cases no constructor arguments are provided.
constructor(args: AccountEntityInitArgs | undefined) {
super();
this.address = args?.address ?? "";
this.blockId = args?.blockId ?? "";
this.balance = args?.balance ?? 0;
this.code = args?.code ?? "";
this.isDefaultAccount = args?.isDefaultAccount ?? false;
if (args?.keys) {
this.keys = args.keys;
}
if (args?.storage) {
this.storage = args.storage;
}
if (args?.contracts) {
this.contracts = args.contracts;
}
if (args?.transactions) {
this.transactions = args.transactions;
}
}

/**
* Creates an account with default values (where applicable).
* It doesn't pre-set the values that should be provided.
*/
static createDefault(): AccountEntity {
const account = new AccountEntity();
account.balance = 0;
account.code = "";
account.keys = [];
account.transactions = [];
account.contracts = [];
account.storage = [];
return account;
return new AccountEntity({
balance: 0,
address: "",
blockId: "",
isDefaultAccount: false,
code: "",
keys: [],
transactions: [],
contracts: [],
storage: [],
});
}

toProto(): Account {
return {
address: this.address,
balance: this.balance,
code: this.code,
storage: this.storage.map((storage) => storage.toProto()),
keys: this.keys.map((key) => key.toProto()),
contracts: this.contracts.map((contract) => contract.toProto()),
transactions: this.transactions.map((transaction) =>
transaction.toProto()
),
storage: this.storage?.map((storage) => storage.toProto()) ?? [],
keys: this.keys?.map((key) => key.toProto()) ?? [],
contracts: this.contracts?.map((contract) => contract.toProto()) ?? [],
transactions:
this.transactions?.map((transaction) => transaction.toProto()) ?? [],
isDefaultAccount: this.isDefaultAccount,
createdAt: this.createdAt.toISOString(),
updatedAt: this.updatedAt.toISOString(),
Expand Down
28 changes: 20 additions & 8 deletions backend/src/accounts/entities/contract.entity.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { PollingEntity } from "../../core/entities/polling.entity";
import {
Column,
Entity,
ManyToOne,
PrimaryColumn,
} from "typeorm";
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
import { AccountEntity } from "./account.entity";
import { BadRequestException } from "@nestjs/common";
import { AccountContract } from "@flowser/shared";
import { BlockContextEntity } from "../../blocks/entities/block-context.entity";
import { PollingEntityInitArguments } from "../../utils/type-utils";

type AccountContractEntityInitArgs = Omit<
PollingEntityInitArguments<AccountContractEntity>,
"id"
>;

@Entity({ name: "contracts" })
export class AccountContractEntity
Expand All @@ -29,7 +30,18 @@ export class AccountContractEntity
code: string;

@ManyToOne(() => AccountEntity, (account) => account.contracts)
account: AccountEntity;
account: AccountEntity | null;

// Entities are also automatically initialized by TypeORM.
// In those cases no constructor arguments are provided.
constructor(args: AccountContractEntityInitArgs | undefined) {
super();
this.accountAddress = args?.accountAddress ?? "";
this.name = args?.name ?? "";
this.blockId = args?.blockId ?? "";
this.code = args?.code ?? "";
this.account = args?.account ?? null;
}

toProto(): AccountContract {
return {
Expand All @@ -43,7 +55,7 @@ export class AccountContractEntity
}

get id() {
return `${this.accountAddress}.${this.name}`
return `${this.accountAddress}.${this.name}`;
}

public static decodeId(id: string) {
Expand Down
57 changes: 43 additions & 14 deletions backend/src/accounts/entities/key.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { AccountEntity } from "./account.entity";
import { AccountKey } from "@flowser/shared";
import { HashAlgorithm, SignatureAlgorithm } from "@flowser/shared";
import { BlockContextEntity } from "../../blocks/entities/block-context.entity";
import { PollingEntityInitArguments } from "../../utils/type-utils";

type AccountKeyEntityInitArgs = PollingEntityInitArguments<AccountKeyEntity>;

// https://developers.flow.com/tooling/flow-cli/accounts/create-accounts#key-weight
export const defaultKeyWeight = 1000;
Expand All @@ -21,13 +24,13 @@ export class AccountKeyEntity

// Nullable for backward compatability - to not cause not null constraint failure on migration.
@Column({ nullable: true })
blockId: string;
blockId: string = "NULL";

@Column()
publicKey: string;

@Column({ nullable: true })
privateKey: string | null;
privateKey: string;

@Column()
signAlgo: SignatureAlgorithm;
Expand All @@ -45,24 +48,50 @@ export class AccountKeyEntity
revoked: boolean;

@ManyToOne(() => AccountEntity, (account) => account.storage)
account: AccountEntity;
account?: AccountEntity;

// Entities are also automatically initialized by TypeORM.
// In those cases no constructor arguments are provided.
constructor(args: AccountKeyEntityInitArgs | undefined) {
super();
this.index = args?.index ?? -1;
this.accountAddress = args?.accountAddress ?? "";
this.blockId = args?.blockId ?? "";
this.publicKey = args?.publicKey ?? "";
this.privateKey = args?.privateKey ?? "";
this.signAlgo =
args?.signAlgo ?? SignatureAlgorithm.SIGNATURE_ALGORITHM_UNSPECIFIED;
this.hashAlgo = args?.hashAlgo ?? HashAlgorithm.HASH_ALGORITHM_UNSPECIFIED;
this.weight = args?.weight ?? -1;
this.sequenceNumber = args?.sequenceNumber ?? -1;
this.revoked = args?.revoked ?? false;
if (args?.account) {
this.account = args.account;
}
}

/**
* Creates a key with default values (where applicable).
* It doesn't pre-set the values that should be provided.
*/
static createDefault(): AccountKeyEntity {
const key = new AccountKeyEntity();
// https://developers.flow.com/tooling/flow-cli/accounts/create-accounts#public-key-signature-algorithm
key.signAlgo = SignatureAlgorithm.ECDSA_P256;
// Which has algorithm is actually used here by default?
// Flow CLI doesn't support the option to specify it as an argument,
// nor does it return this info when generating the key.
key.hashAlgo = HashAlgorithm.SHA3_256;
key.weight = defaultKeyWeight;
key.sequenceNumber = 0;
key.revoked = false;
return key;
return new AccountKeyEntity({
// https://developers.flow.com/tooling/flow-cli/accounts/create-accounts#public-key-signature-algorithm
signAlgo: SignatureAlgorithm.ECDSA_P256,
// Which has algorithm is actually used here by default?
// Flow CLI doesn't support the option to specify it as an argument,
// nor does it return this info when generating the key.
hashAlgo: HashAlgorithm.SHA3_256,
weight: defaultKeyWeight,
sequenceNumber: 0,
revoked: false,
account: undefined,
accountAddress: "",
blockId: "",
index: 0,
privateKey: "",
publicKey: "",
});
}

toProto(): AccountKey {
Expand Down
30 changes: 22 additions & 8 deletions backend/src/accounts/entities/storage-item.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
import { AccountEntity } from "./account.entity";
import { AccountStorageDomain, AccountStorageItem } from "@flowser/shared";
import { PollingEntity } from "../../core/entities/polling.entity";
import {
FlowAccountStorage,
FlowAccountStorageDomain,
FlowStorageIdentifier,
} from "../../flow/services/storage.service";
import { ensurePrefixedAddress } from "../../utils";
import { PollingEntityInitArguments } from "../../utils/type-utils";

type AccountStorageItemEntityInitArgs = Omit<
PollingEntityInitArguments<AccountStorageItemEntity>,
"_id" | "id"
>;

@Entity({ name: "storage" })
export class AccountStorageItemEntity extends PollingEntity {
Expand All @@ -26,10 +26,24 @@ export class AccountStorageItemEntity extends PollingEntity {
accountAddress: string;

@Column("simple-json")
data: unknown;
data: any;

@ManyToOne(() => AccountEntity, (account) => account.storage)
account: AccountEntity;
account?: AccountEntity;

// Entities are also automatically initialized by TypeORM.
// In those cases no constructor arguments are provided.
constructor(args: AccountStorageItemEntityInitArgs | undefined) {
super();
this.pathIdentifier = args?.pathIdentifier ?? "";
this.pathDomain =
args?.pathDomain ?? AccountStorageDomain.STORAGE_DOMAIN_UNKNOWN;
this.accountAddress = args?.accountAddress ?? "";
this.data = args?.data ?? {};
if (args?.account) {
this.account = args.account;
}
}

get id() {
return `${this.accountAddress}/${this.getLowerCasedPathDomain()}/${
Expand Down
5 changes: 4 additions & 1 deletion backend/src/accounts/services/contracts.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { MoreThan, Repository } from "typeorm";
import { AccountContractEntity } from "../entities/contract.entity";
import { computeEntitiesDiff, processEntitiesDiff } from "../../utils";
import {
computeEntitiesDiff,
processEntitiesDiff,
} from "../../utils/common-utils";
import { removeByBlockIds } from "../../blocks/entities/block-context.entity";

@Injectable()
Expand Down
5 changes: 4 additions & 1 deletion backend/src/accounts/services/keys.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { AccountKeyEntity } from "../entities/key.entity";
import { MoreThan, Repository } from "typeorm";
import { computeEntitiesDiff, processEntitiesDiff } from "../../utils";
import {
computeEntitiesDiff,
processEntitiesDiff,
} from "../../utils/common-utils";
import { removeByBlockIds } from "../../blocks/entities/block-context.entity";

@Injectable()
Expand Down
2 changes: 1 addition & 1 deletion backend/src/accounts/services/storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
computeEntitiesDiff,
ensurePrefixedAddress,
processEntitiesDiff,
} from "../../utils";
} from "../../utils/common-utils";

@Injectable()
export class AccountStorageService {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/blocks/blocks.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class BlocksController {

@Post("/polling")
@UseInterceptors(new PollingResponseInterceptor(GetPollingBlocksResponse))
async findAllNew(@Body() data) {
async findAllNew(@Body() data: unknown) {
const request = GetPollingBlocksRequest.fromJSON(data);
const blocks = await this.blocksService.findAllNewerThanTimestamp(
new Date(request.timestamp)
Expand Down
Loading