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

Polish fixes 6.20. #167

Merged
merged 6 commits into from
Jun 20, 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
61 changes: 11 additions & 50 deletions backend/src/accounts/entities/storage-item.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import { ensurePrefixedAddress } from "../../utils";

@Entity({ name: "storage" })
export class AccountStorageItemEntity extends PollingEntity {
@PrimaryColumn()
id: string;
// This is a deprecated column, but removing it would cause a database migration error.
// To completely remove this we would need to write a manual migration script.
@PrimaryColumn({ name: "id" })
_id: string = "";

@Column()
@PrimaryColumn()
pathIdentifier: string;

@PrimaryColumn()
Expand All @@ -29,6 +31,12 @@ export class AccountStorageItemEntity extends PollingEntity {
@ManyToOne(() => AccountEntity, (account) => account.storage)
account: AccountEntity;

get id() {
return `${this.accountAddress}/${this.getLowerCasedPathDomain()}/${
this.pathIdentifier
}`;
}

toProto(): AccountStorageItem {
return {
id: this.id,
Expand All @@ -40,53 +48,6 @@ export class AccountStorageItemEntity extends PollingEntity {
};
}

static create(
flowStorageDomain: FlowAccountStorageDomain,
flowStorageIdentifier: FlowStorageIdentifier,
flowAccountStorage: FlowAccountStorage
) {
const storageData =
flowAccountStorage[flowStorageDomain][flowStorageIdentifier];

const storageItem = new AccountStorageItemEntity();
storageItem.pathIdentifier = flowStorageIdentifier;
storageItem.pathDomain = this.convertFlowStorageDomain(flowStorageDomain);

// TODO(milestone-x): For now we will just show plain (unparsed) storage data
// But in the future we will want to parse it so that we can extract info
// This will be possible after storage API implements proper deserialization of storage data
if (typeof storageData !== "object") {
// In case the data is a simple value (string, number, boolean,...)
// we need to store it in object form (e.g. under "value" key).
// Otherwise it won't get properly encoded/decoded by protocol buffers.
storageItem.data = { value: storageData };
} else {
storageItem.data = storageData;
}
storageItem.accountAddress = ensurePrefixedAddress(
flowAccountStorage.Address
);
storageItem.id = `${
storageItem.accountAddress
}/${storageItem.getLowerCasedPathDomain()}/${storageItem.pathIdentifier}`;
return storageItem;
}

private static convertFlowStorageDomain(
flowStorageDomain: FlowAccountStorageDomain
): AccountStorageDomain {
switch (flowStorageDomain) {
case "Public":
return AccountStorageDomain.STORAGE_DOMAIN_PUBLIC;
case "Private":
return AccountStorageDomain.STORAGE_DOMAIN_PRIVATE;
case "Storage":
return AccountStorageDomain.STORAGE_DOMAIN_STORAGE;
default:
return AccountStorageDomain.STORAGE_DOMAIN_UNKNOWN;
}
}

getLowerCasedPathDomain() {
switch (this.pathDomain) {
case AccountStorageDomain.STORAGE_DOMAIN_PUBLIC:
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 @@ -40,7 +40,7 @@ export class AccountStorageService {
) {
const oldStorageItems = await this.findStorageByAccount(address);
const entitiesDiff = computeEntitiesDiff<AccountStorageItemEntity>({
primaryKey: "id",
primaryKey: ["pathIdentifier", "pathDomain", "accountAddress"],
newEntities: newStorageItems,
oldEntities: oldStorageItems,
deepCompare: true,
Expand Down
4 changes: 2 additions & 2 deletions backend/src/core/services/cache-removal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class CacheRemovalService {
this.blocksService.removeAll(),
]);
} catch (e) {
this.logger.error("Failed to remove other data", e);
this.logger.error("Failed to remove cached data", e);
throw e;
}
}
Expand All @@ -62,7 +62,7 @@ export class CacheRemovalService {
this.blocksService.removeByBlockIds(blockIds),
]);
} catch (e) {
this.logger.error("Failed to remove other data", e);
this.logger.error("Failed to remove cached data", e);
throw e;
}
}
Expand Down
76 changes: 56 additions & 20 deletions backend/src/data-processing/processor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
FlowEvent,
FlowGatewayService,
FlowKey,
FlowSignableObject,
FlowTransaction,
FlowTransactionStatus,
} from "../flow/services/gateway.service";
Expand All @@ -31,6 +32,7 @@ import {
FlowCoreEventType,
ManagedProcessState,
ServiceStatus,
SignableObject,
TransactionStatus,
} from "@flowser/shared";
import {
Expand Down Expand Up @@ -305,9 +307,9 @@ export class ProcessorService implements ProjectContextLifecycle {
const transactionPromises = Promise.all(
data.transactions.map((transaction) =>
this.processNewTransaction({
block: data.block,
transaction,
transactionStatus: transaction.status,
flowBlock: data.block,
flowTransaction: transaction,
flowTransactionStatus: transaction.status,
}).catch((e) =>
this.logger.error(`transaction save error: ${e.message}`, e.stack)
)
Expand Down Expand Up @@ -451,7 +453,7 @@ export class ProcessorService implements ProjectContextLifecycle {
case FlowCoreEventType.ACCOUNT_CONTRACT_ADDED:
case FlowCoreEventType.ACCOUNT_CONTRACT_UPDATED:
case FlowCoreEventType.ACCOUNT_CONTRACT_REMOVED:
// TODO: Use event.data.address & event.data.contract to determine updated/created/removed contract
// TODO: Stop re-fetching all contracts and instead use event.data.contract to get the removed/updated/added contract
return this.updateStoredAccountContracts({ address, flowBlock });
// For now keep listening for monotonic and non-monotonic addresses,
// although I think only non-monotonic ones are used for contract deployment.
Expand Down Expand Up @@ -480,22 +482,13 @@ export class ProcessorService implements ProjectContextLifecycle {
}

private async processNewTransaction(options: {
block: FlowBlock;
transaction: FlowTransaction;
transactionStatus: FlowTransactionStatus;
flowBlock: FlowBlock;
flowTransaction: FlowTransaction;
flowTransactionStatus: FlowTransactionStatus;
}) {
// TODO: Should we also mark all tx.authorizers as updated?
const payerAddress = ensurePrefixedAddress(options.transaction.payer);
return Promise.all([
this.transactionService.createOrUpdate(
TransactionEntity.create(
options.block,
options.transaction,
options.transactionStatus
)
),
this.accountService.markUpdated(payerAddress),
]);
this.transactionService.createOrUpdate(
this.createTransactionEntity(options)
);
}

private async storeNewAccountWithContractsAndKeys(options: {
Expand Down Expand Up @@ -750,7 +743,50 @@ export class ProcessorService implements ProjectContextLifecycle {
contract.accountAddress = ensurePrefixedAddress(flowAccount.address);
contract.name = name;
contract.code = code;
// contract.updateId();
return contract;
}

private createTransactionEntity(options: {
flowBlock: FlowBlock;
flowTransaction: FlowTransaction;
flowTransactionStatus: FlowTransactionStatus;
}): TransactionEntity {
const { flowBlock, flowTransaction, flowTransactionStatus } = options;
const transaction = new TransactionEntity();
transaction.id = flowTransaction.id;
transaction.script = flowTransaction.script;
transaction.payerAddress = ensurePrefixedAddress(flowTransaction.payer);
transaction.blockId = flowBlock.id;
transaction.referenceBlockId = flowTransaction.referenceBlockId;
transaction.gasLimit = flowTransaction.gasLimit;
transaction.authorizers = flowTransaction.authorizers.map((address) =>
ensurePrefixedAddress(address)
);
transaction.args = flowTransaction.args;
transaction.proposalKey = {
...flowTransaction.proposalKey,
address: ensurePrefixedAddress(flowTransaction.proposalKey.address),
};
transaction.envelopeSignatures = this.deserializeSignableObjects(
flowTransaction.envelopeSignatures
);
transaction.payloadSignatures = this.deserializeSignableObjects(
flowTransaction.payloadSignatures
);
transaction.status = TransactionStatus.fromJSON({
errorMessage: flowTransactionStatus.errorMessage,
grcpStatus: flowTransactionStatus.statusCode,
executionStatus: flowTransactionStatus.status,
});
return transaction;
}

private deserializeSignableObjects(signableObjects: FlowSignableObject[]) {
return signableObjects.map((signable) =>
SignableObject.fromJSON({
...signable,
address: ensurePrefixedAddress(signable.address),
})
);
}
}
73 changes: 67 additions & 6 deletions backend/src/flow/services/storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Injectable, HttpException } from "@nestjs/common";
import axios from "axios";
import { FlowAccount } from "./gateway.service";
import { AccountStorageItemEntity } from "../../accounts/entities/storage-item.entity";
import { ensurePrefixedAddress } from "../../utils";
import { AccountStorageDomain } from "@flowser/shared";

/**
* For more info on the account storage model and API, see:
Expand Down Expand Up @@ -47,14 +49,27 @@ export class FlowAccountStorageService {
);
const storageIdentifiers = Object.keys(flowAccountStorage.Storage ?? {});

const privateItems = privateStorageIdentifiers.map((identifier) =>
AccountStorageItemEntity.create("Private", identifier, flowAccountStorage)
const privateItems = privateStorageIdentifiers.map(
(flowStorageIdentifier) =>
this.createStorageEntity({
flowStorageDomain: "Private",
flowStorageIdentifier,
flowAccountStorage,
})
);
const publicItems = publicStorageIdentifiers.map((identifier) =>
AccountStorageItemEntity.create("Public", identifier, flowAccountStorage)
const publicItems = publicStorageIdentifiers.map((flowStorageIdentifier) =>
this.createStorageEntity({
flowStorageDomain: "Public",
flowStorageIdentifier,
flowAccountStorage,
})
);
const storageItems = storageIdentifiers.map((identifier) =>
AccountStorageItemEntity.create("Storage", identifier, flowAccountStorage)
const storageItems = storageIdentifiers.map((flowStorageIdentifier) =>
this.createStorageEntity({
flowStorageDomain: "Storage",
flowStorageIdentifier,
flowAccountStorage,
})
);

return { privateItems, publicItems, storageItems };
Expand All @@ -72,4 +87,50 @@ export class FlowAccountStorageService {

return response.data as FlowAccountStorage;
}

private createStorageEntity(options: {
flowStorageDomain: FlowAccountStorageDomain;
flowStorageIdentifier: FlowStorageIdentifier;
flowAccountStorage: FlowAccountStorage;
}) {
const { flowAccountStorage, flowStorageIdentifier, flowStorageDomain } =
options;
const storageData =
flowAccountStorage[flowStorageDomain][flowStorageIdentifier];

const storageItem = new AccountStorageItemEntity();
storageItem.pathIdentifier = flowStorageIdentifier;
storageItem.pathDomain = this.flowStorageDomainToEnum(flowStorageDomain);

// TODO(milestone-x): For now we will just show plain (unparsed) storage data
// But in the future we will want to parse it so that we can extract info
// This will be possible after storage API implements proper deserialization of storage data
if (typeof storageData !== "object") {
// In case the data is a simple value (string, number, boolean,...)
// we need to store it in object form (e.g. under "value" key).
// Otherwise, it won't get properly encoded/decoded by protocol buffers.
storageItem.data = { value: storageData };
} else {
storageItem.data = storageData;
}
storageItem.accountAddress = ensurePrefixedAddress(
flowAccountStorage.Address
);
return storageItem;
}

private flowStorageDomainToEnum(
flowStorageDomain: FlowAccountStorageDomain
): AccountStorageDomain {
switch (flowStorageDomain) {
case "Public":
return AccountStorageDomain.STORAGE_DOMAIN_PUBLIC;
case "Private":
return AccountStorageDomain.STORAGE_DOMAIN_PRIVATE;
case "Storage":
return AccountStorageDomain.STORAGE_DOMAIN_STORAGE;
default:
return AccountStorageDomain.STORAGE_DOMAIN_UNKNOWN;
}
}
}
43 changes: 0 additions & 43 deletions backend/src/transactions/transaction.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,47 +88,4 @@ export class TransactionEntity
updatedAt: this.updatedAt.toISOString(),
};
}

static create(
flowBlock: FlowBlock,
flowTransaction: FlowTransaction,
flowTransactionStatus: FlowTransactionStatus
): TransactionEntity {
const transaction = new TransactionEntity();
transaction.id = flowTransaction.id;
transaction.script = flowTransaction.script;
transaction.payerAddress = ensurePrefixedAddress(flowTransaction.payer);
transaction.blockId = flowBlock.id;
transaction.referenceBlockId = flowTransaction.referenceBlockId;
transaction.gasLimit = flowTransaction.gasLimit;
transaction.authorizers = flowTransaction.authorizers.map((address) =>
ensurePrefixedAddress(address)
);
transaction.args = flowTransaction.args;
transaction.proposalKey = {
...flowTransaction.proposalKey,
address: ensurePrefixedAddress(flowTransaction.proposalKey.address),
};
transaction.envelopeSignatures = deserializeSignableObjects(
flowTransaction.envelopeSignatures
);
transaction.payloadSignatures = deserializeSignableObjects(
flowTransaction.payloadSignatures
);
transaction.status = TransactionStatus.fromJSON({
errorMessage: flowTransactionStatus.errorMessage,
grcpStatus: flowTransactionStatus.statusCode,
executionStatus: flowTransactionStatus.status,
});
return transaction;
}
}

function deserializeSignableObjects(signableObjects: FlowSignableObject[]) {
return signableObjects.map((signable) =>
SignableObject.fromJSON({
...signable,
address: ensurePrefixedAddress(signable.address),
})
);
}
8 changes: 6 additions & 2 deletions frontend/src/hooks/use-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,12 @@ export function useGetPollingContracts(): TimeoutPollingHook<AccountContract> {
}

export function useGetContract(contractId: string) {
return useQuery<GetSingleContractResponse>(`/contract/${contractId}`, () =>
contractsService.getSingle(contractId)
return useQuery<GetSingleContractResponse>(
`/contract/${contractId}`,
() => contractsService.getSingle(contractId),
{
refetchInterval: 2000,
}
);
}

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/pages/contracts/details/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const Details: FunctionComponent = () => {
</NavLink>
),
},
{
label: "Updated date",
value: TextUtils.longDate(contract.updatedAt),
},
{
label: "Created date",
value: TextUtils.longDate(contract.createdAt),
Expand Down
Loading