Skip to content

SQLite timeouts after upgrade from prisma 2 to prisma 4 #17029

@philipp1992

Description

@philipp1992

Bug description

      "type": "PrismaClientUnknownRequestError",
      "message": "\nInvalid `this.prisma.onchainTransaction.update()` invocation in\n/app/modules/server-node/src/services/store.ts:410:42\n\n  407 }\n  408 \n  409 async saveTransactionReceipt(onchainTransactionId: string, receipt: TransactionReceipt): Promise<void> {\n→ 410   await this.prisma.onchainTransaction.update(\nError occurred during query execution:\nConnectorError(ConnectorError { user_facing_error: None, kind: ConnectionError(Timed out during query execution.) })",
      "stack":
          Error:
          Invalid `this.prisma.onchainTransaction.update()` invocation in
          /app/modules/server-node/src/services/store.ts:410:42

Getting a lot of these after ive upgraded from 2 to 4. Seems to be caused by sqlite database being locked.
How can i specify a timeout or retry?
Didnt happen on v2

How to reproduce

Expected behavior

No response

Prisma information

generator client {
  provider      = "prisma-client-js"
  binaryTargets = ["native"]
  output        = "../src/generated/db-client"
  previewFeatures = ["referentialActions"]
}

datasource db {
  provider = "sqlite"
  url      = env("VECTOR_DATABASE_URL")
}

model Balance {
  participant      String
  assetId          String
  to               String
  amount           String
  processedDeposit String
  defundNonce      String
  Channel          Channel @relation(fields: [channelAddress], references: [channelAddress])
  channelAddress   String

  @@id([participant, channelAddress, assetId])
  @@map(name: "balance")
}

model ChannelDispute {
  channelAddress   String @id // TODO: better ID?
  channelStateHash String
  nonce            String
  merkleRoot       String
  consensusExpiry  String
  defundExpiry     String

  channel Channel @relation("OffchainChannel", fields: [channelAddress], references: [channelAddress])
}

model TransferDispute {
  transferId            String  @id // TODO: better ID?
  transferStateHash     String
  transferDisputeExpiry String
  isDefunded            Boolean

  transfer Transfer @relation("OffchainTransfer", fields: [transferId], references: [transferId])
}

model Channel {
  channelAddress          String    @id
  publicIdentifierA       String
  publicIdentifierB       String
  participantA            String
  participantB            String
  assetIds                String
  timeout                 String
  nonce                   Int
  merkleRoot              String
  balances                Balance[]
  channelFactoryAddress   String
  transferRegistryAddress String
  chainId                 String
  latestUpdate            Update?

  activeTransfers Transfer[]

  createdAt DateTime @default(now())

  OnchainTransaction OnchainTransaction[]

  // Disputes
  // NOTE: disputes can have a different offchain and onchain state.
  // The `*Reference` field links to the offchain transfer representation
  // while the `*Record` field links to the onchain representation that
  // has the transferId suffix `-dispute`
  dispute ChannelDispute? @relation("OffchainChannel")

  @@unique([publicIdentifierA, publicIdentifierB, chainId])
  @@unique([participantA, participantB, chainId])
  @@map(name: "channel")
}

model Update {
  // COMMON PARAMS
  channelAddress   String? @unique
  channel          Channel? @relation(fields: [channelAddress], references: [channelAddress])
  channelAddressId String // required for ID so that relation can be removed
  createdAt        DateTime @default(now())

  fromIdentifier String
  toIdentifier   String
  type           String
  nonce          Int

  // balance
  amountA String
  amountB String
  toA     String
  toB     String

  assetId    String
  signatureA String?
  signatureB String?

  // DETAILS
  // deposit details
  totalDepositsAlice String?
  totalDepositsBob   String?

  // create details
  transferAmountA      String?
  transferAmountB      String?
  transferToA          String?
  transferToB          String?
  transferId           String?
  transferDefinition   String?
  transferTimeout      String?
  transferInitialState String? // JSON string
  transferEncodings    String?
  merkleProofData      String? // proofs.join(",")
  meta                 String?
  responder            String?

  // resolve details
  transferResolver String?
  merkleRoot       String?

  // setup inferred from channel params

  createdTransfer  Transfer? @relation("CreatedTransfer")
  resolvedTransfer Transfer? @relation("ResolvedTransfer")

  @@id([channelAddressId, nonce])
  @@map(name: "update")
}

model Transfer {
  transferId   String   @id
  routingId    String
  channelNonce Int
  createdAt    DateTime @default(now())

  createUpdate  Update? @relation(name: "CreatedTransfer", fields: [createUpdateChannelAddressId, createUpdateNonce], references: [channelAddressId, nonce])
  resolveUpdate Update? @relation(name: "ResolvedTransfer", fields: [resolveUpdateChannelAddressId, resolveUpdateNonce], references: [channelAddressId, nonce])

  // transactions for withdrawals
  // has onchainTransactionId always if withdrawal
  // has OnchainTransaction relation IFF we submitted (and have tx record)
  onchainTransactionId String? @unique
  onchainTransaction   OnchainTransaction? @relation(fields: [onchainTransactionId], references: [id])
  transactionHash      String? // in case we didn't submit

  // balance
  amountA String
  amountB String
  toA     String
  toB     String

  initialStateHash String

  channel          Channel? @relation(fields: [channelAddress], references: [channelAddress])
  channelAddress   String?
  channelAddressId String // required for ID so that relation can be removed

  // created will always exist
  createUpdateChannelAddressId String?
  createUpdateNonce            Int?

  // resolved will not always exist
  resolveUpdateChannelAddressId String?
  resolveUpdateNonce            Int?

  dispute TransferDispute? @relation("OffchainTransfer")
  @@unique([createUpdateChannelAddressId, createUpdateNonce])
  @@unique([resolveUpdateChannelAddressId, resolveUpdateNonce])
  @@unique([routingId, channelAddressId])
  @@map(name: "transfer")
}

model EventSubscription {
  id               String @id @default(uuid())
  event            String
  publicIdentifier String
  url              String

  @@unique([publicIdentifier, event])
  @@map(name: "event-subscription")
}

model OnchainTransaction {
  id        String    @id @default(uuid()) 
  createdAt DateTime  @default(now())
  transfer  Transfer?

  // Response fields : should be saved upon receiving first response
  chainId String?
  nonce   Int?
  to      String?
  from    String?
  data    String?
  value   String?

  // Channel fields
  status         String  @default("submitted") // no enums
  reason         String // no enums
  error          String?
  channelAddress String
  channel        Channel @relation(fields: [channelAddress], references: [channelAddress])

  // Tx fields
  attempts OnchainTransactionAttempt[] // 1-n
  receipt  OnchainTransactionReceipt?

  @@map(name: "onchain_transaction")
}

model OnchainTransactionAttempt {
  createdAt            DateTime            @default(now())
  onchainTransaction   OnchainTransaction? @relation(fields: [onchainTransactionId], references: [id])
  onchainTransactionId String @unique

  // Response fields
  transactionHash String @id
  gasLimit        String
  gasPrice        String

  @@map(name: "onchain_transaction_attempt")
}

model OnchainTransactionReceipt {
  createdAt            DateTime           @default(now())
  onchainTransaction   OnchainTransaction @relation(fields: [onchainTransactionId], references: [id]) // 1-1
  onchainTransactionId String @unique

  // Response fields
  transactionHash String @id

  // Receipt fields
  timestamp         String?
  raw               String?
  blockHash         String?
  blockNumber       Int?
  contractAddress   String?
  transactionIndex  Int?
  root              String?
  gasUsed           String?
  logsBloom         String?
  logs              String?
  cumulativeGasUsed String?
  byzantium         Boolean?
  status            Int?

  @@map(name: "onchain_transaction_receipt")
}

model Configuration {
  id Int @id

  @@map(name: "configuration")
}

model NodeIndex {
  index            Int    @id
  publicIdentifier String @unique

  @@map(name: "node_index")
}
 await this.prisma.onchainTransaction.upsert({
      where: { id: onchainTransactionId },
      create: {
        id: onchainTransactionId,
        status: StoredTransactionStatus.submitted,
        chainId: response.chainId.toString(),
        nonce: response.nonce,
        to: response.to ?? "",
        from: response.from,
        data: response.data,
        value: (response.value ?? BigNumber.from(0)).toString(),
        reason,
        receipt: undefined,
        attempts: {
          create: {
            gasLimit: (response.gasLimit ?? BigNumber.from(0)).toString(),
            gasPrice: (response.gasPrice ?? BigNumber.from(0)).toString(),
            transactionHash: response.hash,
          },
        },
        channel: {
          connect: {
            channelAddress,
          },
        },
      },
      update: {
        status: StoredTransactionStatus.submitted,
        reason,
        attempts: {
          create: {
            gasLimit: (response.gasLimit ?? BigNumber.from(0)).toString(),
            gasPrice: (response.gasPrice ?? BigNumber.from(0)).toString(),
            transactionHash: response.hash,
          },
        },
        channel: {
          connect: {
            channelAddress,
          },
        },
      },
      include: { channel: true },
    });

Environment & setup

node 14.17
linux docker alpine node:14.17

Prisma Version

prisma 4.8.0
sqlite3 5.0.2

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions