Skip to content

Commit 2d31493

Browse files
DarkLord017rvaggwjmelements
authored
feat: Implement EXTRA_DATA_MAX_SIZE Limit in Filecoin-Services (#313)
### Resolves #304 - Added new error `ExtraDataSizeExceeded` in `Errors.sol` to signal when `extraData` exceeds allowed limits. - Defined new constants for maximum extra data sizes in `FilecoinWarmStorageService.sol`: - `MAX_CREATE_DATA_SET_EXTRA_DATA_SIZE = 4 KiB` - `MAX_PIECES_ADDED_EXTRA_DATA_SIZE = 8 KiB` - Updated functions in `FilecoinWarmStorageService` to: - Check `extraData.length` against these limits. - Revert with `ExtraDataSizeExceeded` if the size exceeds the maximum allowed. Did not add tests as this adds only a require statement , le'me know will add tests as well --------- Co-authored-by: Rod Vagg <[email protected]> Co-authored-by: William Morriss <[email protected]>
1 parent 1c54968 commit 2d31493

File tree

5 files changed

+225
-4
lines changed

5 files changed

+225
-4
lines changed

service_contracts/abi/Errors.abi.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,22 @@
228228
"name": "ExtraDataRequired",
229229
"inputs": []
230230
},
231+
{
232+
"type": "error",
233+
"name": "ExtraDataTooLarge",
234+
"inputs": [
235+
{
236+
"name": "actualSize",
237+
"type": "uint256",
238+
"internalType": "uint256"
239+
},
240+
{
241+
"name": "maxAllowedSize",
242+
"type": "uint256",
243+
"internalType": "uint256"
244+
}
245+
]
246+
},
231247
{
232248
"type": "error",
233249
"name": "FilBeamServiceNotConfigured",

service_contracts/abi/FilecoinWarmStorageService.abi.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,6 +1670,22 @@
16701670
"name": "ExtraDataRequired",
16711671
"inputs": []
16721672
},
1673+
{
1674+
"type": "error",
1675+
"name": "ExtraDataTooLarge",
1676+
"inputs": [
1677+
{
1678+
"name": "actualSize",
1679+
"type": "uint256",
1680+
"internalType": "uint256"
1681+
},
1682+
{
1683+
"name": "maxAllowedSize",
1684+
"type": "uint256",
1685+
"internalType": "uint256"
1686+
}
1687+
]
1688+
},
16731689
{
16741690
"type": "error",
16751691
"name": "FailedCall",

service_contracts/src/Errors.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,10 @@ library Errors {
289289
/// @param pdpEndEpoch The end epoch when the PDP payment rail will finalize
290290
error PaymentRailsNotFinalized(uint256 dataSetId, uint256 pdpEndEpoch);
291291

292+
/// @notice Extra data size exceeds the maximum allowed limit
293+
/// @param actualSize The size of the provided extra data
294+
/// @param maxAllowedSize The maximum allowed size for extra data
295+
error ExtraDataTooLarge(uint256 actualSize, uint256 maxAllowedSize);
292296
/// @notice The supplied capability keys did not contain all of the required keys for the product type
293297
/// @param productType The kind of service product attempted to be registered
294298
error InsufficientCapabilitiesForProduct(ServiceProviderRegistryStorage.ProductType productType);

service_contracts/src/FilecoinWarmStorageService.sol

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ uint256 constant BYTES_PER_LEAF = 32; // Each leaf is 32 bytes
2525
uint64 constant CHALLENGES_PER_PROOF = 5;
2626
uint256 constant COMMISSION_MAX_BPS = 10000; // 100% in basis points
2727

28+
/*
29+
* Maximum extraData for createDataSet
30+
* Supports: 10 metadata entries with max sizes
31+
*/
32+
uint256 constant MAX_CREATE_DATA_SET_EXTRA_DATA_SIZE = 4096; // 4 KiB
33+
34+
/*
35+
* Maximum extraData for addPieces
36+
* Supports: 5 pieces with full metadata, or 61 pieces with no metadata
37+
*/
38+
uint256 constant MAX_ADD_PIECES_EXTRA_DATA_SIZE = 8192; // 8 KiB
39+
40+
/*
41+
* Maximum extraData for schedulePieceRemovals
42+
* Supports: signature (160 bytes needed)
43+
*/
44+
uint256 constant MAX_SCHEDULE_PIECE_REMOVALS_EXTRA_DATA_SIZE = 256; // 256 bytes
45+
2846
/// @title FilecoinWarmStorageService
2947
/// @notice An implementation of PDP Listener with payment integration.
3048
/// @dev This contract extends SimplePDPService by adding payment functionality
@@ -519,7 +537,12 @@ contract FilecoinWarmStorageService is
519537
onlyPDPVerifier
520538
{
521539
// Decode the extra data to get the metadata, payer address, and signature
522-
require(extraData.length > 0, Errors.ExtraDataRequired());
540+
uint256 len = extraData.length;
541+
require(len > 0, Errors.ExtraDataRequired());
542+
require(
543+
len <= MAX_CREATE_DATA_SET_EXTRA_DATA_SIZE,
544+
Errors.ExtraDataTooLarge(len, MAX_CREATE_DATA_SET_EXTRA_DATA_SIZE)
545+
);
523546
DataSetCreateData memory createData = decodeDataSetCreateData(extraData);
524547

525548
// Validate the addresses
@@ -734,7 +757,9 @@ contract FilecoinWarmStorageService is
734757

735758
// Get the payer address for this data set
736759
address payer = info.payer;
737-
require(extraData.length > 0, Errors.ExtraDataRequired());
760+
uint256 len = extraData.length;
761+
require(len > 0, Errors.ExtraDataRequired());
762+
require(len <= MAX_ADD_PIECES_EXTRA_DATA_SIZE, Errors.ExtraDataTooLarge(len, MAX_ADD_PIECES_EXTRA_DATA_SIZE));
738763
// Decode the extra data
739764
(uint256 nonce, string[][] memory metadataKeys, string[][] memory metadataValues, bytes memory signature) =
740765
abi.decode(extraData, (uint256, string[][], string[][], bytes));
@@ -809,7 +834,12 @@ contract FilecoinWarmStorageService is
809834
address payer = info.payer;
810835

811836
// Decode the signature from extraData
812-
require(extraData.length > 0, Errors.ExtraDataRequired());
837+
uint256 len = extraData.length;
838+
require(len > 0, Errors.ExtraDataRequired());
839+
require(
840+
len <= MAX_SCHEDULE_PIECE_REMOVALS_EXTRA_DATA_SIZE,
841+
Errors.ExtraDataTooLarge(len, MAX_SCHEDULE_PIECE_REMOVALS_EXTRA_DATA_SIZE)
842+
);
813843
bytes memory signature = abi.decode(extraData, (bytes));
814844

815845
// Verify the signature

service_contracts/test/FilecoinWarmStorageService.t.sol

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import {Cids} from "@pdp/Cids.sol";
1010
import {MyERC1967Proxy} from "@pdp/ERC1967Proxy.sol";
1111
import {SessionKeyRegistry} from "@session-key-registry/SessionKeyRegistry.sol";
1212

13-
import {CHALLENGES_PER_PROOF, FilecoinWarmStorageService} from "../src/FilecoinWarmStorageService.sol";
13+
import {
14+
CHALLENGES_PER_PROOF,
15+
MAX_ADD_PIECES_EXTRA_DATA_SIZE,
16+
MAX_CREATE_DATA_SET_EXTRA_DATA_SIZE,
17+
FilecoinWarmStorageService
18+
} from "../src/FilecoinWarmStorageService.sol";
1419
import {FilecoinWarmStorageServiceStateView} from "../src/FilecoinWarmStorageServiceStateView.sol";
1520
import {SignatureVerificationLib} from "../src/lib/SignatureVerificationLib.sol";
1621
import {FilecoinWarmStorageServiceStateLibrary} from "../src/lib/FilecoinWarmStorageServiceStateLibrary.sol";
@@ -243,6 +248,22 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
243248
);
244249
}
245250

251+
function _generateKey(uint256 index) public pure returns (string memory) {
252+
bytes32 hash = keccak256(abi.encodePacked("base_salt", index));
253+
254+
// Convert hash to hex string
255+
string memory hexStr = Strings.toHexString(uint256(hash), 32); // 0x + 64 chars
256+
257+
// Remove the "0x" prefix and take only first 32 characters to make exactly 32 bytes
258+
bytes memory keyBytes = bytes(hexStr);
259+
bytes memory result = new bytes(32);
260+
for (uint256 i = 0; i < 32; i++) {
261+
result[i] = keyBytes[i + 2]; // skip '0x'
262+
}
263+
264+
return string(result); // exactly 32-byte unique string
265+
}
266+
246267
function testInitialState() public view {
247268
assertEq(
248269
pdpServiceWithPayments.pdpVerifierAddress(),
@@ -2517,6 +2538,55 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
25172538
}
25182539
}
25192540

2541+
function testDataSetMetaDataWithAllBoundaries() public {
2542+
// Create metadata with max keys, each with max key and value lengths
2543+
uint256[] memory keyCounts = new uint256[](3);
2544+
keyCounts[0] = MAX_KEYS_PER_DATASET - 1; // Just below max
2545+
keyCounts[1] = MAX_KEYS_PER_DATASET; // At max
2546+
keyCounts[2] = MAX_KEYS_PER_DATASET + 4; // Exceeds max
2547+
2548+
for (uint256 testIdx = 0; testIdx < keyCounts.length; testIdx++) {
2549+
uint256 keyCount = keyCounts[testIdx];
2550+
string[] memory metadataKeys = new string[](keyCount);
2551+
string[] memory metadataValues = new string[](keyCount);
2552+
2553+
for (uint256 i = 0; i < keyCount; i++) {
2554+
//key must be unique with length 32 bytes
2555+
metadataKeys[i] = _generateKey(i);
2556+
assertEq(bytes(metadataKeys[i]).length, 32, "Key length should be 32 bytes");
2557+
// Max key length
2558+
metadataValues[i] = _makeStringOfLength(128); // Max value length
2559+
}
2560+
2561+
if (keyCount <= MAX_KEYS_PER_DATASET) {
2562+
// Should succeed for valid counts
2563+
uint256 dataSetId = createDataSetForClient(sp1, client, metadataKeys, metadataValues);
2564+
2565+
// Verify all metadata keys and values
2566+
for (uint256 i = 0; i < metadataKeys.length; i++) {
2567+
(bool exists, string memory storedMetadata) =
2568+
viewContract.getDataSetMetadata(dataSetId, metadataKeys[i]);
2569+
assertTrue(exists, string.concat("Key ", metadataKeys[i], " should exist"));
2570+
assertEq(
2571+
storedMetadata,
2572+
metadataValues[i],
2573+
string.concat("Stored metadata for ", metadataKeys[i], " should match")
2574+
);
2575+
}
2576+
} else {
2577+
// Should fail for exceeding max
2578+
bytes memory encodedData = prepareDataSetForClient(sp1, client, metadataKeys, metadataValues);
2579+
vm.prank(sp1);
2580+
vm.expectRevert(
2581+
abi.encodeWithSelector(
2582+
Errors.ExtraDataTooLarge.selector, encodedData.length, MAX_CREATE_DATA_SET_EXTRA_DATA_SIZE
2583+
)
2584+
);
2585+
mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedData);
2586+
}
2587+
}
2588+
}
2589+
25202590
function setupDataSetWithPieceMetadata(
25212591
uint256 pieceId,
25222592
string[] memory keys,
@@ -2778,6 +2848,91 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
27782848
}
27792849
}
27802850

2851+
function testPieceMetadataAllBoundaries() public {
2852+
uint256 pieceId = 42;
2853+
2854+
// Helper to create a dataset
2855+
(string[] memory metadataKeys, string[] memory metadataValues) =
2856+
_getSingleMetadataKV("label", "Test Root Metadata");
2857+
uint256 dataSetId = createDataSetForClient(sp1, client, metadataKeys, metadataValues);
2858+
2859+
// Parameters
2860+
uint256 totalPieces = 5;
2861+
2862+
// --- Phase 1: all pieces within limits ---
2863+
{
2864+
Cids.Cid[] memory pieceData = new Cids.Cid[](totalPieces);
2865+
string[][] memory allKeys = new string[][](totalPieces);
2866+
string[][] memory allValues = new string[][](totalPieces);
2867+
2868+
for (uint256 p = 0; p < totalPieces; p++) {
2869+
pieceData[p] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("file", Strings.toString(p))));
2870+
2871+
uint256 keyCount = MAX_KEYS_PER_PIECE; // at the limit
2872+
string[] memory keys = new string[](keyCount);
2873+
string[] memory values = new string[](keyCount);
2874+
2875+
for (uint256 k = 0; k < keyCount; k++) {
2876+
// Generate globally unique keys by combining piece index and key index
2877+
keys[k] = _generateKey(p * 1000 + k);
2878+
assertEq(bytes(keys[k]).length, 32, "Key length should be 32 bytes");
2879+
values[k] = _makeStringOfLength(128); // max value length
2880+
}
2881+
2882+
allKeys[p] = keys;
2883+
allValues[p] = values;
2884+
}
2885+
2886+
uint256 nonce = pieceId + 1000;
2887+
bytes memory encodedData = abi.encode(nonce, allKeys, allValues, FAKE_SIGNATURE);
2888+
// Expect success
2889+
vm.expectEmit(true, false, false, true);
2890+
emit FilecoinWarmStorageService.PieceAdded(dataSetId, pieceId, pieceData[0], allKeys[0], allValues[0]);
2891+
2892+
vm.prank(address(mockPDPVerifier));
2893+
pdpServiceWithPayments.piecesAdded(dataSetId, pieceId, pieceData, encodedData);
2894+
console.log("encodedData length (within limits):", encodedData.length);
2895+
}
2896+
2897+
// --- Phase 2: one piece exceeds key limit and must revert ---
2898+
{
2899+
Cids.Cid[] memory pieceData = new Cids.Cid[](totalPieces);
2900+
string[][] memory allKeys = new string[][](totalPieces);
2901+
string[][] memory allValues = new string[][](totalPieces);
2902+
2903+
for (uint256 p = 0; p < totalPieces; p++) {
2904+
pieceData[p] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("file-ex", Strings.toString(p))));
2905+
2906+
// Make the last piece exceed the per-piece key limit
2907+
uint256 keyCount = (p == totalPieces - 1) ? (MAX_KEYS_PER_PIECE + 1) : MAX_KEYS_PER_PIECE;
2908+
string[] memory keys = new string[](keyCount);
2909+
string[] memory values = new string[](keyCount);
2910+
2911+
for (uint256 k = 0; k < keyCount; k++) {
2912+
// Ensure uniqueness across all pieces
2913+
keys[k] = _generateKey(p * 1000 + k + 1);
2914+
values[k] = _makeStringOfLength(128);
2915+
}
2916+
2917+
allKeys[p] = keys;
2918+
allValues[p] = values;
2919+
}
2920+
2921+
uint256 nonce = pieceId + 2000;
2922+
bytes memory encodedData = abi.encode(nonce, allKeys, allValues, FAKE_SIGNATURE);
2923+
2924+
// Expect revert when at least one piece has too many keys or extraData becomes too large
2925+
vm.prank(address(mockPDPVerifier));
2926+
vm.expectRevert(
2927+
abi.encodeWithSelector(
2928+
Errors.ExtraDataTooLarge.selector, encodedData.length, MAX_ADD_PIECES_EXTRA_DATA_SIZE
2929+
)
2930+
);
2931+
pdpServiceWithPayments.piecesAdded(dataSetId, pieceId + totalPieces, pieceData, encodedData);
2932+
console.log("encodedData length (exceeding limits):", encodedData.length);
2933+
}
2934+
}
2935+
27812936
function testPieceMetadataForSameKeyCannotRewrite() public {
27822937
uint256 pieceId = 42;
27832938

0 commit comments

Comments
 (0)