Skip to content

Commit

Permalink
blockchain: allow optimistic block insertion in blockchain
Browse files Browse the repository at this point in the history
  • Loading branch information
g11tech committed Aug 13, 2024
1 parent 837a83f commit c7f6c87
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 63 deletions.
140 changes: 79 additions & 61 deletions packages/blockchain/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ export class Blockchain implements BlockchainInterface {
* heads/hashes are overwritten.
* @param block - The block to be added to the blockchain
*/
async putBlock(block: Block) {
await this._putBlockOrHeader(block)
async putBlock(block: Block, optimistic: boolean = false) {
await this._putBlockOrHeader(block, optimistic)
}

/**
Expand Down Expand Up @@ -344,7 +344,7 @@ export class Blockchain implements BlockchainInterface {
* header using the iterator method.
* @hidden
*/
private async _putBlockOrHeader(item: Block | BlockHeader) {
private async _putBlockOrHeader(item: Block | BlockHeader, optimistic: boolean = false) {
await this.runWithLock<void>(async () => {
// Save the current sane state incase _putBlockOrHeader midway with some
// dirty changes in head trackers
Expand All @@ -367,15 +367,10 @@ export class Blockchain implements BlockchainInterface {
throw new Error(
'Cannot put a different genesis block than current blockchain genesis: create a new Blockchain',
)
// genesis block is not optimistic
optimistic = false
}

const { header } = block
const blockHash = header.hash()
const blockNumber = header.number
let td = header.difficulty
const currentTd = { header: BIGINT_0, block: BIGINT_0 }
let dbOps: DBOp[] = []

if (block.common.chainId() !== this.common.chainId()) {
throw new Error(
`Chain mismatch while trying to put block or header. Chain ID of block: ${block.common.chainId}, chain ID of blockchain : ${this.common.chainId}`,
Expand All @@ -391,67 +386,90 @@ export class Blockchain implements BlockchainInterface {
await this.consensus!.validateConsensus(block)
}

// set total difficulty in the current context scope
if (this._headHeaderHash) {
currentTd.header = await this.getTotalDifficulty(this._headHeaderHash)
}
if (this._headBlockHash) {
currentTd.block = await this.getTotalDifficulty(this._headBlockHash)
}

// calculate the total difficulty of the new block
const parentTd = await this.getParentTD(header)
if (!block.isGenesis()) {
td += parentTd
}

// save total difficulty to the database
dbOps = dbOps.concat(DBSetTD(td, blockNumber, blockHash))
const { header } = block
const blockHash = header.hash()
const blockNumber = header.number

// save header/block to the database, but save the input not our wrapper block
dbOps = dbOps.concat(DBSetBlockOrHeader(item))
let td = header.difficulty
try {
const parentTd = await this.getParentTD(header)
if (!block.isGenesis()) {
td += parentTd
}
// since its linked its no more optimistic
optimistic = false
} catch (e) {
// opimistic insertion does care about td
if (!optimistic) {
throw e
}
}

let commonAncestor: undefined | BlockHeader
let ancestorHeaders: undefined | BlockHeader[]
// if total difficulty is higher than current, add it to canonical chain
if (
block.isGenesis() ||
td > currentTd.header ||
block.common.consensusType() === ConsensusType.ProofOfStake
) {
const foundCommon = await this.findCommonAncestor(header)
commonAncestor = foundCommon.commonAncestor
ancestorHeaders = foundCommon.ancestorHeaders
let dbOps: DBOp[] = []
if (optimistic) {
dbOps = dbOps.concat(DBSetBlockOrHeader(item))
dbOps.push(DBSetHashToNumber(blockHash, blockNumber))
await this.dbManager.batch(dbOps)
} else {
const currentTd = { header: BIGINT_0, block: BIGINT_0 }

this._headHeaderHash = blockHash
if (item instanceof Block) {
this._headBlockHash = blockHash
// set total difficulty in the current context scope
if (this._headHeaderHash) {
currentTd.header = await this.getTotalDifficulty(this._headHeaderHash)
}
if (this._hardforkByHeadBlockNumber) {
await this.checkAndTransitionHardForkByNumber(blockNumber, header.timestamp)
if (this._headBlockHash) {
currentTd.block = await this.getTotalDifficulty(this._headBlockHash)
}

// delete higher number assignments and overwrite stale canonical chain
await this._deleteCanonicalChainReferences(blockNumber + BIGINT_1, blockHash, dbOps)
// from the current header block, check the blockchain in reverse (i.e.
// traverse `parentHash`) until `numberToHash` matches the current
// number/hash in the canonical chain also: overwrite any heads if these
// heads are stale in `_heads` and `_headBlockHash`
await this._rebuildCanonical(header, dbOps)
} else {
// the TD is lower than the current highest TD so we will add the block
// to the DB, but will not mark it as the canonical chain.
if (td > currentTd.block && item instanceof Block) {
this._headBlockHash = blockHash
// save total difficulty to the database
dbOps = dbOps.concat(DBSetTD(td, blockNumber, blockHash))

// save header/block to the database, but save the input not our wrapper block
dbOps = dbOps.concat(DBSetBlockOrHeader(item))

let commonAncestor: undefined | BlockHeader
let ancestorHeaders: undefined | BlockHeader[]
// if total difficulty is higher than current, add it to canonical chain
if (
block.isGenesis() ||
td > currentTd.header ||
block.common.consensusType() === ConsensusType.ProofOfStake
) {
const foundCommon = await this.findCommonAncestor(header)
commonAncestor = foundCommon.commonAncestor
ancestorHeaders = foundCommon.ancestorHeaders

this._headHeaderHash = blockHash
if (item instanceof Block) {
this._headBlockHash = blockHash
}
if (this._hardforkByHeadBlockNumber) {
await this.checkAndTransitionHardForkByNumber(blockNumber, header.timestamp)
}

// delete higher number assignments and overwrite stale canonical chain
await this._deleteCanonicalChainReferences(blockNumber + BIGINT_1, blockHash, dbOps)
// from the current header block, check the blockchain in reverse (i.e.
// traverse `parentHash`) until `numberToHash` matches the current
// number/hash in the canonical chain also: overwrite any heads if these
// heads are stale in `_heads` and `_headBlockHash`
await this._rebuildCanonical(header, dbOps)
} else {
// the TD is lower than the current highest TD so we will add the block
// to the DB, but will not mark it as the canonical chain.
if (td > currentTd.block && item instanceof Block) {
this._headBlockHash = blockHash
}
// save hash to number lookup info even if rebuild not needed
dbOps.push(DBSetHashToNumber(blockHash, blockNumber))
}
// save hash to number lookup info even if rebuild not needed
dbOps.push(DBSetHashToNumber(blockHash, blockNumber))
}

const ops = dbOps.concat(this._saveHeadOps())
await this.dbManager.batch(ops)
const ops = dbOps.concat(this._saveHeadOps())
await this.dbManager.batch(ops)

await this.consensus?.newBlock(block, commonAncestor, ancestorHeaders)
await this.consensus?.newBlock(block, commonAncestor, ancestorHeaders)
}
} catch (e) {
// restore head to the previouly sane state
this._heads = oldHeads
Expand Down
4 changes: 2 additions & 2 deletions packages/blockchain/src/db/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const NUM_SUFFIX = utf8ToBytes('n')
/**
* blockHashPrefix + hash -> number
*/
const BLOCK_HASH_PEFIX = utf8ToBytes('H')
const BLOCK_HASH_PREFIX = utf8ToBytes('H')

/**
* bodyPrefix + number + hash -> block body
Expand All @@ -55,7 +55,7 @@ const bodyKey = (n: bigint, hash: Uint8Array) => concatBytes(BODY_PREFIX, bytesB

const numberToHashKey = (n: bigint) => concatBytes(HEADER_PREFIX, bytesBE8(n), NUM_SUFFIX)

const hashToNumberKey = (hash: Uint8Array) => concatBytes(BLOCK_HASH_PEFIX, hash)
const hashToNumberKey = (hash: Uint8Array) => concatBytes(BLOCK_HASH_PREFIX, hash)

/**
* @hidden
Expand Down

0 comments on commit c7f6c87

Please sign in to comment.