Commit 5026308f authored by Karl Floersch's avatar Karl Floersch Committed by GitHub

Add verify transaction (#22)

* WIP queue element

* Add verify transaction for queue txs

* Add verifyTransaction(..) except witness
parent 17dfcf15
......@@ -15,6 +15,7 @@ import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalT
/* Contract Imports */
import { OVM_BaseChain } from "./OVM_BaseChain.sol";
import { OVM_ExecutionManager } from "../execution/OVM_ExecutionManager.sol";
/* Logging Imports */
import { console } from "@nomiclabs/buidler/console.sol";
......@@ -46,6 +47,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
uint256 internal forceInclusionPeriodSeconds;
uint256 internal lastOVMTimestamp;
address internal sequencer;
address internal decompressionPrecompileAddress;
using Lib_TimeboundRingBuffer for TimeboundRingBuffer;
TimeboundRingBuffer internal queue;
......@@ -67,6 +69,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
Lib_AddressResolver(_libAddressManager)
{
sequencer = resolve("OVM_Sequencer");
decompressionPrecompileAddress = resolve("OVM_DecompressionPrecompileAddress");
forceInclusionPeriodSeconds = _forceInclusionPeriodSeconds;
queue.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
......@@ -346,6 +349,30 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
);
}
/**
* Verifies whether a transaction is included in the chain.
* @return _isVerified True if the transaction exists in the CTC, false if not.
*/
function verifyTransaction(
Lib_OVMCodec.Transaction memory _transaction,
TransactionChainElement memory _txChainElement,
Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
)
public
view
returns (
bool _isVerified
)
{
if (_txChainElement.isSequenced == true) {
return _verifySequencerTransaction(_transaction, _txChainElement, _inclusionProof);
} else {
return _verifyQueueTransaction(_transaction, _txChainElement.queueIndex, _inclusionProof);
}
}
/**********************
* Internal Functions *
......@@ -562,4 +589,118 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
)
);
}
/************************************************
* Internal Functions: Transaction Verification *
************************************************/
/**
* Verifies a sequencer transaction, returning true if it was indeed included in the CTC
* @param _transaction The transaction we are verifying inclusion of.
* @param _txChainElement The chain element that the transaction is claimed to be a part of.
* @param _inclusionProof An inclusion proof into the CTC at a particular index.
* @return _isVerified True if the transaction was included in the specified location, else false.
*/
function _verifySequencerTransaction(
Lib_OVMCodec.Transaction memory _transaction,
TransactionChainElement memory _txChainElement,
Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
)
internal
view
returns (
bool _isVerified
)
{
OVM_ExecutionManager em = OVM_ExecutionManager(resolve("OVM_ExecutionManager"));
uint256 gasLimit = em.getMaxTransactionGasLimit();
address decompressionAddress = decompressionPrecompileAddress;
bytes32 leafHash = _getSequencerChainElementLeafHash(_txChainElement);
// TODO: Verify inclusion proof points to the computed leafHash
// require(verifyElement(_inclusionProof, leafHash), "Invalid sequencer tx: inclusion proof invalid.")
require(_transaction.blockNumber == _txChainElement.blockNumber, "Invalid sequencer tx: blockNumber.");
require(_transaction.timestamp == _txChainElement.timestamp, "Invalid sequencer tx: timestamp.");
require(_transaction.entrypoint == decompressionAddress, "Invalid sequencer tx: entrypoint.");
require(_transaction.gasLimit == gasLimit, "Invalid sequencer tx: gasLimit.");
require(keccak256(_transaction.data) == keccak256(_txChainElement.txData), "Invalid sequencer tx: data.");
require(_transaction.l1TxOrigin == address(0), "Invalid sequencer tx: l1TxOrigin.");
require(_transaction.l1QueueOrigin == Lib_OVMCodec.QueueOrigin.SEQUENCER_QUEUE, "Invalid sequencer tx: l1QueueOrigin.");
return true;
}
/**
* Computes the sequencerChainElement leaf hash
* @param _txChainElement The chain element which is hashed to calculate the leaf.
* @return _leafHash The hash of the chain element (using the special chain element leaf encoding).
*/
function _getSequencerChainElementLeafHash(
TransactionChainElement memory _txChainElement
)
internal
view
returns(
bytes32 _leafHash
)
{
bytes memory txData = _txChainElement.txData;
uint256 txDataLength = _txChainElement.txData.length;
bytes memory _chainElement = new bytes(BYTES_TILL_TX_DATA + txDataLength);
bytes32 leafHash;
uint _timestamp = _txChainElement.timestamp;
uint _blockNumber = _txChainElement.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)
// Copy the txData into memory using identity precompile
pop(staticcall(gas(), 0x04, add(txData, 0x20), txDataLength, add(chainElementStart, 66), txDataLength))
// Calculate the hash
leafHash := keccak256(chainElementStart, add(BYTES_TILL_TX_DATA, txDataLength))
}
return _leafHash;
}
/**
* Verifies a queue transaction, returning true if it was indeed included in the CTC
* @param _transaction The transaction we are verifying inclusion of.
* @param _queueIndex The queueIndex of the queued transaction.
* @param _inclusionProof An inclusion proof into the CTC at a particular index (should point to queue tx).
* @return _isVerified True if the transaction was included in the specified location, else false.
*/
function _verifyQueueTransaction(
Lib_OVMCodec.Transaction memory _transaction,
uint256 _queueIndex,
Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
)
internal
view
returns (
bool _isVerified
)
{
bytes32 leafHash = _getQueueLeafHash(_inclusionProof.index);
// TODO: Verify inclusion proof
// require(verifyElement(_inclusionProof, leafHash), "Invalid queue tx: inclusion proof invalid.")
Lib_OVMCodec.QueueElement memory ele = getQueueElement(_queueIndex);
bytes memory txEncoded = abi.encode(
_transaction.l1TxOrigin,
_transaction.entrypoint,
_transaction.gasLimit,
_transaction.data
);
bytes32 transactionHash = keccak256(txEncoded);
require(ele.queueRoot == transactionHash, "Invalid queue tx: transaction hash does not match.");
require(ele.timestamp == _transaction.timestamp, "Invalid queue tx: timestamp does not match.");
require(ele.blockNumber == _transaction.blockNumber, "Invalid queue tx: blockNumber does not match.");
return true;
}
}
......@@ -802,6 +802,22 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
);
}
/***************************************
* Public Functions: Execution Context *
***************************************/
function getMaxTransactionGasLimit()
external
view
override
returns (
uint256 _maxTransactionGasLimit
)
{
return gasMeterConfig.maxTransactionGasLimit;
}
/********************************************
* Internal Functions: Contract Interaction *
......@@ -1674,10 +1690,10 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
internal
{
transactionContext.ovmTIMESTAMP = _transaction.timestamp;
transactionContext.ovmNUMBER = _transaction.number;
transactionContext.ovmNUMBER = _transaction.blockNumber;
transactionContext.ovmTXGASLIMIT = _transaction.gasLimit;
transactionContext.ovmL1QUEUEORIGIN = _transaction.l1QueueOrigin;
transactionContext.ovmL1TXORIGIN = _transaction.l1Txorigin;
transactionContext.ovmL1TXORIGIN = _transaction.l1TxOrigin;
transactionContext.ovmGASLIMIT = gasMeterConfig.maxGasPerQueuePerEpoch;
messageRecord.nuisanceGasLeft = _getNuisanceGasLimit(_transaction.gasLimit);
......
......@@ -155,4 +155,10 @@ interface iOVM_ExecutionManager {
**************************************/
function safeCREATE(address _address, bytes memory _bytecode) external;
/***************************************
* Public Functions: Execution Context *
***************************************/
function getMaxTransactionGasLimit() external view returns (uint _maxTransactionGasLimit);
}
......@@ -72,9 +72,9 @@ library Lib_OVMCodec {
struct Transaction {
uint256 timestamp;
uint256 number;
uint256 blockNumber;
QueueOrigin l1QueueOrigin;
address l1Txorigin;
address l1TxOrigin;
address entrypoint;
uint256 gasLimit;
bytes data;
......@@ -138,9 +138,9 @@ library Lib_OVMCodec {
{
return abi.encodePacked(
_transaction.timestamp,
_transaction.number,
_transaction.blockNumber,
_transaction.l1QueueOrigin,
_transaction.l1Txorigin,
_transaction.l1TxOrigin,
_transaction.entrypoint,
_transaction.gasLimit,
_transaction.data
......
......@@ -18,6 +18,7 @@ import {
getEthTime,
getNextBlockNumber,
increaseEthTime,
ZERO_ADDRESS
} from '../../../helpers'
import { defaultAbiCoder, keccak256 } from 'ethers/lib/utils'
......@@ -29,6 +30,8 @@ interface sequencerBatchContext {
}
const ELEMENT_TEST_SIZES = [1, 2, 4, 8, 16]
const DECOMPRESSION_ADDRESS = '0x4200000000000000000000000000000000000008'
const MAX_GAS_LIMIT = 8_000_000
const getTransactionHash = (
sender: string,
......@@ -128,7 +131,7 @@ const encodeBatchContext = (context: BatchContext): string => {
)
}
describe('OVM_CanonicalTransactionChain', () => {
describe.only('OVM_CanonicalTransactionChain', () => {
let signer: Signer
let sequencer: Signer
before(async () => {
......@@ -136,12 +139,31 @@ describe('OVM_CanonicalTransactionChain', () => {
})
let AddressManager: Contract
let Mock__OVM_ExecutionManager: MockContract
before(async () => {
AddressManager = await makeAddressManager()
await AddressManager.setAddress(
'OVM_Sequencer',
await sequencer.getAddress()
)
await AddressManager.setAddress(
'OVM_DecompressionPrecompileAddress',
DECOMPRESSION_ADDRESS
)
Mock__OVM_ExecutionManager = smockit(
await ethers.getContractFactory('OVM_ExecutionManager')
)
await setProxyTarget(
AddressManager,
'OVM_ExecutionManager',
Mock__OVM_ExecutionManager
)
Mock__OVM_ExecutionManager.smocked.getMaxTransactionGasLimit.will.return.with(
MAX_GAS_LIMIT
)
})
let Factory__OVM_CanonicalTransactionChain: ContractFactory
......@@ -445,6 +467,86 @@ describe('OVM_CanonicalTransactionChain', () => {
})
})
describe('verifyTransaction', () => {
it('should successfully verify against a valid queue transaction', async () => {
const entrypoint = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
const timestamp = (await getEthTime(ethers.provider) + 100)
await setEthTime(ethers.provider, timestamp)
await OVM_CanonicalTransactionChain.enqueue(
entrypoint,
gasLimit,
data
)
const blockNumber = await ethers.provider.getBlockNumber()
await increaseEthTime(
ethers.provider,
FORCE_INCLUSION_PERIOD_SECONDS * 2
)
await OVM_CanonicalTransactionChain.appendQueueBatch(1)
expect(await OVM_CanonicalTransactionChain.verifyTransaction(
{
timestamp,
blockNumber,
l1QueueOrigin: 1,
l1TxOrigin: await OVM_CanonicalTransactionChain.signer.getAddress(),
entrypoint,
gasLimit,
data
},
{
isSequenced: false,
queueIndex: 0,
timestamp: 0,
blockNumber: 0,
txData: '0x00'
},
{
index: 0,
siblings: []
}
)).to.equal(true)
})
it('should successfully verify against a valid sequencer transaction', async () => {
const entrypoint = DECOMPRESSION_ADDRESS
const gasLimit = MAX_GAS_LIMIT
const data = '0x' + '12'.repeat(1234)
const timestamp = await getEthTime(ethers.provider) - 10
const blockNumber = await ethers.provider.getBlockNumber() - 1
// TODO: await OVM_CanonicalTransactionChain.appendQueueBatch(1)
expect(await OVM_CanonicalTransactionChain.verifyTransaction(
{
timestamp,
blockNumber,
l1QueueOrigin: 0,
l1TxOrigin: ZERO_ADDRESS,
entrypoint,
gasLimit,
data
},
{
isSequenced: true,
queueIndex: 0,
timestamp,
blockNumber,
txData: data
},
{
index: 0,
siblings: []
}
)).to.equal(true)
})
})
describe('appendSequencerBatch', () => {
beforeEach(() => {
OVM_CanonicalTransactionChain = OVM_CanonicalTransactionChain.connect(
......
......@@ -5,7 +5,7 @@ export const DUMMY_OVM_TRANSACTIONS = [
timestamp: 0,
number: 0,
l1QueueOrigin: 0,
l1Txorigin: ZERO_ADDRESS,
l1TxOrigin: ZERO_ADDRESS,
entrypoint: ZERO_ADDRESS,
gasLimit: 0,
data: NULL_BYTES32,
......
......@@ -265,7 +265,7 @@ export class ExecutionManagerTestRunner {
timestamp: step.functionParams.timestamp,
number: 0,
l1QueueOrigin: step.functionParams.queueOrigin,
l1Txorigin: step.functionParams.origin,
l1TxOrigin: step.functionParams.origin,
entrypoint: step.functionParams.entrypoint,
gasLimit: step.functionParams.gasLimit,
data: calldata,
......
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