Commit e7196c18 authored by Karl Floersch's avatar Karl Floersch Committed by GitHub

Merge pull request #16 from ethereum-optimism/feat/correct_encoding

Add correct encoding
parents 9530e0b2 5a33924c
......@@ -31,6 +31,12 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
uint256 constant public MIN_ROLLUP_TX_GAS = 20000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 10000;
uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 10;
// Encoding Constants
uint256 constant internal BATCH_CONTEXT_SIZE = 16;
uint256 constant internal BATCH_CONTEXT_LENGTH_POS = 12;
uint256 constant internal BATCH_CONTEXT_START_POS = 15;
uint256 constant internal TX_DATA_HEADER_SIZE = 3;
uint256 constant internal BYTES_TILL_TX_DATA = 66;
/*************
......@@ -64,7 +70,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
forceInclusionPeriodSeconds = _forceInclusionPeriodSeconds;
queue.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
batches.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
batches.init(2, 50, 0); // TODO: Update once we have arbitrary condition
}
......@@ -221,15 +227,27 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
/**
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function appendSequencerBatch(
bytes[] memory _transactions,
BatchContext[] memory _contexts,
uint256 _shouldStartAtBatch,
uint _totalElementsToAppend
function appendSequencerBatch( // USES CUSTOM ENCODING FOR EFFICIENCY PURPOSES
// uint40 _shouldStartAtBatch,
// uint24 _totalElementsToAppend,
// BatchContext[] _contexts,
// bytes[] _transactionDataFields
)
override
public
{
uint40 _shouldStartAtBatch;
uint24 _totalElementsToAppend;
uint24 _contextsLength;
assembly {
// First 5 bytes after MethodId is _shouldStartAtBatch
_shouldStartAtBatch := shr(216, calldataload(4))
// Next 3 bytes is _totalElementsToAppend
_totalElementsToAppend := shr(232, calldataload(9))
// And the last 3 bytes is the _contextsLength
_contextsLength := shr(232, calldataload(12))
}
require(
_shouldStartAtBatch == getTotalBatches(),
"Actual batch start index does not match expected start index."
......@@ -241,7 +259,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
);
require(
_contexts.length > 0,
_contextsLength > 0,
"Must provide at least one batch context."
);
......@@ -253,27 +271,51 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
bytes32[] memory leaves = new bytes32[](_totalElementsToAppend);
uint32 transactionIndex = 0;
uint32 numSequencerTransactionsProcessed = 0;
uint32 nextSequencerTransactionPosition = uint32(BATCH_CONTEXT_START_POS + BATCH_CONTEXT_SIZE * _contextsLength);
uint theCalldataSize;
assembly {
theCalldataSize := calldatasize()
}
require(theCalldataSize >= nextSequencerTransactionPosition, "Not enough BatchContexts provided.");
(, uint32 nextQueueIndex) = _getLatestBatchContext();
for (uint32 i = 0; i < _contexts.length; i++) {
BatchContext memory context = _contexts[i];
for (uint32 i = 0; i < _contextsLength; i++) {
BatchContext memory context = _getBatchContext(i);
_validateBatchContext(context, nextQueueIndex);
for (uint32 i = 0; i < context.numSequencedTransactions; i++) {
leaves[transactionIndex] = _hashTransactionChainElement(
TransactionChainElement({
isSequenced: true,
queueIndex: 0,
timestamp: context.timestamp,
blockNumber: context.blockNumber,
txData: _transactions[numSequencerTransactionsProcessed]
})
);
for (uint32 j = 0; j < context.numSequencedTransactions; j++) {
uint256 txDataLength;
assembly {
// 3 byte txDataLength
txDataLength := shr(232, calldataload(nextSequencerTransactionPosition))
}
bytes memory _chainElement = new bytes(BYTES_TILL_TX_DATA + txDataLength);
bytes32 leafHash;
uint _timestamp = context.timestamp;
uint _blockNumber = context.blockNumber;
assembly {
let chainElementStart := add(_chainElement, 0x20)
mstore8(chainElementStart, 1)
mstore8(add(chainElementStart, 1), 0)
mstore(add(chainElementStart, 2), _timestamp)
mstore(add(chainElementStart, 34), _blockNumber)
// Store the rest of the transaction
calldatacopy(add(chainElementStart, BYTES_TILL_TX_DATA), add(nextSequencerTransactionPosition, 3), txDataLength)
// Calculate the hash
leafHash := keccak256(chainElementStart, add(BYTES_TILL_TX_DATA, txDataLength))
}
leaves[transactionIndex] = leafHash;
nextSequencerTransactionPosition += uint32(TX_DATA_HEADER_SIZE + txDataLength);
numSequencerTransactionsProcessed++;
transactionIndex++;
}
for (uint32 i = 0; i < context.numSubsequentQueueTransactions; i++) {
for (uint32 j = 0; j < context.numSubsequentQueueTransactions; j++) {
leaves[transactionIndex] = _getQueueLeafHash(nextQueueIndex);
nextQueueIndex++;
transactionIndex++;
......@@ -281,7 +323,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
}
require(
numSequencerTransactionsProcessed == _transactions.length,
theCalldataSize == nextSequencerTransactionPosition,
"Not all sequencer transactions were processed."
);
require(
......@@ -307,6 +349,45 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
* Internal Functions *
**********************/
/**
* Returns the BatchContext located at a particular index.
* @param _index The index of the BatchContext
* @return _context The BatchContext at the specified index.
*/
function _getBatchContext(
uint _index
)
internal
view
returns (
BatchContext memory _context
)
{
// Batch contexts always start at byte 12:
// 4[method_id] + 5[_shouldStartAtBatch] + 3[_totalElementsToAppend] + 3[numBatchContexts]
uint contextPosition = 15 + _index * BATCH_CONTEXT_SIZE;
uint numSequencedTransactions;
uint numSubsequentQueueTransactions;
uint ctxTimestamp;
uint ctxBlockNumber;
assembly {
// 3 byte numSequencedTransactions
numSequencedTransactions := shr(232, calldataload(contextPosition))
// 3 byte numSubsequentQueueTransactions
numSubsequentQueueTransactions := shr(232, calldataload(add(contextPosition, 3)))
// 5 byte timestamp
ctxTimestamp := shr(216, calldataload(add(contextPosition, 6)))
// 5 byte blockNumber
ctxBlockNumber := shr(216, calldataload(add(contextPosition, 11)))
}
return BatchContext({
numSequencedTransactions: numSequencedTransactions,
numSubsequentQueueTransactions: numSubsequentQueueTransactions,
timestamp: ctxTimestamp,
blockNumber: ctxBlockNumber
});
}
/**
* Parses the batch context from the extra data.
* @return _totalElements Total number of elements submitted.
......
......@@ -97,15 +97,15 @@ interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
/**
* Allows the sequencer to append a batch of transactions.
* @param _transactions Array of raw transaction data.
* @param _contexts Array of batch contexts.
* @param _shouldStartAtBatch Specific batch we expect to start appending to.
* @param _totalElementsToAppend Total number of batch elements we expect to append.
* param _shouldStartAtBatch Specific batch we expect to start appending to.
* param _totalElementsToAppend Total number of batch elements we expect to append.
* param _contexts Array of batch contexts.
* param _transactionDataFields Array of raw transaction data.
*/
function appendSequencerBatch(
bytes[] memory _transactions,
BatchContext[] memory _contexts,
uint256 _shouldStartAtBatch,
uint _totalElementsToAppend
function appendSequencerBatch( // USES CUSTOM ENCODING FOR EFFICIENCY PURPOSES
// uint40 _shouldStartAtBatch,
// uint24 _totalElementsToAppend,
// BatchContext[] _contexts,
// bytes[] _transactionDataFields
) external;
}
......@@ -3,6 +3,7 @@ import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import { TransactionResponse } from "@ethersproject/abstract-provider";
import { smockit, MockContract } from '@eth-optimism/smock'
import _ from 'lodash'
......@@ -17,8 +18,6 @@ import {
getEthTime,
getNextBlockNumber,
increaseEthTime,
// NON_NULL_BYTES32,
// ZERO_ADDRESS,
} from '../../../helpers'
import { defaultAbiCoder, keccak256 } from 'ethers/lib/utils'
......@@ -31,33 +30,6 @@ interface sequencerBatchContext {
const ELEMENT_TEST_SIZES = [1, 2, 4, 8, 16]
const getQueueElementHash = (queueIndex: number): string => {
return getChainElementHash(false, queueIndex, 0, 0, '0x')
}
const getSequencerElementHash = (
timestamp: number,
blockNumber: number,
txData: string
): string => {
return getChainElementHash(true, 0, timestamp, blockNumber, txData)
}
const getChainElementHash = (
isSequenced: boolean,
queueIndex: number,
timestamp: number,
blockNumber: number,
txData: string
): string => {
return keccak256(
defaultAbiCoder.encode(
['bool', 'uint256', 'uint256', 'uint256', 'bytes'],
[isSequenced, queueIndex, timestamp, blockNumber, txData]
)
)
}
const getTransactionHash = (
sender: string,
target: string,
......@@ -79,14 +51,61 @@ const encodeQueueTransaction = (
)
}
const encodeTimestampAndBlockNumber = (
timestamp: number,
interface BatchContext {
numSequencedTransactions: number
numSubsequentQueueTransactions: number
timestamp: number
blockNumber: number
}
interface AppendSequencerBatchParams {
shouldStartAtBatch: number, // 5 bytes -- starts at batch
totalElementsToAppend: number, // 3 bytes -- total_elements_to_append
contexts: BatchContext[], // total_elements[fixed_size[]]
transactions: string[] // total_size_bytes[],total_size_bytes[]
}
const encodeAppendSequencerBatch = (
b: AppendSequencerBatchParams
): string => {
let encoding: string
const encodedShouldStartAtBatch = remove0x(BigNumber.from(b.shouldStartAtBatch).toHexString()).padStart(10, '0')
const encodedTotalElementsToAppend = remove0x(BigNumber.from(b.totalElementsToAppend).toHexString()).padStart(6, '0')
const encodedContextsHeader = remove0x(BigNumber.from(b.contexts.length).toHexString()).padStart(6, '0')
const encodedContexts = encodedContextsHeader + b.contexts.reduce((acc, cur) => acc + encodeBatchContext(cur), '')
const encodedTransactionData = b.transactions.reduce((acc, cur) => {
if (cur.length % 2 !== 0) throw new Error('Unexpected uneven hex string value!')
const encodedTxDataHeader = remove0x(BigNumber.from(remove0x(cur).length/2).toHexString()).padStart(6, '0')
return acc + encodedTxDataHeader + remove0x(cur)
}, '')
return (
'0x' +
remove0x(BigNumber.from(blockNumber).toHexString()).padStart(54, '0') +
remove0x(BigNumber.from(timestamp).toHexString()).padStart(10, '0')
encodedShouldStartAtBatch +
encodedTotalElementsToAppend +
encodedContexts +
encodedTransactionData
)
}
const appendSequencerBatch = async (
OVM_CanonicalTransactionChain: Contract,
batch: AppendSequencerBatchParams
): Promise<TransactionResponse> => {
const methodId = keccak256(Buffer.from('appendSequencerBatch()')).slice(2,10)
const calldata = encodeAppendSequencerBatch(batch)
return OVM_CanonicalTransactionChain.signer.sendTransaction({
to: OVM_CanonicalTransactionChain.address,
data:'0x' + methodId + calldata,
})
}
const encodeBatchContext = (context: BatchContext): string => {
return (
remove0x(BigNumber.from(context.numSequencedTransactions).toHexString()).padStart(6, '0') +
remove0x(BigNumber.from(context.numSubsequentQueueTransactions).toHexString()).padStart(6, '0') +
remove0x(BigNumber.from(context.timestamp).toHexString()).padStart(10, '0') +
remove0x(BigNumber.from(context.blockNumber).toHexString()).padStart(10, '0')
)
}
......@@ -428,11 +447,66 @@ describe('OVM_CanonicalTransactionChain', () => {
)
})
it.skip('should allow for a lower bound per-tx gas usage of <400 gas [GAS BENCHMARK]', async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber = (await getNextBlockNumber(ethers.provider)) + 100
// do two batch appends for no reason
await appendSequencerBatch(OVM_CanonicalTransactionChain, {
shouldStartAtBatch: 0,
totalElementsToAppend: 1,
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
transactions: ['0x1234'],
})
await appendSequencerBatch(OVM_CanonicalTransactionChain, {
shouldStartAtBatch: 1,
totalElementsToAppend: 1,
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
transactions: ['0x1234'],
})
console.log('\n~~~~ BEGINNGING TRASACTION IN QUESTION ~~~~')
const transactions = []
const numTxs = 200
for (let i = 0; i < numTxs; i++) {
transactions.push('0x' + '1080111111111111111111111111111111111111111111'.repeat(20))
}
const res = await appendSequencerBatch(OVM_CanonicalTransactionChain, {
shouldStartAtBatch: 2,
totalElementsToAppend: numTxs,
contexts: [
{
numSequencedTransactions: numTxs,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
transactions,
})
const receipt = await res.wait()
console.log("Benchmark complete. Gas used:", receipt.gasUsed)
}).timeout(100000000)
it('should revert if expected start does not match current total batches', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
......@@ -440,19 +514,19 @@ describe('OVM_CanonicalTransactionChain', () => {
blockNumber: 0,
},
],
1234,
1
)
).to.be.revertedWith(
shouldStartAtBatch: 1234,
totalElementsToAppend: 1
}
)).to.be.revertedWith(
'Actual batch start index does not match expected start index.'
)
})
it('should revert if not called by the sequencer', async () => {
it('should revert if not all sequencer transactions are processed', async () => {
await expect(
OVM_CanonicalTransactionChain.connect(signer).appendSequencerBatch(
['0x1234'],
[
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234', '0x1234'],
contexts: [
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
......@@ -460,34 +534,57 @@ describe('OVM_CanonicalTransactionChain', () => {
blockNumber: 0,
},
],
0,
1
shouldStartAtBatch: 0,
totalElementsToAppend: 1
}
)).to.be.revertedWith(
'Not all sequencer transactions were processed.'
)
).to.be.revertedWith('Function can only be called by the Sequencer.')
})
it('should revert if not called by the sequencer', async () => {
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain.connect(signer), {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
shouldStartAtBatch: 0,
totalElementsToAppend: 1
}
)).to.be.revertedWith('Function can only be called by the Sequencer.')
})
it('should revert if no contexts are provided', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(['0x1234'], [], 0, 1)
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [],
shouldStartAtBatch: 0,
totalElementsToAppend: 1
})
).to.be.revertedWith('Must provide at least one batch context.')
})
it('should revert if total elements to append is zero', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
{
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
0,
0
)
).to.be.revertedWith('Must append at least one element.')
}],
shouldStartAtBatch: 0,
totalElementsToAppend: 0
}
)).to.be.revertedWith('Must append at least one element.')
})
for (const size of ELEMENT_TEST_SIZES) {
......@@ -506,9 +603,10 @@ describe('OVM_CanonicalTransactionChain', () => {
)
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
......@@ -516,9 +614,9 @@ describe('OVM_CanonicalTransactionChain', () => {
blockNumber: 0,
},
],
0,
1
)
shouldStartAtBatch: 0,
totalElementsToAppend: 1
})
).to.be.revertedWith(
'Older queue batches must be processed before a new sequencer batch.'
)
......@@ -528,9 +626,9 @@ describe('OVM_CanonicalTransactionChain', () => {
const timestamp = (await getEthTime(ethers.provider)) + 1000
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
......@@ -538,8 +636,9 @@ describe('OVM_CanonicalTransactionChain', () => {
blockNumber: 0,
},
],
0,
1
shouldStartAtBatch: 0,
totalElementsToAppend: 1
}
)
).to.be.revertedWith('Sequencer transactions timestamp too high.')
})
......@@ -549,9 +648,9 @@ describe('OVM_CanonicalTransactionChain', () => {
const blockNumber = (await getNextBlockNumber(ethers.provider)) + 100
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: ['0x1234'],
contexts: [
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
......@@ -559,8 +658,9 @@ describe('OVM_CanonicalTransactionChain', () => {
blockNumber: blockNumber,
},
],
0,
1
shouldStartAtBatch: 0,
totalElementsToAppend: 1
}
)
).to.be.revertedWith('Sequencer transactions blockNumber too high.')
})
......@@ -590,12 +690,12 @@ describe('OVM_CanonicalTransactionChain', () => {
it('should append the given number of transactions', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions,
contexts,
0,
size
)
shouldStartAtBatch: 0,
totalElementsToAppend: size
})
)
.to.emit(OVM_CanonicalTransactionChain, 'SequencerBatchAppended')
.withArgs(0, 0)
......@@ -638,11 +738,12 @@ describe('OVM_CanonicalTransactionChain', () => {
it('should append the batch', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions,
contexts,
0,
size * 2
shouldStartAtBatch: 0,
totalElementsToAppend: size * 2
}
)
)
.to.emit(OVM_CanonicalTransactionChain, 'SequencerBatchAppended')
......@@ -678,12 +779,12 @@ describe('OVM_CanonicalTransactionChain', () => {
it('should append the batch', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions,
contexts,
0,
size + spacing
)
shouldStartAtBatch: 0,
totalElementsToAppend: size + spacing
})
)
.to.emit(OVM_CanonicalTransactionChain, 'SequencerBatchAppended')
.withArgs(0, spacing)
......@@ -718,9 +819,14 @@ describe('OVM_CanonicalTransactionChain', () => {
return '0x' + '12' + '34'.repeat(idx)
})
await OVM_CanonicalTransactionChain.connect(
await appendSequencerBatch(OVM_CanonicalTransactionChain.connect(
sequencer
).appendSequencerBatch(transactions, contexts, 0, size)
), {
transactions,
contexts,
shouldStartAtBatch: 0,
totalElementsToAppend: size
})
})
it(`should return ${size}`, async () => {
......
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