Commit 538a7895 authored by Maurelian's avatar Maurelian Committed by Kelvin Fichter

feat(contracts): reduce cost of appendSequencerBatch

parent e20deca0
......@@ -465,19 +465,6 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
for (uint32 i = 0; i < numContexts; i++) {
BatchContext memory nextContext = _getBatchContext(i);
if (i == 0) {
// Execute a special check for the first batch.
_validateFirstBatchContext(nextContext);
}
// Execute this check on every single batch, including the first one.
_validateNextBatchContext(
curContext,
nextContext,
nextQueueIndex,
queueRef
);
// Now we can update our current context.
curContext = nextContext;
......@@ -517,13 +504,6 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
}
}
_validateFinalBatchContext(
curContext,
nextQueueIndex,
queueLength,
queueRef
);
require(
msg.data.length == nextTransactionPtr,
"Not all sequencer transactions were processed."
......@@ -945,156 +925,6 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
batchesRef.push(batchHeaderHash, latestBatchContext);
}
/**
* Checks that the first batch context in a sequencer submission is valid
* @param _firstContext The batch context to validate.
*/
function _validateFirstBatchContext(
BatchContext memory _firstContext
)
internal
view
{
// If there are existing elements, this batch must have the same context
// or a later timestamp and block number.
if (getTotalElements() > 0) {
(,, uint40 lastTimestamp, uint40 lastBlockNumber) = _getBatchExtraData();
require(
_firstContext.blockNumber >= lastBlockNumber,
"Context block number is lower than last submitted."
);
require(
_firstContext.timestamp >= lastTimestamp,
"Context timestamp is lower than last submitted."
);
}
// Sequencer cannot submit contexts which are more than the force inclusion period old.
require(
_firstContext.timestamp + forceInclusionPeriodSeconds >= block.timestamp,
"Context timestamp too far in the past."
);
require(
_firstContext.blockNumber + forceInclusionPeriodBlocks >= block.number,
"Context block number too far in the past."
);
}
/**
* Checks that a given batch context has a time context which is below a given que element
* @param _context The batch context to validate has values lower.
* @param _queueIndex Index of the queue element we are validating came later than the context.
* @param _queueRef The storage container for the queue.
*/
function _validateContextBeforeEnqueue(
BatchContext memory _context,
uint40 _queueIndex,
iOVM_ChainStorageContainer _queueRef
)
internal
view
{
Lib_OVMCodec.QueueElement memory nextQueueElement = _getQueueElement(
_queueIndex,
_queueRef
);
// If the force inclusion period has passed for an enqueued transaction, it MUST be the
// next chain element.
require(
block.timestamp < nextQueueElement.timestamp + forceInclusionPeriodSeconds,
// solhint-disable-next-line max-line-length
"Previously enqueued batches have expired and must be appended before a new sequencer batch."
);
// Just like sequencer transaction times must be increasing relative to each other,
// We also require that they be increasing relative to any interspersed queue elements.
require(
_context.timestamp <= nextQueueElement.timestamp,
"Sequencer transaction timestamp exceeds that of next queue element."
);
require(
_context.blockNumber <= nextQueueElement.blockNumber,
"Sequencer transaction blockNumber exceeds that of next queue element."
);
}
/**
* Checks that a given batch context is valid based on its previous context, and the next queue
* elemtent.
* @param _prevContext The previously validated batch context.
* @param _nextContext The batch context to validate with this call.
* @param _nextQueueIndex Index of the next queue element to process for the _nextContext's
* subsequentQueueElements.
* @param _queueRef The storage container for the queue.
*/
function _validateNextBatchContext(
BatchContext memory _prevContext,
BatchContext memory _nextContext,
uint40 _nextQueueIndex,
iOVM_ChainStorageContainer _queueRef
)
internal
view
{
// All sequencer transactions' times must be greater than or equal to the previous ones.
require(
_nextContext.timestamp >= _prevContext.timestamp,
"Context timestamp values must monotonically increase."
);
require(
_nextContext.blockNumber >= _prevContext.blockNumber,
"Context blockNumber values must monotonically increase."
);
// If there is going to be a queue element pulled in from this context:
if (_nextContext.numSubsequentQueueTransactions > 0) {
_validateContextBeforeEnqueue(
_nextContext,
_nextQueueIndex,
_queueRef
);
}
}
/**
* Checks that the final batch context in a sequencer submission is valid.
* @param _finalContext The batch context to validate.
* @param _queueLength The length of the queue at the start of the batchAppend call.
* @param _nextQueueIndex The next element in the queue that will be pulled into the CTC.
* @param _queueRef The storage container for the queue.
*/
function _validateFinalBatchContext(
BatchContext memory _finalContext,
uint40 _nextQueueIndex,
uint40 _queueLength,
iOVM_ChainStorageContainer _queueRef
)
internal
view
{
// If the queue is not now empty, check the mononoticity of whatever the next batch that
// will come in is.
if (_queueLength - _nextQueueIndex > 0 && _finalContext.numSubsequentQueueTransactions == 0)
{
_validateContextBeforeEnqueue(
_finalContext,
_nextQueueIndex,
_queueRef
);
}
// Batches cannot be added from the future, or subsequent enqueue() contexts would violate
// monotonicity.
require(_finalContext.timestamp <= block.timestamp,
"Context timestamp is from the future.");
require(_finalContext.blockNumber <= block.number,
"Context block number is from the future.");
}
/**
* Hashes a transaction chain element.
......
......@@ -834,7 +834,7 @@ describe('OVM_CanonicalTransactionChain', () => {
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith('Index out of bounds.')
).to.be.revertedWith('Not enough queued transactions to append.')
})
it('reverts when there are insufficient (but nonzero) transactions in the queue', async () => {
......@@ -863,477 +863,6 @@ describe('OVM_CanonicalTransactionChain', () => {
).to.be.revertedWith('Not enough queued transactions to append.')
})
})
describe('when the sequencer attempts to add transactions which are not monotonically increasing', () => {
describe('when the sequencer transactions themselves have out-of-order times', () => {
it('should revert when adding two out-of-order-timestamp sequencer elements', async () => {
const timestamp = await getEthTime(ethers.provider)
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 1
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234', '0x5678'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp: timestamp + 1,
blockNumber,
},
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 2,
})
).to.be.revertedWith(
'Context timestamp values must monotonically increase.'
)
})
it('should revert when adding two out-of-order-blocknumber sequencer elements', async () => {
const timestamp = await getEthTime(ethers.provider)
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 1
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234', '0x5678'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber: blockNumber + 1,
},
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 2,
})
).to.be.revertedWith(
'Context blockNumber values must monotonically increase.'
)
})
})
describe('when the elements are out-of-order with regards to pending queue elements', async () => {
describe('adding a single sequencer transaction with a single pending queue element', () => {
beforeEach(async () => {
// enqueue a single element so that it is pending, but do not yet apply it
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
})
it('should revert if the first context timestamp is > the head queue element timestamp', async () => {
const timestamp = (await getEthTime(ethers.provider)) + 100
const blockNumber =
(await getNextBlockNumber(ethers.provider)) - 1
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith(
'Sequencer transaction timestamp exceeds that of next queue element.'
)
})
it('should revert if the context block number is > the head queue element block number', async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber =
(await getNextBlockNumber(ethers.provider)) + 100
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith(
'Sequencer transaction blockNumber exceeds that of next queue element.'
)
})
})
describe('adding multiple sequencer transactions with multiple pending queue elements', () => {
const numQueuedTransactions = 10
const queueElements = []
const validContexts = []
beforeEach(async () => {
for (let i = 0; i < numQueuedTransactions; i++) {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
queueElements[i] =
await OVM_CanonicalTransactionChain.getQueueElement(i)
// this is a valid context for this TX
validContexts[i] = {
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 1,
timestamp: queueElements[i].timestamp,
blockNumber: queueElements[i].blockNumber,
}
}
})
it('does not revert for valid context', async () => {
await appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: new Array(numQueuedTransactions).fill('0x1234'),
contexts: validContexts,
shouldStartAtElement: 0,
totalElementsToAppend: 2 * numQueuedTransactions,
})
})
it('reverts if wrong timestamp in middle', async () => {
const invalidTimestampContexts = [...validContexts]
// put a bigger timestamp early
invalidTimestampContexts[6].timestamp =
invalidTimestampContexts[8].timestamp
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: new Array(numQueuedTransactions).fill('0x1234'),
contexts: invalidTimestampContexts,
shouldStartAtElement: 0,
totalElementsToAppend: 2 * numQueuedTransactions,
})
).to.be.revertedWith(
'Sequencer transaction timestamp exceeds that of next queue element.'
)
})
it('reverts if wrong block number in the middle', async () => {
const invalidBlockNumberContexts = [...validContexts]
// put a bigger block number early
invalidBlockNumberContexts[6].blockNumber =
invalidBlockNumberContexts[8].blockNumber
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: new Array(numQueuedTransactions).fill('0x1234'),
contexts: invalidBlockNumberContexts,
shouldStartAtElement: 0,
totalElementsToAppend: 2 * numQueuedTransactions,
})
).to.be.revertedWith(
'Sequencer transaction blockNumber exceeds that of next queue element.'
)
})
})
})
})
describe('when the sequencer attempts to add transactions with out-of-bounds times', async () => {
describe('when trying to add elements from the future', () => {
it('reverts on initial timestamp in the future', async () => {
const timestamp = (await getEthTime(ethers.provider)) + 100_000_000
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 1
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith('Context timestamp is from the future.')
})
it('reverts on non-initial timestamp in the future', async () => {
const timestamp = await getEthTime(ethers.provider)
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 1
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234', '0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp: timestamp + 100_000_000,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 2,
})
).to.be.revertedWith('Context timestamp is from the future.')
})
it('reverts on initial blocknumber in the future', async () => {
const timestamp = await getEthTime(ethers.provider)
const blockNumber = (await getNextBlockNumber(ethers.provider)) + 1
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith('Context block number is from the future.')
})
})
})
describe('when trying to add elements which are older than the force inclusion period', async () => {
it('reverts for a timestamp older than the f.i.p. ago', async () => {
const timestamp = await getEthTime(ethers.provider)
await increaseEthTime(
ethers.provider,
FORCE_INCLUSION_PERIOD_SECONDS + 1
)
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 1
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith('Context timestamp too far in the past.')
})
it('reverts for a blockNumber older than the f.i.p. ago', async () => {
const timestamp = await getEthTime(ethers.provider)
for (let i = 0; i < FORCE_INCLUSION_PERIOD_BLOCKS + 1; i++) {
await mineBlock(ethers.provider)
}
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber: 10,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith('Context block number too far in the past.')
})
})
describe('when trying to add elements which are older than already existing CTC elements', () => {
let timestamp
let blockNumber
describe('when the most recent CTC element is a sequencer transaction', () => {
beforeEach(async () => {
timestamp = await getEthTime(ethers.provider)
blockNumber = (await getNextBlockNumber(ethers.provider)) - 1
await appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
})
it('reverts if timestamp is older than previous one', async () => {
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp: timestamp - 1,
blockNumber,
},
],
shouldStartAtElement: 1,
totalElementsToAppend: 1,
})
).to.be.revertedWith(
'Context timestamp is lower than last submitted.'
)
})
it('reverts if block number is older than previous one', async () => {
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber: blockNumber - 1,
},
],
shouldStartAtElement: 1,
totalElementsToAppend: 1,
})
).to.be.revertedWith(
'Context block number is lower than last submitted.'
)
})
})
describe('when the previous transaction is a queue transaction', () => {
beforeEach(async () => {
// enqueue
timestamp = await getEthTime(ethers.provider)
blockNumber = await getNextBlockNumber(ethers.provider)
await OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
await appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 1, // final element will be CTC
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 2,
})
})
it('reverts if timestamp is older than previous one', async () => {
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp: timestamp - 1,
blockNumber,
},
],
shouldStartAtElement: 2,
totalElementsToAppend: 1,
})
).to.be.revertedWith(
'Context timestamp is lower than last submitted.'
)
})
it('reverts if block number is older than previous one', async () => {
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber: blockNumber - 1,
},
],
shouldStartAtElement: 2,
totalElementsToAppend: 1,
})
).to.be.revertedWith(
'Context block number is lower than last submitted.'
)
})
})
})
it('should revert if a queue element has expired and needs to be included', async () => {
// enqueue a tx
await OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
// increase time past force inclusion period
await increaseEthTime(
ethers.provider,
FORCE_INCLUSION_PERIOD_SECONDS * 2
)
const blockNumber = (await ethers.provider.getBlockNumber()) - 1
const validTimestamp = (await getBlockTime(ethers.provider)) + 100
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp: validTimestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith(
'Previously enqueued batches have expired and must be appended before a new sequencer batch.'
)
})
})
for (const size of ELEMENT_TEST_SIZES) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment