Commit d0831b40 authored by Karl Floersch's avatar Karl Floersch

Wip transaction encoding/decoding

parent 9530e0b2
...@@ -31,6 +31,10 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba ...@@ -31,6 +31,10 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
uint256 constant public MIN_ROLLUP_TX_GAS = 20000; uint256 constant public MIN_ROLLUP_TX_GAS = 20000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 10000; uint256 constant public MAX_ROLLUP_TX_SIZE = 10000;
uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 10; uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 10;
// Encoding Constants
uint256 constant public BATCH_CONTEXT_SIZE = 16;
uint256 constant public BATCH_CONTEXT_LENGTH_POS = 12;
uint256 constant public BATCH_CONTEXT_START_POS = 15;
/************* /*************
...@@ -221,91 +225,195 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba ...@@ -221,91 +225,195 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
/** /**
* @inheritdoc iOVM_CanonicalTransactionChain * @inheritdoc iOVM_CanonicalTransactionChain
*/ */
function appendSequencerBatch( function appendSequencerBatch( // USES CUSTOM ENCODING FOR EFFICIENCY PURPOSES
bytes[] memory _transactions, // uint40 _shouldStartAtBatch,
BatchContext[] memory _contexts, // uint24 _totalElementsToAppend,
uint256 _shouldStartAtBatch, // BatchContext[] _contexts,
uint _totalElementsToAppend // bytes[] _transactionDatas
) )
override override
public public
{ {
require( bytes32 firstCalldataWord;
_shouldStartAtBatch == getTotalBatches(), uint40 _shouldStartAtBatch;
"Actual batch start index does not match expected start index." uint24 _totalElementsToAppend;
); assembly {
// First 5 bytes after MethodId is _shouldStartAtBatch
require( _shouldStartAtBatch := shr(216, calldataload(4))
msg.sender == sequencer, // Next 3 bytes is _totalElementsToAppend
"Function can only be called by the Sequencer." _totalElementsToAppend := shr(232, calldataload(9))
);
require(
_contexts.length > 0,
"Must provide at least one batch context."
);
require(
_totalElementsToAppend > 0,
"Must append at least one element."
);
bytes32[] memory leaves = new bytes32[](_totalElementsToAppend);
uint32 transactionIndex = 0;
uint32 numSequencerTransactionsProcessed = 0;
(, uint32 nextQueueIndex) = _getLatestBatchContext();
for (uint32 i = 0; i < _contexts.length; i++) {
BatchContext memory context = _contexts[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]
})
);
numSequencerTransactionsProcessed++;
transactionIndex++;
} }
console.log("_shouldStartAtBatch");
for (uint32 i = 0; i < context.numSubsequentQueueTransactions; i++) { console.log(_shouldStartAtBatch);
leaves[transactionIndex] = _getQueueLeafHash(nextQueueIndex); console.log("_totalElementsToAppend");
nextQueueIndex++; console.log(_totalElementsToAppend);
transactionIndex++;
// numBatchContexts header always starts at byte 12
// 4[method_id] + 5[_shouldStartAtBatch] + 3[_totalElementsToAppend]
uint numBatchContexts;
assembly {
// 3 byte numSubsequentQueueTransactions
numBatchContexts := shr(232, calldataload(BATCH_CONTEXT_LENGTH_POS))
} }
console.log("numBatchContexts");
// Grab a batch context
BatchContext memory batchCtx = _getBatchContext(0);
// Grab transaction data
bytes memory transaction = _getTransactionData(0);
console.logBytes(transaction);
// require(
// _shouldStartAtBatch == getTotalBatches(),
// "Actual batch start index does not match expected start index."
// );
// require(
// msg.sender == sequencer,
// "Function can only be called by the Sequencer."
// );
// require(
// _contexts.length > 0,
// "Must provide at least one batch context."
// );
// require(
// _totalElementsToAppend > 0,
// "Must append at least one element."
// );
// bytes32[] memory leaves = new bytes32[](_totalElementsToAppend);
// uint32 transactionIndex = 0;
// uint32 numSequencerTransactionsProcessed = 0;
// (, uint32 nextQueueIndex) = _getLatestBatchContext();
// for (uint32 i = 0; i < _contexts.length; i++) {
// BatchContext memory context = _contexts[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]
// })
// );
// numSequencerTransactionsProcessed++;
// transactionIndex++;
// }
// for (uint32 i = 0; i < context.numSubsequentQueueTransactions; i++) {
// leaves[transactionIndex] = _getQueueLeafHash(nextQueueIndex);
// nextQueueIndex++;
// transactionIndex++;
// }
// }
// require(
// numSequencerTransactionsProcessed == _transactions.length,
// "Not all sequencer transactions were processed."
// );
// require(
// transactionIndex == _totalElementsToAppend,
// "Actual transaction index does not match expected total elements to append."
// );
// uint256 numQueuedTransactions = _totalElementsToAppend - numSequencerTransactionsProcessed;
// _appendBatch(
// Lib_MerkleRoot.getMerkleRoot(leaves),
// _totalElementsToAppend,
// numQueuedTransactions
// );
// emit SequencerBatchAppended(
// nextQueueIndex - numQueuedTransactions,
// numQueuedTransactions
// );
} }
require(
numSequencerTransactionsProcessed == _transactions.length,
"Not all sequencer transactions were processed."
);
require(
transactionIndex == _totalElementsToAppend,
"Actual transaction index does not match expected total elements to append."
);
uint256 numQueuedTransactions = _totalElementsToAppend - numSequencerTransactionsProcessed; /**********************
_appendBatch( * Internal Functions *
Lib_MerkleRoot.getMerkleRoot(leaves), **********************/
_totalElementsToAppend,
numQueuedTransactions
);
emit SequencerBatchAppended( function _getBatchContext(
nextQueueIndex - numQueuedTransactions, uint _numProcessed
numQueuedTransactions )
); 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 + _numProcessed * 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)))
}
console.log("RETURNING BATCH:");
console.log("numSequencedTransactions");
console.log(numSequencedTransactions);
console.log("numSubsequentQueueTransactions");
console.log(numSubsequentQueueTransactions);
console.log("timestamp");
console.log(ctxTimestamp);
console.log("blockNumber");
console.log(ctxBlockNumber);
return BatchContext({
numSequencedTransactions: numSequencedTransactions,
numSubsequentQueueTransactions: numSubsequentQueueTransactions,
timestamp: ctxTimestamp,
blockNumber: ctxBlockNumber,
index: _numProcessed
});
} }
function _getTransactionData(
uint _numProcessed
)
internal
view
returns (
bytes memory transactionData
)
{
uint numBatchContexts;
assembly {
numBatchContexts := shr(232, calldataload(BATCH_CONTEXT_LENGTH_POS))
}
uint startPosition = BATCH_CONTEXT_START_POS + BATCH_CONTEXT_SIZE * numBatchContexts;
/********************** uint transactionSize;
* Internal Functions * assembly {
**********************/ // 3 byte transactionSize
transactionSize := shr(232, calldataload(startPosition))
// Initialize transactionData at the free memory pointer 0x40
transactionData := mload(0x40)
// Store the length as the first word to conform with `bytes memory`
mstore(transactionData, transactionSize)
// Store the rest of the transaction
calldatacopy(add(transactionData, 32), add(startPosition, 3), transactionSize)
}
return transactionData;
}
/** /**
* Parses the batch context from the extra data. * Parses the batch context from the extra data.
......
...@@ -46,6 +46,7 @@ interface iOVM_CanonicalTransactionChain is iOVM_BaseChain { ...@@ -46,6 +46,7 @@ interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
uint256 numSubsequentQueueTransactions; uint256 numSubsequentQueueTransactions;
uint256 timestamp; uint256 timestamp;
uint256 blockNumber; uint256 blockNumber;
uint256 index;
} }
struct TransactionChainElement { struct TransactionChainElement {
...@@ -95,17 +96,17 @@ interface iOVM_CanonicalTransactionChain is iOVM_BaseChain { ...@@ -95,17 +96,17 @@ interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
uint256 _numQueuedTransactions uint256 _numQueuedTransactions
) external; ) external;
/** // /**
* Allows the sequencer to append a batch of transactions. // * Allows the sequencer to append a batch of transactions.
* @param _transactions Array of raw transaction data. // * @param _transactions Array of raw transaction data.
* @param _contexts Array of batch contexts. // * @param _contexts Array of batch contexts.
* @param _shouldStartAtBatch Specific batch we expect to start appending to. // * @param _shouldStartAtBatch Specific batch we expect to start appending to.
* @param _totalElementsToAppend Total number of batch elements we expect to append. // * @param _totalElementsToAppend Total number of batch elements we expect to append.
*/ // */
function appendSequencerBatch( function appendSequencerBatch(
bytes[] memory _transactions, // uint256 _shouldStartAtBatch,
BatchContext[] memory _contexts, // uint _totalElementsToAppend,
uint256 _shouldStartAtBatch, // BatchContext[] memory _contexts,
uint _totalElementsToAppend // bytes[] memory _transactions
) external; ) external;
} }
...@@ -3,6 +3,8 @@ import { expect } from '../../../setup' ...@@ -3,6 +3,8 @@ import { expect } from '../../../setup'
/* External Imports */ /* External Imports */
import { ethers } from '@nomiclabs/buidler' import { ethers } from '@nomiclabs/buidler'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers' import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import { TransactionResponse } from "@ethersproject/abstract-provider";
import { FunctionFragment } from "@ethersproject/abi";
import { smockit, MockContract } from '@eth-optimism/smock' import { smockit, MockContract } from '@eth-optimism/smock'
import _ from 'lodash' import _ from 'lodash'
...@@ -90,6 +92,69 @@ const encodeTimestampAndBlockNumber = ( ...@@ -90,6 +92,69 @@ const encodeTimestampAndBlockNumber = (
) )
} }
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')
console.log('encodedShouldStartAtBatch', encodedShouldStartAtBatch)
const encodedTotalElementsToAppend = remove0x(BigNumber.from(b.totalElementsToAppend).toHexString()).padStart(6, '0')
console.log('encodedTotalElementsToAppend', encodedTotalElementsToAppend)
const encodedContextsHeader = remove0x(BigNumber.from(b.contexts.length).toHexString()).padStart(6, '0')
const encodedContexts = encodedContextsHeader + b.contexts.reduce((acc, cur) => acc + encodeBatchContext(cur), '')
console.log('encodedContexts', encodedContexts)
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)
}, '')
console.log('encodedTransactionData', encodedTransactionData)
return (
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)
console.log('Generated batch calldata:', calldata)
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')
)
}
describe('OVM_CanonicalTransactionChain', () => { describe('OVM_CanonicalTransactionChain', () => {
let signer: Signer let signer: Signer
let sequencer: Signer let sequencer: Signer
...@@ -428,6 +493,25 @@ describe('OVM_CanonicalTransactionChain', () => { ...@@ -428,6 +493,25 @@ describe('OVM_CanonicalTransactionChain', () => {
) )
}) })
it.only('should revert if expected start does not match current total batches', async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber = (await getNextBlockNumber(ethers.provider)) + 100
const batch: AppendSequencerBatchParams = {
shouldStartAtBatch: 99,
totalElementsToAppend: 88,
contexts: [
{
numSequencedTransactions: 69,
numSubsequentQueueTransactions: 42,
timestamp,
blockNumber,
},
],
transactions: ['0x1234'],
}
const res = await appendSequencerBatch(OVM_CanonicalTransactionChain, batch)
})
it('should revert if expected start does not match current total batches', async () => { it('should revert if expected start does not match current total batches', async () => {
await expect( await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch( OVM_CanonicalTransactionChain.appendSequencerBatch(
......
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