Commit 67dc5654 authored by clabby's avatar clabby Committed by GitHub

feat(ctb): In-order restriction in `addLeavesLPP` (#9134)

* Large preimage proposal - restriction on submitting data in-order

* Challenger updates

* op-challenger: Update tx data decoding for new ABI

---------
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>
parent 2ca70b4e
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -94,8 +94,8 @@ func (c *PreimageOracleContract) InitLargePreimage(uuid *big.Int, partOffset uin ...@@ -94,8 +94,8 @@ func (c *PreimageOracleContract) InitLargePreimage(uuid *big.Int, partOffset uin
return call.ToTxCandidate() return call.ToTxCandidate()
} }
func (c *PreimageOracleContract) AddLeaves(uuid *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error) { func (c *PreimageOracleContract) AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error) {
call := c.contract.Call(methodAddLeavesLPP, uuid, input, commitments, finalize) call := c.contract.Call(methodAddLeavesLPP, uuid, startingBlockIndex, input, commitments, finalize)
return call.ToTxCandidate() return call.ToTxCandidate()
} }
...@@ -124,7 +124,7 @@ func (c *PreimageOracleContract) Squeeze( ...@@ -124,7 +124,7 @@ func (c *PreimageOracleContract) Squeeze(
// abiEncodeStateMatrix encodes the state matrix for the contract ABI // abiEncodeStateMatrix encodes the state matrix for the contract ABI
func abiEncodeStateMatrix(stateMatrix *matrix.StateMatrix) bindings.LibKeccakStateMatrix { func abiEncodeStateMatrix(stateMatrix *matrix.StateMatrix) bindings.LibKeccakStateMatrix {
packedState := stateMatrix.PackState() packedState := stateMatrix.PackState()
var stateSlice = new([25]uint64) stateSlice := new([25]uint64)
// SAFETY: a maximum of 25 * 8 bytes will be read from packedState and written to stateSlice // SAFETY: a maximum of 25 * 8 bytes will be read from packedState and written to stateSlice
for i := 0; i < min(len(packedState), 25*8); i += 8 { for i := 0; i < min(len(packedState), 25*8); i += 8 {
stateSlice[i/8] = new(big.Int).SetBytes(packedState[i : i+8]).Uint64() stateSlice[i/8] = new(big.Int).SetBytes(packedState[i : i+8]).Uint64()
...@@ -205,9 +205,10 @@ func (c *PreimageOracleContract) DecodeInputData(data []byte) (*big.Int, keccakT ...@@ -205,9 +205,10 @@ func (c *PreimageOracleContract) DecodeInputData(data []byte) (*big.Int, keccakT
return nil, keccakTypes.InputData{}, fmt.Errorf("%w: %v", ErrInvalidAddLeavesCall, method) return nil, keccakTypes.InputData{}, fmt.Errorf("%w: %v", ErrInvalidAddLeavesCall, method)
} }
uuid := args.GetBigInt(0) uuid := args.GetBigInt(0)
input := args.GetBytes(1) // Arg 1 is the starting block index which we don't current use
stateCommitments := args.GetBytes32Slice(2) input := args.GetBytes(2)
finalize := args.GetBool(3) stateCommitments := args.GetBytes32Slice(3)
finalize := args.GetBool(4)
commitments := make([]common.Hash, 0, len(stateCommitments)) commitments := make([]common.Hash, 0, len(stateCommitments))
for _, c := range stateCommitments { for _, c := range stateCommitments {
......
...@@ -139,11 +139,13 @@ func (p *LargePreimageUploader) initLargePreimage(ctx context.Context, uuid *big ...@@ -139,11 +139,13 @@ func (p *LargePreimageUploader) initLargePreimage(ctx context.Context, uuid *big
func (p *LargePreimageUploader) addLargePreimageData(ctx context.Context, uuid *big.Int, chunks []keccakTypes.InputData) error { func (p *LargePreimageUploader) addLargePreimageData(ctx context.Context, uuid *big.Int, chunks []keccakTypes.InputData) error {
queue := txmgr.NewQueue[int](ctx, p.txMgr, 10) queue := txmgr.NewQueue[int](ctx, p.txMgr, 10)
receiptChs := make([]chan txmgr.TxReceipt[int], len(chunks)) receiptChs := make([]chan txmgr.TxReceipt[int], len(chunks))
blocksProcessed := int64(0)
for i, chunk := range chunks { for i, chunk := range chunks {
tx, err := p.contract.AddLeaves(uuid, chunk.Input, chunk.Commitments, chunk.Finalize) tx, err := p.contract.AddLeaves(uuid, big.NewInt(blocksProcessed), chunk.Input, chunk.Commitments, chunk.Finalize)
if err != nil { if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err) return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
} }
blocksProcessed += int64(len(chunk.Input) / keccakTypes.BlockSize)
receiptChs[i] = make(chan txmgr.TxReceipt[int], 1) receiptChs[i] = make(chan txmgr.TxReceipt[int], 1)
queue.Send(i, tx, receiptChs[i]) queue.Send(i, tx, receiptChs[i])
} }
......
...@@ -193,7 +193,8 @@ func (s *mockPreimageOracleContract) InitLargePreimage(_ *big.Int, _ uint32, _ u ...@@ -193,7 +193,8 @@ func (s *mockPreimageOracleContract) InitLargePreimage(_ *big.Int, _ uint32, _ u
} }
return txmgr.TxCandidate{}, nil return txmgr.TxCandidate{}, nil
} }
func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, input []byte, _ []common.Hash, _ bool) (txmgr.TxCandidate, error) {
func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, _ *big.Int, input []byte, _ []common.Hash, _ bool) (txmgr.TxCandidate, error) {
s.addCalls++ s.addCalls++
s.addData = append(s.addData, input...) s.addData = append(s.addData, input...)
if s.addFails { if s.addFails {
...@@ -201,6 +202,7 @@ func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, input []byte, _ []com ...@@ -201,6 +202,7 @@ func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, input []byte, _ []com
} }
return txmgr.TxCandidate{}, nil return txmgr.TxCandidate{}, nil
} }
func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ *matrix.StateMatrix, _ keccakTypes.Leaf, _ contracts.MerkleProof, _ keccakTypes.Leaf, _ contracts.MerkleProof) (txmgr.TxCandidate, error) { func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ *matrix.StateMatrix, _ keccakTypes.Leaf, _ contracts.MerkleProof, _ keccakTypes.Leaf, _ contracts.MerkleProof) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, nil return txmgr.TxCandidate{}, nil
} }
......
...@@ -25,7 +25,7 @@ type PreimageUploader interface { ...@@ -25,7 +25,7 @@ type PreimageUploader interface {
// PreimageOracleContract is the interface for interacting with the PreimageOracle contract. // PreimageOracleContract is the interface for interacting with the PreimageOracle contract.
type PreimageOracleContract interface { type PreimageOracleContract interface {
InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error) InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error)
AddLeaves(uuid *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error) AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error)
Squeeze(claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState keccakTypes.Leaf, preStateProof contracts.MerkleProof, postState keccakTypes.Leaf, postStateProof contracts.MerkleProof) (txmgr.TxCandidate, error) Squeeze(claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState keccakTypes.Leaf, preStateProof contracts.MerkleProof, postState keccakTypes.Leaf, postStateProof contracts.MerkleProof) (txmgr.TxCandidate, error)
GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error)
} }
...@@ -46,6 +46,7 @@ contract SubmitLPP is Script, StdAssertions { ...@@ -46,6 +46,7 @@ contract SubmitLPP is Script, StdAssertions {
vm.broadcast(); vm.broadcast();
oracle.addLeavesLPP({ oracle.addLeavesLPP({
_uuid: TEST_UUID, _uuid: TEST_UUID,
_inputStartBlock: i / 136,
_input: chunk, _input: chunk,
_stateCommitments: finalize ? mockStateCommitmentsLast : mockStateCommitments, _stateCommitments: finalize ? mockStateCommitmentsLast : mockStateCommitments,
_finalize: finalize _finalize: finalize
......
...@@ -1096,10 +1096,10 @@ ...@@ -1096,10 +1096,10 @@
"impact": "Medium", "impact": "Medium",
"confidence": "Medium", "confidence": "Medium",
"check": "uninitialized-local", "check": "uninitialized-local",
"description": "PreimageOracle.challengeFirstLPP(address,uint256,PreimageOracle.Leaf,bytes32[]).stateMatrix (src/cannon/PreimageOracle.sol#459) is a local variable never initialized\n", "description": "PreimageOracle.challengeFirstLPP(address,uint256,PreimageOracle.Leaf,bytes32[]).stateMatrix (src/cannon/PreimageOracle.sol#444) is a local variable never initialized\n",
"type": "variable", "type": "variable",
"name": "stateMatrix", "name": "stateMatrix",
"start": 20988, "start": 20524,
"length": 40, "length": 40,
"filename_relative": "src/cannon/PreimageOracle.sol" "filename_relative": "src/cannon/PreimageOracle.sol"
}, },
......
...@@ -48,6 +48,11 @@ ...@@ -48,6 +48,11 @@
"name": "_uuid", "name": "_uuid",
"type": "uint256" "type": "uint256"
}, },
{
"internalType": "uint256",
"name": "_inputStartBlock",
"type": "uint256"
},
{ {
"internalType": "bytes", "internalType": "bytes",
"name": "_input", "name": "_input",
...@@ -771,5 +776,10 @@ ...@@ -771,5 +776,10 @@
"inputs": [], "inputs": [],
"name": "TreeSizeOverflow", "name": "TreeSizeOverflow",
"type": "error" "type": "error"
},
{
"inputs": [],
"name": "WrongStartingBlock",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -278,15 +278,13 @@ contract PreimageOracle is IPreimageOracle { ...@@ -278,15 +278,13 @@ contract PreimageOracle is IPreimageOracle {
/// @notice Adds a contiguous list of keccak state matrices to the merkle tree. /// @notice Adds a contiguous list of keccak state matrices to the merkle tree.
function addLeavesLPP( function addLeavesLPP(
uint256 _uuid, uint256 _uuid,
uint256 _inputStartBlock,
bytes calldata _input, bytes calldata _input,
bytes32[] calldata _stateCommitments, bytes32[] calldata _stateCommitments,
bool _finalize bool _finalize
) )
external external
{ {
// The caller of `addLeavesLPP` must be an EOA.
if (msg.sender != tx.origin) revert NotEOA();
// If we're finalizing, pad the input for the submitter. If not, copy the input into memory verbatim. // If we're finalizing, pad the input for the submitter. If not, copy the input into memory verbatim.
bytes memory input; bytes memory input;
if (_finalize) { if (_finalize) {
...@@ -298,6 +296,10 @@ contract PreimageOracle is IPreimageOracle { ...@@ -298,6 +296,10 @@ contract PreimageOracle is IPreimageOracle {
// Pull storage variables onto the stack / into memory for operations. // Pull storage variables onto the stack / into memory for operations.
bytes32[KECCAK_TREE_DEPTH] memory branch = proposalBranches[msg.sender][_uuid]; bytes32[KECCAK_TREE_DEPTH] memory branch = proposalBranches[msg.sender][_uuid];
LPPMetaData metaData = proposalMetadata[msg.sender][_uuid]; LPPMetaData metaData = proposalMetadata[msg.sender][_uuid];
uint256 blocksProcessed = metaData.blocksProcessed();
// The caller of `addLeavesLPP` must be an EOA.
if (msg.sender != tx.origin) revert NotEOA();
// Revert if the proposal has not been initialized. 0-size preimages are *not* allowed. // Revert if the proposal has not been initialized. 0-size preimages are *not* allowed.
if (metaData.claimedSize() == 0) revert NotInitialized(); if (metaData.claimedSize() == 0) revert NotInitialized();
...@@ -305,37 +307,15 @@ contract PreimageOracle is IPreimageOracle { ...@@ -305,37 +307,15 @@ contract PreimageOracle is IPreimageOracle {
// Revert if the proposal has already been finalized. No leaves can be added after this point. // Revert if the proposal has already been finalized. No leaves can be added after this point.
if (metaData.timestamp() != 0) revert AlreadyFinalized(); if (metaData.timestamp() != 0) revert AlreadyFinalized();
// Check if the part offset is present in the input data being posted. If it is, assign the part to the mapping. // Revert if the starting block is not the next block to be added. This is to aid submitters in ensuring that
uint256 offset = metaData.partOffset(); // they don't corrupt an in-progress proposal by submitting input out of order.
uint256 currentSize = metaData.bytesProcessed(); if (blocksProcessed != _inputStartBlock) revert WrongStartingBlock();
if (offset < 8 && currentSize == 0) {
uint32 claimedSize = metaData.claimedSize();
bytes32 preimagePart;
assembly {
mstore(0x00, shl(192, claimedSize))
mstore(0x08, calldataload(_input.offset))
preimagePart := mload(offset)
}
proposalParts[msg.sender][_uuid] = preimagePart;
} else if (offset >= 8 && (offset = offset - 8) >= currentSize && offset < currentSize + _input.length) {
uint256 relativeOffset = offset - currentSize;
// Revert if the full preimage part is not available in the data we're absorbing. The submitter must
// supply data that contains the full preimage part so that no partial preimage parts are stored in the
// oracle. Partial parts are *only* allowed at the tail end of the preimage, where no more data is available
// to be absorbed.
if (relativeOffset + 32 >= _input.length && !_finalize) revert PartOffsetOOB();
// If the preimage part is in the data we're about to absorb, persist the part to the caller's large // Attempt to extract the preimage part from the input data, if the part offset is present in the current
// preimaage metadata. // chunk of input. This function has side effects, and will persist the preimage part to the caller's large
bytes32 preimagePart; // preimage proposal storage if the part offset is present in the input data.
assembly { _extractPreimagePart(_input, _uuid, _finalize, metaData);
preimagePart := calldataload(add(_input.offset, relativeOffset))
}
proposalParts[msg.sender][_uuid] = preimagePart;
}
uint256 blocksProcessed = metaData.blocksProcessed();
assembly { assembly {
let inputLen := mload(input) let inputLen := mload(input)
let inputPtr := add(input, 0x20) let inputPtr := add(input, 0x20)
...@@ -389,15 +369,20 @@ contract PreimageOracle is IPreimageOracle { ...@@ -389,15 +369,20 @@ contract PreimageOracle is IPreimageOracle {
// Do not allow for posting preimages larger than the merkle tree can support. // Do not allow for posting preimages larger than the merkle tree can support.
if (blocksProcessed > MAX_LEAF_COUNT) revert TreeSizeOverflow(); if (blocksProcessed > MAX_LEAF_COUNT) revert TreeSizeOverflow();
// Perist the branch to storage. // Update the proposal metadata to include the number of blocks processed and total bytes processed.
metaData = metaData.setBlocksProcessed(uint32(blocksProcessed)).setBytesProcessed(
uint32(_input.length + metaData.bytesProcessed())
);
// If the proposal is being finalized, set the timestamp to the current block timestamp. This begins the
// challenge period, which must be waited out before the proposal can be finalized.
if (_finalize) metaData = metaData.setTimestamp(uint64(block.timestamp));
// Perist the latest branch to storage.
proposalBranches[msg.sender][_uuid] = branch; proposalBranches[msg.sender][_uuid] = branch;
// Track the block number that these leaves were added at. // Persist the block number that these leaves were added in. This assists off-chain observers in reconstructing
// the proposal merkle tree by querying block bodies.
proposalBlocks[msg.sender][_uuid].push(uint64(block.number)); proposalBlocks[msg.sender][_uuid].push(uint64(block.number));
// Persist the updated metadata to storage.
// Update the proposal metadata.
metaData =
metaData.setBlocksProcessed(uint32(blocksProcessed)).setBytesProcessed(uint32(_input.length + currentSize));
if (_finalize) metaData = metaData.setTimestamp(uint64(block.timestamp));
proposalMetadata[msg.sender][_uuid] = metaData; proposalMetadata[msg.sender][_uuid] = metaData;
} }
...@@ -534,6 +519,52 @@ contract PreimageOracle is IPreimageOracle { ...@@ -534,6 +519,52 @@ contract PreimageOracle is IPreimageOracle {
} }
} }
/// @notice Attempts to persist the preimage part to the caller's large preimage proposal storage, if the preimage
/// part is present in the input data being posted.
/// @param _input The portion of the preimage being posted.
/// @param _uuid The UUID of the large preimage proposal.
/// @param _finalize Whether or not the proposal is being finalized in the current call.
/// @param _metaData The metadata of the large preimage proposal.
function _extractPreimagePart(
bytes calldata _input,
uint256 _uuid,
bool _finalize,
LPPMetaData _metaData
)
internal
{
uint256 offset = _metaData.partOffset();
uint256 claimedSize = _metaData.claimedSize();
uint256 currentSize = _metaData.bytesProcessed();
// Check if the part offset is present in the input data being posted. If it is, assign the part to the mapping.
if (offset < 8 && currentSize == 0) {
bytes32 preimagePart;
assembly {
mstore(0x00, shl(192, claimedSize))
mstore(0x08, calldataload(_input.offset))
preimagePart := mload(offset)
}
proposalParts[msg.sender][_uuid] = preimagePart;
} else if (offset >= 8 && (offset = offset - 8) >= currentSize && offset < currentSize + _input.length) {
uint256 relativeOffset = offset - currentSize;
// Revert if the full preimage part is not available in the data we're absorbing. The submitter must
// supply data that contains the full preimage part so that no partial preimage parts are stored in the
// oracle. Partial parts are *only* allowed at the tail end of the preimage, where no more data is available
// to be absorbed.
if (relativeOffset + 32 >= _input.length && !_finalize) revert PartOffsetOOB();
// If the preimage part is in the data we're about to absorb, persist the part to the caller's large
// preimaage metadata.
bytes32 preimagePart;
assembly {
preimagePart := calldataload(add(_input.offset, relativeOffset))
}
proposalParts[msg.sender][_uuid] = preimagePart;
}
}
/// Check if leaf` at `index` verifies against the Merkle `root` and `branch`. /// Check if leaf` at `index` verifies against the Merkle `root` and `branch`.
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#is_valid_merkle_branch /// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#is_valid_merkle_branch
function _verify( function _verify(
......
...@@ -13,6 +13,9 @@ error InvalidPreimage(); ...@@ -13,6 +13,9 @@ error InvalidPreimage();
/// @notice Thrown when a leaf with an invalid input size is added. /// @notice Thrown when a leaf with an invalid input size is added.
error InvalidInputSize(); error InvalidInputSize();
/// @notice Thrown when data is submitted out of order in a large preimage proposal.
error WrongStartingBlock();
/// @notice Thrown when the pre and post states passed aren't contiguous. /// @notice Thrown when the pre and post states passed aren't contiguous.
error StatesNotContiguous(); error StatesNotContiguous();
......
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