> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# طبقات التوسع Layer 2: كيف تقلل الرسوم وتسرع المعاملات
- URL: https://arabroot.io/articles/طبقات-التوسع-layer-2-كيف-تقلل-الرسوم-وتسرع-المعاملات/
- Published: 2026-10-28T06:10:35.000Z
- Updated: 2026-10-28T07:46:03.000Z
- Description: نبني طبقة ثانية متفائلة مصغرة ونقيس تكلفة مئة تحويل على السلسلة الأم مقابل دفعة وBlob، ثم نكشف مشغلاً كاذباً بإثبات الاحتيال.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, البلوكتشين والعملات الرقمية, الاستضافة الذاتية

إذا تابعت رسوم شبكة Ethereum الرئيسية في يوم مزدحم، فقد رأيت أن رسوم تحويل رمز بسيط قد تتجاوز قيمة ما يحول، ثم سمعت الجواب الذي يتكرر في كل نقاش عن ذلك: «استخدم طبقة ثانية Layer 2، فالرسوم فيها أقل بكثير». والجملة صحيحة، ولكن قبل أن تبني عليها يجب أن تعرف لماذا هي أرخص، وما الذي تدفعه مقابل ذلك، ومن تثق به الآن بعد أن كنت تثق بالسلسلة وحدها. ولكي نجيب بمثال عملي سوف نأخذ برنامج ولاء لسلسلة مقاه خارج المملكة، يحصل فيه العميل على نقاط مع كل كوب، ويستطيع أن ينقلها لصديق أو يستبدلها، ويريد صاحب السلسلة أن تكون النقاط رموزاً على شبكة Ethereum العامة حتى يثق العملاء بأنه لا يستطيع أن يمسح نقاطهم أو يغيرها. وكل نقل نقاط معاملة بغاز، وكل عملية في عقد الرموز أغلى من تحويل بسيط، كما رأينا في مقال الاختبار من خمسة أسئلة عندما كلف تسجيل حدث واحد قرابة 48 ألف وحدة غاز، ومع آلاف العملاء يومياً تصبح الرسوم على الشبكة الرئيسية أكبر من قيمة النقاط نفسها.

وفي مقال شبكة Lightning رأينا طريقة لتوسيع Bitcoin بقنوات بين طرفين. أما هنا فنتكلم عن الفكرة التي سيطرت على توسيع Ethereum، وهي الطبقات الثانية التي تجمع معاملات كثيرة وتنشرها في دفعة واحدة على السلسلة الأم، وتسمى Rollups. وسوف نبني نسخة مصغرة من طبقة ثانية متفائلة بعقد Solidity ومشغل مكتوب بلغة Python، ونقيس على شبكة Anvil المحلية تكلفة مئة تحويل مباشرة على السلسلة الأم مقابل دفعة واحدة كبيانات ومقابل Blob، ثم نجعل المشغل يكذب ونرى إثبات الاحتيال يكشفه خلال المهلة، ونرى ماذا يحدث عندما تنتهي المهلة ولا يراقب أحد. والمقال تعليمي تقني، ولا يوصي بأي شبكة أو رمز، وكل الأرقام من شبكة محلية بلا قيمة.

وسوف نناقش في هذا المقال ما يلي:

- لماذا لا تتسع السلسلة الأم لكل شيء، وما معنى أن نوسعها دون أن نضعف أمانها.
- فكرة التجميع Rollup: التنفيذ خارج السلسلة، ونشر البيانات والنتيجة عليها.
- تجربة: مشغل يجمع مئة تحويل في 300 بايت، وقياس الغاز على L1 مقابل الدفعة مقابل Blob.
- الطبقة الثانية المتفائلة وإثبات الاحتيال Fraud Proof، بتجربة طعن في دفعة صحيحة ودفعة كاذبة وبعد انتهاء المهلة.
- الطبقة الثانية بإثبات الصحة ZK Rollup، وقنوات الحالة، والسلاسل الجانبية، والفرق بينها.
- توفر البيانات Data Availability وBlobs منذ ترقية Dencun.
- مسار المعاملة على الطبقة الثانية ودرجات النهائية، ومن أين تأتي الرسوم.
- أين تثق عندما تستخدم طبقة ثانية: المشغل ومفاتيح الترقية والجسر، وحوادث الجسور الموثقة، وقائمة قرار.

## لماذا لا تتسع السلسلة الأم لكل شيء؟

في Ethereum كل عقدة كاملة تنفذ كل معاملة في كل كتلة وتحفظ نتيجتها، كما شرحنا في المقال الأول، وهذا ما يجعل أي شخص يستطيع أن يتحقق بنفسه دون أن يثق بأحد. والكتلة محدودة بكمية الغاز التي تتسع لها، والسبب أن رفع هذا الحد بلا نهاية يعني أن تشغيل عقدة يحتاج أجهزة أقوى وأغلى، فيقل عدد من يستطيعون التحقق، ونعود إلى الوثوق بعدد صغير من المشغلين الكبار. وعندما يكون الطلب على المساحة أكبر من الكتلة ترتفع الرسوم، لأن المعاملات تتنافس عليها بالسعر كما رأينا في Bitcoin. وهذه مقايضة يسميها الناس أحياناً ثلاثية التوسع: أن تكون الشبكة لامركزية وآمنة وقادرة على معاملات كثيرة في نفس الوقت، وكل حل يحاول أن يحرك هذه المقايضة دون أن يتخلى عن أحد أطرافها.

والفكرة التي جاءت بها الطبقات الثانية بسيطة في جوهرها: السلسلة الأم لا تحتاج أن تنفذ كل معاملة صغيرة بنفسها، وإنما تحتاج أن تضمن أمرين: أن البيانات التي تكفي لإعادة بناء الحالة منشورة ومتاحة لأي شخص، وأن النتيجة المعلنة صحيحة أو يمكن إثبات خطئها. فإذا تحقق هذان الأمران، يستطيع مشغل أن ينفذ آلاف المعاملات خارج السلسلة، ويكتب على السلسلة الأم ملخصاً مضغوطاً لها ومعه النتيجة، ويبقى المستخدم قادراً على استرداد أمواله حتى لو اختفى المشغل. وتشرح [صفحة التوسع في ethereum.org](https://ethereum.org/en/developers/docs/scaling/?ref=arabroot.io) هذا التقسيم بين حلول على السلسلة نفسها وحلول خارجها، والطبقات الثانية هي الثانية.

## التجميع Rollup: التنفيذ خارج السلسلة والنشر عليها

التجميع Rollup يعمل بثلاث خطوات: المستخدمون يرسلون معاملاتهم إلى مشغل الطبقة الثانية Sequencer بدلاً من السلسلة الأم، والمشغل ينفذها ويحسب الحالة الجديدة ويرتبها في دفعات، ثم ينشر على السلسلة الأم بيانات الدفعة مضغوطة ومعها جذر الحالة الجديدة State Root، أي هاش يلخص كل الأرصدة بعد الدفعة. والسلسلة الأم لا تنفذ التحويلات، وإنما تحفظ البيانات والجذر. والتوفير يأتي من أمرين: الأول أن الدفعة كلها تدفع تكلفة المعاملة الأساسية مرة واحدة بدلاً من مرة لكل تحويل، والثاني أن كل تحويل داخل الدفعة يمكن تمثيله ببايتات قليلة، لأن التوقيعات يتحقق منها المشغل، ولأن العناوين يمكن استبدالها بأرقام فهرسة قصيرة.

ولكن هنا يأتي السؤال الذي يقرر كل شيء: كيف تعرف السلسلة الأم أن الجذر الذي نشره المشغل صحيح، دون أن تنفذ المعاملات بنفسها؟ وهناك جوابان، ومنهما جاء نوعا التجميع: المتفائل Optimistic يفترض أن الجذر صحيح ويعطي الجميع مهلة ليثبتوا العكس، والتجميع بإثبات الصحة ZK Rollup يرفق مع كل دفعة إثباتاً رياضياً على أن الجذر صحيح يتحقق منه عقد على السلسلة الأم. وسوف نبني الأول بأيدينا لأنه أسهل في الفهم، ثم نقارنه بالثاني.

### قبل التجميع: لماذا لم تكف Plasma؟

فكرة أن ينشر مشغل خارج السلسلة ملخصاً لحالته على السلسلة الأم أقدم من التجميع، وأشهر محاولاتها كانت Plasma. وكما تشرح [صفحة Plasma في ethereum.org](https://ethereum.org/en/developers/docs/scaling/plasma/?ref=arabroot.io) كان المشغل ينفذ المعاملات وينشر على السلسلة الأم دورياً «التزامات حالة» State Commitments، أي جذور Merkle تلخص الحالة، كأنها نقاط حفظ، ويستطيع المستخدم أن يخرج بأمواله إلى السلسلة الأم بإثبات أنه يملكها في آخر حالة. والمشكلة أن Plasma لم تكن تنشر بيانات المعاملات نفسها على السلسلة الأم، وإنما الجذور فقط، فإذا نشر مشغل جذراً لحالة لا يعرف أحد تفاصيلها، لا يستطيع أحد أن يثبت أنها خاطئة، ولا يستطيع المستخدمون أن يعرفوا أرصدتهم ليخرجوا بها، فيضطر الجميع إلى الخروج دفعة واحدة، وهذا ما تسميه الصفحة مشكلة الخروج الجماعي Mass Exit.

والتجميع حل هذه المشكلة بأن جعل بيانات كل دفعة منشورة على السلسلة الأم نفسها، كما فعلنا في تجربتنا: أي مراقب يقرأ البيانات من السلسلة ويعيد بناء الحالة دون أن يحتاج إذناً من المشغل أو نسخة منه، وهذا ما جعل إثبات الاحتيال ممكناً عملياً. والثمن أن نشر البيانات أغلى من نشر الجذر وحده، وهذا هو الثمن الذي جاءت الـ Blobs لتخفضه كما سنرى.

## تجربة: طبقة ثانية متفائلة مصغرة

تصميمنا مصغر عمداً: ثمانية حسابات فقط، وكل تحويل ثلاثة بايتات، رقم الحساب المرسل ورقم المستلم والمبلغ حتى 255، والحالة هي أرصدة الحسابات الثمانية، وجذرها هاش Keccak-256 لهذه الأرصدة. والعقد على السلسلة الأم يحفظ لكل دفعة الجذر السابق وهاش البيانات والجذر الجديد ووقت النشر، ويقبل الطعن خلال ساعة، والطاعن يقدم بيانات الدفعة والأرصدة السابقة، فيعيد العقد تنفيذ الدفعة على السلسلة الأم ويقارن الناتج بالجذر المعلن. احفظ العقد باسم `src/MiniRollup.sol`:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// A toy optimistic rollup on L1: 8 accounts, transfers of 3 bytes (from, to, amount),
// the sequencer posts the batch and the new state root; anyone can prove a wrong root during the challenge window.
contract MiniRollup {
    struct Batch { bytes32 prevRoot; bytes32 dataHash; bytes32 newRoot; uint256 postedAt; bool reverted; }

    address public immutable sequencer;
    uint256 public constant CHALLENGE_PERIOD = 1 hours;
    uint256 public bond;                       // sequencer's deposit, paid to a successful challenger
    bytes32 public stateRoot;                  // latest claimed state root
    Batch[] public batches;

    event BatchPosted(uint256 indexed index, bytes32 newRoot, uint256 bytesOfData);
    event FraudProven(uint256 indexed index, bytes32 claimed, bytes32 correct);

    constructor(bytes32 genesisRoot) payable { sequencer = msg.sender; bond = msg.value; stateRoot = genesisRoot; }

    function postBatch(bytes calldata data, bytes32 newRoot) external {
        require(msg.sender == sequencer, "only sequencer");
        batches.push(Batch(stateRoot, keccak256(data), newRoot, block.timestamp, false));
        stateRoot = newRoot;
        emit BatchPosted(batches.length - 1, newRoot, data.length);
    }

    function rootOf(uint64[8] memory bal) public pure returns (bytes32) { return keccak256(abi.encodePacked(bal)); }

    // re-execute the batch on L1 from the previous state; if the result differs from the claim, the batch is reverted
    function challenge(uint256 i, bytes calldata data, uint64[8] calldata prevBalances) external {
        Batch storage b = batches[i];
        require(!b.reverted, "already reverted");
        require(block.timestamp < b.postedAt + CHALLENGE_PERIOD, "challenge period over");
        require(keccak256(data) == b.dataHash, "not the posted data");
        require(rootOf(prevBalances) == b.prevRoot, "wrong previous state");
        uint64[8] memory bal = prevBalances;
        for (uint256 k = 0; k + 2 < data.length; k += 3) {
            uint8 from = uint8(data[k]); uint8 to = uint8(data[k + 1]); uint64 amt = uint8(data[k + 2]);
            if (from < 8 && to < 8 && bal[from] >= amt) { bal[from] -= amt; bal[to] += amt; }   // invalid transfers are skipped
        }
        bytes32 correct = rootOf(bal);
        require(correct != b.newRoot, "batch is correct");
        b.reverted = true;
        stateRoot = b.prevRoot;                 // roll back (later batches would also be reverted in a real design)
        uint256 reward = bond; bond = 0;
        payable(msg.sender).transfer(reward);
        emit FraudProven(i, b.newRoot, correct);
    }
}
```

في الكود أعلاه لاحظ التالي:

- الدالة `postBatch` لا تنفذ أي تحويل، وإنما تحفظ هاش البيانات `keccak256(data)` والجذر الجديد، والبيانات نفسها تبقى في بيانات المعاملة Calldata التي يحفظها كل من يحمل السلسلة، فيستطيع أي شخص أن يقرأها لاحقاً.
- الدالة `challenge` هي إثبات الاحتيال Fraud Proof: تتأكد أن البيانات هي نفس ما نشر، وأن الأرصدة السابقة تطابق الجذر السابق، ثم تعيد التنفيذ على السلسلة الأم، فإذا اختلف الجذر الناتج عن المعلن ألغت الدفعة وأعادت الجذر السابق ودفعت ضمان المشغل للطاعن.
- التحويلات غير الصالحة، مثل مبلغ أكبر من الرصيد، تتجاوز ولا تفشل الدفعة كلها، وهذه القاعدة يجب أن يطبقها المشغل والعقد بنفس الطريقة بالضبط، وإلا اختلف الجذران دون أن يكون أحد كاذباً.
- في التصميم الحقيقي تلغى أيضاً كل الدفعات التي جاءت بعد الدفعة الكاذبة لأنها بنيت عليها، وتصميمنا يعيد الجذر فقط للتبسيط.

والمشغل برنامج Python ينفذ مئة تحويل عشوائي بين الحسابات الثمانية، ويحسب الجذر الصحيح، وجذراً كاذباً يضيف فيه 500 نقطة لحساب رقم 7، ويحفظ كل ذلك في ملف. احفظه باسم `sequencer.py`:

```python
# Off-chain sequencer: executes 100 transfers among 8 accounts and prints the batch and the state roots.
import random, sys, json
from Crypto.Hash import keccak

def root(bal):
    # same bytes as Solidity abi.encodePacked(uint64[8]): array items are padded to 32 bytes each
    h = keccak.new(digest_bits=256)
    h.update(b"".join(x.to_bytes(32, "big") for x in bal))
    return "0x" + h.hexdigest()

random.seed(int(sys.argv[1]) if len(sys.argv) > 1 else 7)
bal = [1000] * 8
genesis = root(bal)
data = bytearray()
for _ in range(100):
    f, t, a = random.randrange(8), random.randrange(8), random.randrange(1, 50)
    data += bytes([f, t, a])
    if bal[f] >= a: bal[f] -= a; bal[t] += a
honest = root(bal)
cheat = bal[:]; cheat[7] += 500                      # a dishonest sequencer credits account 7 with 500 extra
json.dump({"genesis": genesis, "data": "0x" + data.hex(), "honest": honest, "cheat": root(cheat),
           "prev": [1000] * 8, "final": bal}, open("batch.json", "w"), indent=1)
open("batch.bin", "wb").write(bytes(data))
print("transfers:", len(data) // 3, "| batch bytes:", len(data))
print("genesis root:", genesis)
print("honest root :", honest)
print("cheat root  :", root(cheat))
print("final balances:", bal, "sum", sum(bal))
```

```bash
docker run --rm --name lab-l2-py -e PIP_DISABLE_PIP_VERSION_CHECK=1 -v "$PWD:/lab" -w /lab python:3.13.16-slim \
  bash -c "pip install -q --root-user-action=ignore pycryptodome==3.23.0 && python sequencer.py"
```

والمخرج سوف يكون كما يلي:

```text
transfers: 100 | batch bytes: 300
genesis root: 0x104e0ea962fdbe8b39d2f005090a399248bed254cb807a90416f61f2a928af40
honest root : 0x5c6ec8d06532c7452fc83e646a08c03ad9132b7e9d3e0f2095f730a66b14f569
cheat root  : 0xc8e5263769c079d8f484e8f975849f4bff5d42dab0c3a0be1455cce8a35678f8
final balances: [1105, 856, 1319, 814, 983, 949, 782, 1192] sum 8000
```

مئة تحويل في 300 بايت فقط، ومجموع الأرصدة بقي 8000 كما بدأ، لأن التحويلات تنقل ولا تنشئ. وهنا ظهرت لنا مشكلة حقيقية في أول تشغيل: حسبنا الجذر في Python بترميز كل رصيد في 8 بايتات، فلم يطابق ما يحسبه العقد، والسبب أن الدالة `abi.encodePacked` في Solidity تضم عناصر المصفوفة بعد أن تجعل كل عنصر 32 بايتاً، حتى لو كان نوعه `uint64`، فعدلنا الدالة `root` لتفعل نفس الشيء، وهذا هو التعليق الموجود فيها. وهذا النوع من الاختلاف في الترميز بين المشغل والعقد هو بالضبط ما يجب أن تختبره أي طبقة ثانية حقيقية بعناية، لأنه يجعل الجذر الصادق يبدو كاذباً.

### على السلسلة الأم: تحويل عادي، ثم نشر الدفعة

نشغل Anvil من Image مجموعة Foundry، وننسخ المشروع إلى مجلد داخل ال Container ونبنيه، ثم نقيس تحويلاً عادياً على السلسلة الأم كخط أساس، وننشر العقد بضمان من المشغل قدره 1 ETH:

```bash
docker run -d --name lab-l2-anvil -v "$PWD:/src" ghcr.io/foundry-rs/foundry:v1.8.6 "anvil --host 0.0.0.0"
docker exec lab-l2-anvil sh -c "cp -r /src /tmp/l2 && cd /tmp/l2 && forge build"
A="docker exec -w /tmp/l2 lab-l2-anvil"
K=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
G=0x104e0ea962fdbe8b39d2f005090a399248bed254cb807a90416f61f2a928af40
$A cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --value 0.01ether --private-key $K | grep -E '^(gasUsed|effectiveGasPrice) '
$A forge create src/MiniRollup.sol:MiniRollup --broadcast --private-key $K --value 1ether --constructor-args $G | grep -E "Deployed to|Transaction hash"
```

والمخرج سوف يكون كما يلي:

```text
effectiveGasPrice    1000000001
gasUsed              21000
Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
Transaction hash: 0xd01d69748cb655418e678a788e447c69d19055365f45cdd9c98619ef0a9e1cab
```

التحويل العادي كلف 21,000 وحدة غاز، وهي أقل تكلفة لأي معاملة على Ethereum، فمئة تحويل مستقل تكلف على الأقل 2,100,000\. والآن نتأكد أن العقد يحسب الجذر بنفس طريقة المشغل، ثم ننشر الدفعة الصادقة، ثم نجرب أن نطعن فيها:

```bash
R=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
P="[1000,1000,1000,1000,1000,1000,1000,1000]"
D=$(python3 -c "import json;print(json.load(open('batch.json'))['data'])")
H=0x5c6ec8d06532c7452fc83e646a08c03ad9132b7e9d3e0f2095f730a66b14f569
K1=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
$A cast call $R "rootOf(uint64[8])(bytes32)" "$P"
$A cast send $R "postBatch(bytes,bytes32)" $D $H --private-key $K | grep -E '^(gasUsed|status) '
$A cast send $R "challenge(uint256,bytes,uint64[8])" 0 $D "$P" --private-key $K1
```

والمخرج سوف يكون كما يلي، مع اختصار رسالة الخطأ الأخيرة:

```text
0x104e0ea962fdbe8b39d2f005090a399248bed254cb807a90416f61f2a928af40
gasUsed              147939
status               1 (success)
execution reverted: batch is correct, data: …
```

في الإعداد أعلاه لاحظ التالي:

- الدالة `rootOf` في العقد أعطت لأرصدة البداية نفس الجذر `0x104e0ea9…` الذي حسبه المشغل، أي أن الطرفين يتفقان على الترميز بعد إصلاحه.
- نشر الدفعة كاملة بمئة تحويل كلف 147,939 وحدة غاز، أي قرابة 1,479 لكل تحويل بدلاً من 21,000 على الأقل، أي قرابة 14 مرة أرخص، وجزء كبير من هذه التكلفة حفظ بيانات الدفعة في تخزين العقد، والبيانات نفسها 300 بايت فقط.
- الطعن في الدفعة الصادقة رفض برسالة `batch is correct`، فالعقد أعاد التنفيذ ووجد نفس الجذر، ولم يتغير شيء، وخسر الطاعن رسوم معاملته فقط، وهذا يمنع الطعون العبثية.

### نجرب العطل: مشغل يكذب

الآن ننشر نسخة جديدة من العقد، ويرسل المشغل نفس البيانات ولكن بالجذر الكاذب الذي يعطي حساب رقم 7 خمسمئة نقطة لم يربحها أحد، ثم يطعن حساب آخر خلال المهلة بالبيانات المنشورة والأرصدة السابقة:

```bash
C1=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
X=0xc8e5263769c079d8f484e8f975849f4bff5d42dab0c3a0be1455cce8a35678f8
R2=$($A forge create src/MiniRollup.sol:MiniRollup --broadcast --private-key $K --value 1ether --constructor-args $G | grep "Deployed to" | awk '{print $3}'); echo "rollup2 $R2"
$A cast send $R2 "postBatch(bytes,bytes32)" $D $X --private-key $K | grep -E '^(gasUsed|status) '
echo "challenger before: $($A cast balance $C1 --ether)"
$A cast send $R2 "challenge(uint256,bytes,uint64[8])" 0 $D "$P" --private-key $K1 | grep -E '^(gasUsed|status) '
echo "challenger after: $($A cast balance $C1 --ether)"
$A cast call $R2 "stateRoot()(bytes32)"
$A cast call $R2 "batches(uint256)(bytes32,bytes32,bytes32,uint256,bool)" 0 | tail -1
```

والمخرج سوف يكون كما يلي:

```text
rollup2 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
gasUsed              147939
status               1 (success)
challenger before: 10000.010000000000000000
gasUsed              256455
status               1 (success)
challenger after: 10001.009865248794888995
0x104e0ea962fdbe8b39d2f005090a399248bed254cb807a90416f61f2a928af40
true
```

في الإعداد أعلاه لاحظ التالي:

- نشر الدفعة الكاذبة كلف نفس الغاز تماماً، 147,939، فالعقد لا يستطيع وقت النشر أن يميز الكاذبة من الصادقة، وهذا معنى «متفائلة».
- الطعن نجح، وكلف 256,455 وحدة غاز، أي أغلى من النشر بكثير، لأن العقد نفذ التحويلات المئة على السلسلة الأم، وهذه التكلفة تدفع فقط عندما يكذب أحد.
- رصيد الطاعن زاد قرابة 1 ETH ناقص رسوم الطعن، وهو ضمان المشغل، وهذا ما يجعل الطعن مربحاً لمن يراقب، والغش مكلفاً لمن يغش.
- جذر الحالة عاد إلى جذر البداية `0x104e0ea9…`، والدفعة أصبحت معلمة كملغاة `true`، فالنقاط الخمسمئة المزيفة لم تعد موجودة.

والصورة التالية تبين الطريق كله، ثم نتائج المحاولات:

![أربع خطوات: المستخدمون يرسلون معاملاتهم إلى المشغل، ثم المشغل ينفذ ويجمع 100 تحويل في 300 بايت والجذر الصحيح 0x5c6ec8d0، ثم النشر على L1 في عقد يحفظ هاش البيانات والجذر بغاز 147,939، ثم مهلة الطعن ساعة في التجربة وقرابة سبعة أيام في الشبكات الحقيقية، وتحتها شرح أن مراقباً أميناً واحداً يكفي](https://arabroot.io/content/images/2026/10/layer2-scaling-02-optimistic.webp)

النشر يفترض الصحة، والمهلة تعطي أي شخص فرصة إثبات العكس

### ماذا لو رفض المشغل معاملتك؟

التصميم الذي بنيناه فيه ثغرة واضحة لم نحاول إصلاحها: المشغل وحده يستطيع النشر، فإذا قرر أن يتجاهل تحويلات حساب معين، أو توقف تماماً، لا يستطيع صاحب الحساب أن يفعل شيئاً، لا أن ينقل نقاطه ولا أن يخرج بها. وتذكر صفحة الطبقات المتفائلة في ethereum.org هذه المشكلة تحت عنوان مقاومة الرقابة Censorship Resistance، فتقول إن المشغل المركزي يستطيع أن يراقب المستخدمين بأن يتوقف كلياً أو يرفض إنتاج كتل تحتوي معاملات معينة، وإن الطبقات المتفائلة لهذا السبب تسمح للمستخدم أن يرسل معاملته مباشرة إلى عقد الطبقة على السلسلة الأم إذا تأخرت أكثر من المدة القصوى المسموحة. أي أن السلسلة الأم تصبح طريق الطوارئ: أبطأ وأغلى، ولكنها لا تحتاج موافقة المشغل. وعندما تقيم أي طبقة ثانية، فهذا من أول ما تسأل عنه: هل هذا الطريق موجود فعلاً ومفعل، وكم يستغرق، وهل جربه أحد؟ لأن الطبقة التي لا يوجد فيها هذا الطريق تعطي مشغلها قدرة على حبس الأموال، مهما كانت رياضيات الطعن فيها صحيحة.

### ماذا يحدث إذا لم يراقب أحد؟

الطعن ممكن فقط خلال المهلة. فننشر نسخة ثالثة، ويرسل المشغل الجذر الكاذب، ثم نقدم الزمن في Anvil ساعة وثانية، ونعدن كتلة، ثم نحاول الطعن:

```bash
R3=$($A forge create src/MiniRollup.sol:MiniRollup --broadcast --private-key $K --value 1ether --constructor-args $G | grep "Deployed to" | awk '{print $3}'); echo "rollup3 $R3"
$A cast send $R3 "postBatch(bytes,bytes32)" $D $X --private-key $K | grep -E '^status '
$A cast rpc evm_increaseTime 3601 > /dev/null
$A cast rpc evm_mine > /dev/null
$A cast send $R3 "challenge(uint256,bytes,uint64[8])" 0 $D "$P" --private-key $K1
$A cast call $R3 "stateRoot()(bytes32)"
```

والمخرج سوف يكون كما يلي، مع اختصار رسالة الخطأ:

```text
rollup3 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
status               1 (success)
execution reverted: challenge period over
0xc8e5263769c079d8f484e8f975849f4bff5d42dab0c3a0be1455cce8a35678f8
```

الطعن رفض لأن المهلة انتهت، والجذر الكاذب بقي هو الحالة الرسمية، والنقاط المزيفة أصبحت نهائية. وهذا هو الشرط الذي يقوم عليه أمان الطبقة المتفائلة: يكفي مراقب أمين واحد، ولكن يجب أن يوجد هذا المراقب، وأن يكون متصلاً، وأن تكون البيانات متاحة له خلال المهلة. والصورة التالية تجمع المحاولات الثلاث:

![ثلاث بطاقات: طعن في دفعة صحيحة يرفض برسالة batch is correct، وطعن في دفعة كاذبة خلال المهلة ينجح بغاز 256,455 ويرتفع رصيد الطاعن من 10000.01 إلى 10001.0099 ويعود الجذر إلى البداية، وطعن بعد انتهاء المهلة يرفض برسالة challenge period over ويبقى الجذر الكاذب، وتحتها درس أن الطعن أغلى من النشر ويحدث فقط عند الغش، والخطر الحقيقي ألا يراقب أحد](https://arabroot.io/content/images/2026/10/layer2-scaling-03-fraud.webp)

الطعن يعمل خلال المهلة فقط، والجذر الكاذب الذي لا يطعن فيه أحد يصبح نهائياً

ولهذا السبب تحديداً تكون مهلة الطعن في الشبكات الحقيقية طويلة، وكما تذكر [صفحة الطبقات المتفائلة في ethereum.org](https://ethereum.org/en/developers/docs/scaling/optimistic-rollups/?ref=arabroot.io)، فالسحب من الطبقة الثانية إلى السلسلة الأم ينتظر انتهاء مهلة الطعن التي تمتد قرابة سبعة أيام، حتى يكون للمراقبين وقت كاف حتى لو تعرضت الشبكة لهجوم أو ازدحام. ولبرنامج الولاء في مثالنا يعني هذا شيئاً عملياً: نقل النقاط داخل الطبقة الثانية سريع ورخيص، ولكن إخراجها إلى الشبكة الرئيسية ينتظر أسبوعاً، إلا إذا استخدم المستخدم خدمة طرف ثالث تعطيه المبلغ فوراً مقابل رسوم وتنتظر هي الأسبوع، وهذا يضيف طرفاً جديداً يجب أن يثق به.

### مراقب مستقل يقرأ البيانات من السلسلة

الطاعن في التجربة السابقة استخدم ملف المشغل نفسه، أما المراقب الحقيقي فلا يثق بأي ملف من المشغل، وإنما يقرأ البيانات المنشورة على السلسلة الأم ويعيد التنفيذ بنفسه. نبحث أولاً عن حدث نشر الدفعة في العقد الأول، ثم نقرأ بيانات المعاملة التي نشرتها ونفك ترميزها بأداة `cast`:

```bash
$A cast logs --address $R "BatchPosted(uint256 indexed,bytes32,uint256)" | grep -E "transactionHash|blockNumber" | head -2
T=$($A cast logs --address $R "BatchPosted(uint256 indexed,bytes32,uint256)" | grep transactionHash | awk '{print $2}')
$A cast calldata-decode "postBatch(bytes,bytes32)" $($A cast tx $T input) > decoded.txt
cut -c1-40 decoded.txt
```

والمخرج سوف يكون كما يلي:

```text
  blockNumber: 3
  transactionHash: 0x7c926efba09971b4f5537ee414de1593353a6b1bac96c55e4ade66a160a93f07
0x05021a00012301052600030301061b01030606
0x5c6ec8d06532c7452fc83e646a08c03ad9132b
```

السطر الأول بعد الحدث هو بيانات الدفعة كما هي على السلسلة، وأول ثلاثة بايتات `05 02 1a` تعني تحويل 26 نقطة من الحساب 5 إلى الحساب 2، والسطر الثاني الجذر الذي أعلنه المشغل. والآن برنامج المراقب، احفظه باسم `verifier.py`:

```python
# An independent watcher: rebuild the state from the data the sequencer posted on L1 and compare roots.
import sys
from Crypto.Hash import keccak

def root(bal):
    h = keccak.new(digest_bits=256)
    h.update(b"".join(x.to_bytes(32, "big") for x in bal))
    return "0x" + h.hexdigest()

data_hex, claimed = open(sys.argv[1]).read().split()
data = bytes.fromhex(data_hex[2:])
bal = [1000] * 8                                   # state before the batch (the genesis state here)
for k in range(0, len(data) - 2, 3):
    f, t, a = data[k], data[k + 1], data[k + 2]
    if f < 8 and t < 8 and bal[f] >= a:
        bal[f] -= a; bal[t] += a
mine = root(bal)
print("transfers read from L1:", len(data) // 3)
print("root I computed       :", mine)
print("root the sequencer set:", claimed)
print("verdict               :", "OK" if mine == claimed else "WRONG ROOT - challenge it")
```

```bash
docker run --rm --name lab-l2-py -e PIP_DISABLE_PIP_VERSION_CHECK=1 -v "$PWD:/lab" -w /lab python:3.13.16-slim \
  bash -c "pip install -q --root-user-action=ignore pycryptodome==3.23.0 && python verifier.py decoded.txt"
```

والمخرج سوف يكون كما يلي:

```text
transfers read from L1: 100
root I computed       : 0x5c6ec8d06532c7452fc83e646a08c03ad9132b7e9d3e0f2095f730a66b14f569
root the sequencer set: 0x5c6ec8d06532c7452fc83e646a08c03ad9132b7e9d3e0f2095f730a66b14f569
verdict               : OK
```

ثم نفعل نفس الشيء مع العقد الثالث الذي بقي فيه الجذر الكاذب بعد انتهاء المهلة، فنحفظ بياناته في `decoded3.txt` بنفس الأوامر ونشغل المراقب عليه، والسطران الأخيران من المخرج سوف يكونان كما يلي:

```text
root the sequencer set: 0xc8e5263769c079d8f484e8f975849f4bff5d42dab0c3a0be1455cce8a35678f8
verdict               : WRONG ROOT - challenge it
```

المراقب كشف الكذب في ثوان من البيانات العلنية وحدها، ولكن متأخراً، فالمهلة انتهت قبل أن يشغله أحد، وهذا بالضبط ما يجعل تشغيل مراقب بشكل دائم جزءاً من الأمان وليس إضافة. ولبرنامج الولاء في مثالنا، إذا بنت الشركة طبقة خاصة بها بهذا التصميم، فوجود مراقب يشغله طرف مستقل عنها، مثل جمعية تجار أو مدقق، هو ما يعطي العملاء الضمان الذي طلبوه، لأن المراقب الذي تشغله الشركة نفسها لن يطعن في كذبها.

### ما الذي ينقص تجربتنا عن طبقة حقيقية؟

حتى لا تخرج من التجربة بانطباع خاطئ، هذه أهم الأشياء التي تركناها عمداً. أولاً، التحويلات في دفعتنا لا تحمل توقيعات، فالمشغل يستطيع أن يكتب أي تحويل من أي حساب، وفي الطبقة الحقيقية يوقع كل مستخدم معاملته، والمشغل يتحقق من التوقيعات، وإثبات الاحتيال يجب أن يغطي التوقيع غير الصحيح أيضاً. ثانياً، لا يوجد عداد Nonce لكل حساب، فلا شيء يمنع تكرار نفس التحويل. ثالثاً، لا يوجد إيداع وسحب عبر جسر، وهما أصعب جزء في أي طبقة، لأن السحب يجب أن ينتظر النهائية، والجسر يجب أن يتحقق من الحالة قبل أن يدفع. رابعاً، الطعن عندنا يعيد تنفيذ الدفعة كلها في معاملة واحدة على السلسلة الأم، وهذا ممكن لمئة تحويل بسيط، ولكنه مستحيل لدفعة فيها آلاف استدعاءات العقود، لذلك تستخدم الطبقات الحقيقية طعناً تفاعلياً يضيق الخلاف بين الطرفين خطوة بخطوة حتى يصل إلى تعليمة واحدة تنفذ على السلسلة الأم. وكل هذه الأجزاء هي ما يجعل الطبقة الحقيقية مشروعاً هندسياً كبيراً، والفكرة الأساسية مع ذلك هي نفسها التي رأيتها تعمل في مئة سطر.

## توفر البيانات Data Availability وBlobs

في تجربتنا وضع المشغل البيانات في بيانات استدعاء المعاملة Calldata، وهي تبقى في السلسلة إلى الأبد عند كل عقدة كاملة، وهذا أغلى من اللازم، لأن المراقبين يحتاجون البيانات فقط خلال مهلة الطعن وما يكفي بعدها لإعادة بناء الحالة. ولهذا السبب جاءت ترقية Dencun بنوع جديد من المعاملات يحمل كتلاً من البيانات تسمى Blobs، كما يشرح [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844?ref=arabroot.io) و[صفحة Danksharding في ethereum.org](https://ethereum.org/en/roadmap/danksharding/?ref=arabroot.io): البيانات في الـ Blob لا يستطيع EVM أن يقرأها، ولها سوق رسوم منفصل عن الغاز العادي، وتحذف من العقد تلقائياً بعد 4096 حقبة، أي قرابة 18 يوماً، وتذكر الصفحة أن هذا المقترح طبق على الشبكة الرئيسية مع ترقية Dencun في مارس 2024\. ونجرب ذلك بإرسال نفس بيانات الدفعة كـ Blob بأداة `cast`:

```bash
$A cast send 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC --blob --path /tmp/l2/batch.bin --private-key $K \
  | grep -E '^(gasUsed|blobGasUsed|blobGasPrice|effectiveGasPrice|status|type|transactionHash) '
```

والمخرج سوف يكون كما يلي:

```text
effectiveGasPrice    312471439
gasUsed              21000
status               1 (success)
transactionHash      0xb611998f28ea69ef48b7b1d3ef58811f464981db39fb41fe249ce40fa8f7ad6b
type                 3
blobGasPrice         1
blobGasUsed          131072
```

في الإعداد أعلاه لاحظ التالي:

- نوع المعاملة `3`، وهو نوع معاملات الـ Blob، والغاز العادي 21,000 فقط، أي تكلفة معاملة بسيطة، لأن البيانات لم تدخل تخزين أي عقد ولم ينفذها EVM.
- غاز الـ Blob المستخدم 131,072، وهو حجم Blob كامل بالبايت، رغم أن بياناتنا 300 بايت فقط، فالـ Blob يدفع كاملاً، ولهذا السبب تجمع الطبقات الثانية معاملات كثيرة حتى تملأ الـ Blob قبل أن ترسله.
- سعر غاز الـ Blob 1 في شبكتنا المحلية لأنه لا يوجد طلب عليه، وعلى الشبكة الرئيسية يرتفع وينخفض بحسب الطلب على مساحة الـ Blobs بشكل مستقل عن غاز الكتل العادي.

والصورة التالية تضع الأرقام الثلاثة جنباً إلى جنب:

![ثلاثة أشرطة: 100 تحويل مستقل على L1 بغاز 2,100,000، ودفعة واحدة كبيانات استدعاء بغاز 147,939 أي قرابة 1,479 لكل تحويل، ونفس البيانات في Blob بغاز 21,000 مع 131,072 من غاز الـ Blob بسعر مستقل، وتحتها ثلاث بطاقات: لماذا تنخفض التكلفة، وBlob منذ Dencun تحذف بعد قرابة 18 يوماً، والثمن الثقة بالمشغل والجسر وآلية الطعن](https://arabroot.io/content/images/2026/10/layer2-scaling-01-cost.webp)

الطبقة الثانية تدفع تكلفة السلسلة الأم مرة واحدة عن دفعة كاملة، والـ Blobs جعلت هذه المرة أرخص

وقد يتساءل البعض: إذا كانت البيانات تحذف بعد 18 يوماً، فكيف يعيد أحد بناء الحالة بعد سنة؟ والإجابة أن الحذف من عقد الإجماع لا يعني أن البيانات تختفي من العالم، فالمشغلون والمراقبون والمستخدمون يستطيعون أن يحفظوها، والمطلوب من السلسلة الأم أن تضمن أنها كانت متاحة للجميع خلال المدة الكافية للطعن، وبعدها تكون الحالة قد أصبحت نهائية ويكفي أن يحفظ أي طرف نسخة. وهذا الفرق بين «توفر البيانات» و«حفظ البيانات للأبد» هو ما سمح بخفض التكلفة. أما الحلول التي تحفظ البيانات خارج السلسلة الأم تماماً، عند لجنة من الحافظين، فهي أرخص، ولكنها تضيف ثقة بهذه اللجنة، لأنها إذا حجبت البيانات لم يستطع أحد أن يطعن.

## مسار معاملة على الطبقة الثانية، وثلاث درجات من النهائية

من وجهة نظر المستخدم الذي ينقل نقاطه لصديقه، المعاملة على الطبقة الثانية تمر بثلاث مراحل، ولكل مرحلة درجة مختلفة من الثقة، ومن المهم أن يعرف فريق المنتج أي مرحلة يعرضها على الشاشة كـ «تم». المرحلة الأولى أن يستقبل المشغل المعاملة ويعطيها ترتيباً ويرد على التطبيق خلال أقل من ثانية، وهذا تأكيد لين Soft Confirmation يعتمد على وعد المشغل وحده، فإذا توقف المشغل قبل أن ينشر الدفعة قد لا تظهر المعاملة أبداً. والمرحلة الثانية أن تنشر الدفعة التي فيها المعاملة على السلسلة الأم، فتصبح بياناتها متاحة للجميع، ولا يستطيع المشغل بعدها أن يغير ترتيبها، وإنما يستطيع فقط أن ينشر جذراً خاطئاً يطعن فيه. والمرحلة الثالثة أن تنتهي مهلة الطعن، أو يتحقق إثبات الصحة في الطبقات التي تستخدمه، فتصبح النتيجة نهائية بقدر نهائية السلسلة الأم نفسها.

وهذا التدرج مفيد عملياً: نقل النقاط بين صديقين يكفيه التأكيد اللين، لأن المبلغ صغير والخطر محدود، أما سحب رصيد كبير إلى الشبكة الرئيسية أو بيعه في منصة أخرى فينتظر النهائية الكاملة. والخطأ الشائع أن يعرض التطبيق كل شيء كـ «نهائي» من المرحلة الأولى، ثم يفاجأ الفريق بحالات نادرة لم تكتمل فيها المعاملة، تماماً كما رأينا مع المعاملة في الطابور قبل الكتلة في مقال كيف يعمل Bitcoin.

## من أين تأتي رسوم الطبقة الثانية؟

الرسوم التي يدفعها المستخدم على الطبقة الثانية مكونة عادة من جزأين: رسوم التنفيذ على الطبقة الثانية نفسها، وهي صغيرة لأن المشغل ينفذ على أجهزته، وحصة المعاملة من تكلفة نشر الدفعة على السلسلة الأم، وهي الجزء الأكبر غالباً. وفي تجربتنا كانت تكلفة النشر كبيانات استدعاء قرابة 1,479 غاز لكل تحويل، ولو كانت الدفعة ألف تحويل لانخفض نصيب كل تحويل من التكلفة الثابتة للمعاملة، وبقي نصيبه من البيانات نفسها. لذلك تضغط الطبقات الثانية البيانات بكل طريقة ممكنة، وتنتظر أحياناً حتى تمتلئ الدفعة قبل نشرها، وهذا يفسر لماذا يتأخر النشر إلى السلسلة الأم دقائق أحياناً بينما التأكيد اللين فوري.

ومنذ الـ Blobs أصبح الجزء الثاني يعتمد على سعر مساحة الـ Blob، وهو سوق مستقل عن الغاز العادي، فإذا ازدحمت الكتل العادية لم ترتفع بالضرورة رسوم الطبقات الثانية، وإذا ازدحمت مساحة الـ Blobs ارتفعت كلها معاً. ولهذا السبب لا تبن تقدير تكلفة برنامج الولاء على رقم رسوم شاهدته يوماً واحداً، وإنما على متوسط مدة طويلة مع هامش لأيام الازدحام، تماماً كما قلنا عن رسوم الغاز في مقال الاختبار من خمسة أسئلة.

وهناك جزء ثالث لا يظهر في فاتورة المستخدم ولكنه يهمك عند تقييم أي طبقة: هامش المشغل نفسه. فالمشغل يجمع الرسوم من المستخدمين، ويدفع تكلفة النشر على السلسلة الأم، والفرق دخله، وهو أيضاً من يرتب المعاملات داخل الطبقة، فيستطيع أن يستفيد من ترتيبها بنفس طرق القيمة القابلة للاستخراج التي شرحناها في مقال التمويل اللامركزي. لذلك عندما تقرأ أن طبقة ما رسومها قريبة من الصفر، اسأل من يدفع الفرق، وهل هو دعم مؤقت سينتهي، أم تكلفة حقيقية منخفضة بفضل الضغط والـ Blobs.

## إثبات الصحة ZK Rollup

الطبقة المتفائلة تفترض الصحة وتنتظر من يثبت العكس، أما الطبقة بإثبات الصحة فتثبت الصحة مقدماً: المشغل ينفذ الدفعة، ثم يولد إثباتاً رياضياً قصيراً يسمى إثبات المعرفة الصفرية Zero-Knowledge Proof، يقول إن الانتقال من الجذر السابق إلى الجذر الجديد بهذه البيانات صحيح حسب قواعد البرنامج، ويرفقه بالدفعة، وعقد على السلسلة الأم يتحقق من الإثبات قبل أن يقبل الجذر. وكما تشرح [صفحة ZK Rollups في ethereum.org](https://ethereum.org/en/developers/docs/scaling/zk-rollups/?ref=arabroot.io) لا توجد هنا مهلة طعن، فالسحب إلى السلسلة الأم ممكن بعد التحقق من الإثبات، والجذر الكاذب لا يقبل أصلاً.

والثمن في مكان آخر: توليد الإثبات حساب ثقيل يحتاج أجهزة قوية ووقتاً، والدوائر الرياضية التي تمثل قواعد التنفيذ معقدة جداً، وخطأ فيها قد يسمح بإثبات صحيح لانتقال خاطئ، أي أن الثقة تنتقل من «مراقب أمين واحد» إلى «صحة نظام الإثبات وتطبيقه». وفي تجربتنا كان التحقق من الدفعة يعني إعادة تنفيذ مئة تحويل على السلسلة الأم بغاز 256,455، أما التحقق من إثبات فتكلفته شبه ثابتة مهما كبرت الدفعة، وهذا هو ما يجعل الإثبات جذاباً للدفعات الكبيرة جداً. والجدول التالي يضع الأنواع جنباً إلى جنب مع حلين آخرين للتوسع:

![جدول من أربعة صفوف: Optimistic Rollup بإثبات الاحتيال والبيانات على L1 والسحب بعد قرابة أسبوع وثقة بمراقب أمين، وZK Rollup بإثبات صحة رياضي والبيانات على L1 والسحب بعد التحقق وثقة بصحة نظام الإثبات، وقنوات الحالة بتوقيعات الطرفين والبيانات عندهما والسحب عند الإغلاق وثقة بالبقاء متصلاً كما في Lightning، والسلسلة الجانبية بإجماعها الخاص والبيانات عليها والسحب عبر جسر وثقة بالمدققين والجسر بالكامل](https://arabroot.io/content/images/2026/10/layer2-scaling-04-types.webp)

الفرق الحقيقي بين الحلول: كيف تثبت الصحة، وأين البيانات، وبمن تثق

ولاحظ الصف الأخير: السلسلة الجانبية Sidechain شبكة مستقلة لها مدققون وإجماع خاص بها، ترتبط بالسلسلة الأم بجسر، وأمانها يأتي من مدققيها وليس من السلسلة الأم، فإذا اتفق أغلب مدققيها على سرقة الجسر لا يوجد طعن يوقفهم. لذلك لا تعد طبقة ثانية بالمعنى الدقيق، وإن كانت المواقع التسويقية تخلط بينهما كثيراً. وقنوات الحالة هي فكرة Lightning التي رأيناها في مقالها، وتصلح لطرفين بينهما تعامل متكرر، ولا تصلح لتطبيق عام فيه آلاف المستخدمين يتعاملون مع بعضهم بشكل عشوائي.

## ما الذي يتغير على المطور؟

أغلب الطبقات الثانية الكبيرة على Ethereum تشغل نفس آلة EVM أو نسخة قريبة جداً منها، فالعقود المكتوبة بلغة Solidity تعمل عليها غالباً دون تعديل، والأدوات نفسها مثل Foundry تعمل بتغيير عنوان RPC فقط، وهذا ما جعل الانتقال إليها سهلاً. ولكن هناك فروق يجب أن يعرفها فريق برنامج الولاء قبل أن يفترض أن كل شيء واحد: لكل طبقة رقم شبكة Chain ID مختلف، فالتوقيع لشبكة لا يصلح لأخرى كما رأينا في المقال الأول، وعناوين الرموز المعروفة تختلف بين الشبكات، فرمز عملة مستقرة على طبقة ثانية قد يكون نسخة منقولة عبر جسر وليس الرمز الأصلي، وتقدير الرسوم يحتاج حساب الجزأين اللذين ذكرناهما وليس الغاز وحده، ورقم الكتلة والوقت داخل العقد لهما معنى يختلف أحياناً عن السلسلة الأم.

والفرق الأهم ليس في الكود، وإنما في التصميم حول النهائية: أي منطق في نظامك يعتبر حدثاً على الطبقة الثانية نهائياً، مثل إصدار مكافأة مادية في المقهى مقابل نقاط، يجب أن يقرر أي درجة من الدرجات الثلاث يكفيه، ويجب أن يتعامل مع احتمال أن تتأخر الدفعة أو أن يتوقف المشغل، ويجب أن يعرف ماذا يفعل إذا ألغيت دفعة بعد طعن ناجح، فكل ما بني عليها يلغى معها.

### طبقة ثانية عامة، أم طبقة خاصة بتطبيقك؟

بعد أن رأيت أن طبقة مصغرة تكتب في مئة سطر، قد يسأل أحد الفريق: لماذا لا نبني طبقتنا الخاصة لبرنامج الولاء؟ وهناك أطر جاهزة تسمح بإطلاق طبقة خاصة بتطبيق واحد، ولكن تذكر ما الذي يحمي المستخدمين فيها: مراقب مستقل يطعن، وطريق للخروج عبر السلسلة الأم، وبيانات منشورة، وكل ذلك يحتاج من يشغله ويراقبه ويحدثه طوال الوقت. فإذا كانت الشركة ستشغل المشغل والمراقب والجسر بنفسها، يعود السؤال الذي بدأنا به مقال الاختبار من خمسة أسئلة: هل أضافت السلسلة ضماناً حقيقياً للعملاء، أم أنها قاعدة بيانات مكلفة يشغلها نفس الطرف؟ والطبقة العامة المستخدمة على نطاق واسع لها مراقبون وأدوات وتاريخ تشغيل، أما الطبقة الخاصة فيجب أن تبني كل هذا من الصفر.

## أين تثق عندما تستخدم طبقة ثانية؟

الحساب والطعن والإثبات تحمي صحة الحالة، ولكن الطبقة الثانية الحقيقية لها نقاط تشغيلية يملكها أشخاص، وهذه هي ما يجب أن يسأل عنه فريق برنامج الولاء قبل أن يختار:

![خمس بطاقات: المشغل الذي يرتب المعاملات وقد يؤخرها أو يتوقف، ومفاتيح الترقية التي تغير العقود على L1، والجسر الذي يحفظ كل الأموال المودعة وهو أكبر هدف كما في سرقتي جسري Harmony وNomad سنة 2022، وتوفر البيانات على L1 أم عند لجنة، وإثبات الصحة هل هو مفعل فعلاً ومن يستطيع تقديمه](https://arabroot.io/content/images/2026/10/layer2-scaling-05-trust.webp)

نقاط الثقة التشغيلية لا تظهر في رقم الرسوم، وتظهر في عقود الشبكة ووثائقها

- **المشغل Sequencer**: في أغلب الطبقات الثانية اليوم يوجد مشغل واحد يستقبل المعاملات ويرتبها، فيستطيع أن يؤخر معاملة أو يتجاهلها أو يتوقف كله. والسؤال المهم: هل يوجد طريق يسمح للمستخدم أن يجبر معاملته عبر السلسلة الأم إذا تجاهله المشغل؟ وفي تجربتنا لا يوجد، فالمشغل وحده يستطيع النشر، وهذا يعني أنه يستطيع أن يحبس الجميع.
- **مفاتيح الترقية**: عقود الطبقة الثانية على السلسلة الأم، وخاصة الجسر وعقد الطعن، كثيراً ما تكون قابلة للترقية كما شرحنا في مقال التمويل اللامركزي، ومن يملك الترقية يستطيع نظرياً أن يغير القواعد كلها، فاسأل من يملكها، وهل توجد مهلة بين إعلان الترقية وتنفيذها تكفي المستخدمين ليخرجوا إذا لم يوافقوا.
- **الجسر Bridge**: العقد الذي يحفظ على السلسلة الأم كل الأموال التي أودعها المستخدمون في الطبقة الثانية، ولذلك هو أكبر هدف للمهاجمين في كل هذا المجال. وفي 8 أغسطس 2022 ذكرت وزارة الخزانة الأمريكية في [بيانها عن عقوبات Tornado Cash](https://home.treasury.gov/news/press-releases/jy0916?ref=arabroot.io) أن الخدمة استخدمت لغسل أموال من سرقة جسر Harmony في 24 يونيو 2022 وسرقة Nomad في 2 أغسطس 2022، وهذان جسران بين شبكات مختلفة، والدرس واحد: الجسر برنامج يحمل مالاً كثيراً في مكان واحد، وخطأ فيه أو في مفاتيح من يشغله يخسر كل شيء.
- **توفر البيانات**: هل البيانات على السلسلة الأم كبيانات استدعاء أو Blobs، فيستطيع أي شخص إعادة البناء، أم عند لجنة خارجها؟
- **حالة إثبات الصحة**: بعض الطبقات أطلقت قبل أن يكتمل نظام الطعن أو الإثبات فيها، أو يقتصر تقديمه على جهات مسموح لها، فاسأل هل يستطيع أي شخص أن يطعن فعلاً اليوم.

ولاحظ أن تصميمنا المصغر يمر على كل هذه النقاط: مشغل واحد، وطعن بدلاً من الإثبات، وبيانات على السلسلة الأم، وضمان واحد بسيط. والفرق بينه وبين طبقة حقيقية ليس في الفكرة، وإنما في عشرات التفاصيل التي تجعل هذه الفكرة آمنة تحت هجوم حقيقي، مثل إلغاء كل الدفعات اللاحقة عند ثبوت الغش، وتقسيم الطعن على خطوات حتى لا يحتاج إعادة تنفيذ دفعة كاملة في معاملة واحدة، ومنع المشغل من تأخير الطعون.

## هل يحتاج برنامج الولاء طبقة ثانية؟ جدول قرار

قبل الجدول، ارجع إلى اختبار الأسئلة الخمسة في مقال هل تحتاج بلوكتشين، فبرنامج ولاء تشغله شركة واحدة لعملاء مقهى قد لا يحتاج سلسلة عامة أصلاً، وسجل بسلسلة هاش ونقاط تحقق موقعة ينشر هاشها اليومي على السلسلة الأم قد يعطي العملاء ضماناً كافياً بتكلفة معاملة واحدة في اليوم. وإذا كان الجواب أن السلسلة العامة مطلوبة فعلاً، فهذه الأسئلة:

| السؤال                                                       | إذا كانت الإجابة   | فالخيار                                                  |
| ------------------------------------------------------------ | ------------------ | -------------------------------------------------------- |
| هل المعاملات كثيرة وصغيرة القيمة؟                            | نعم                | طبقة ثانية أو قناة، والسلسلة الأم وحدها مكلفة            |
| هل يحتاج المستخدم سحباً سريعاً إلى السلسلة الأم؟             | نعم                | إثبات صحة، أو قبول خدمة سحب سريع كطرف إضافي              |
| هل يستطيع الفريق أو طرف مستقل أن يراقب ويطعن؟                | لا                 | لا تعتمد على أمان متفائل لم يراقبه أحد                   |
| من يملك مفاتيح الترقية والمشغل؟                              | جهة واحدة دون مهلة | اعتبرها ثقة بتلك الجهة، وقارنها بقاعدة بيانات تشغلها أنت |
| هل البيانات على السلسلة الأم؟                                | لا، عند لجنة       | ثقة إضافية باللجنة، مقابل رسوم أقل                       |
| ما موقف المستشار القانوني من رموز قابلة للتحويل بين العملاء؟ | غير واضح           | توقف قبل أي شيء تقني                                     |

## أخطاء شائعة

- **«الطبقة الثانية آمنة مثل Ethereum»**: أمانها يعتمد على آلية الإثبات وتوفر البيانات والمشغل ومفاتيح الترقية، وكل منها قد يكون أضعف من السلسلة الأم.
- **الخلط بين السلسلة الجانبية والطبقة الثانية**: السلسلة الجانبية لها مدققون خاصون بها، وأمانها لا يأتي من السلسلة الأم.
- **نسيان مهلة السحب**: في الطبقة المتفائلة الخروج إلى السلسلة الأم يأخذ قرابة أسبوع، وهذا يجب أن يكون في تجربة المستخدم من البداية.
- **اختلاف الترميز بين المكونات**: رأينا جذراً صادقاً يبدو كاذباً لأن Python وSolidity رمزا الأرصدة بطريقتين، وفي النظام الحقيقي هذا يعني طعوناً خاطئة أو قبول أخطاء.
- **الاعتماد على جسر لا تعرف من يشغله**: الجسر هو المكان الذي يتجمع فيه المال، فاقرأ عقوده ومفاتيحه قبل أن ترسل إليه شيئاً.

## إزالة التجربة

```bash
docker rm -f -v lab-l2-anvil
docker image rm ghcr.io/foundry-rs/foundry:v1.8.6
```

## تدريب عملي

1. عدل `sequencer.py` ليجعل الدفعة ألف تحويل، وقس غاز النشر، واحسب التكلفة لكل تحويل، ثم جرب الطعن وقس غازه، وفسر لماذا يحتاج التصميم الحقيقي تقسيم الطعن على خطوات.
2. أضف إلى `MiniRollup.sol` دالة تسمح لأي مستخدم أن يرسل تحويلاً مباشرة عبر السلسلة الأم يجب على المشغل أن يضمه في دفعته التالية، وفكر كيف يتحقق العقد من ذلك.
3. أرسل نفس البيانات في Blob مرتين متتاليتين، وراقب هل يتغير `blobGasPrice` على Anvil.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- السلسلة الأم محدودة لأن كل عقدة تنفذ كل شيء، والطبقة الثانية تنقل التنفيذ خارجها وتبقي البيانات والنتيجة عليها.
- في تجربتنا كلفت مئة تحويل كدفعة واحدة 147,939 غاز مقابل 2,100,000 على الأقل كتحويلات مستقلة، والـ Blob جعل البيانات تكلف 21,000 غاز مع غاز Blob بسعر منفصل.
- الطبقة المتفائلة تقبل الجذر وتعطي مهلة للطعن، ورأينا الطعن يكشف مشغلاً كاذباً ويأخذ ضمانه، ورأينا الجذر الكاذب يصبح نهائياً عندما انتهت المهلة دون طعن.
- الطبقة بإثبات الصحة تثبت الصحة مقدماً، فلا مهلة للسحب، والثقة تنتقل إلى صحة نظام الإثبات.
- الـ Blobs منذ Dencun بيانات رخيصة لا يقرؤها EVM وتحذف بعد قرابة 18 يوماً، لأن المطلوب توفر البيانات خلال المهلة وليس حفظها للأبد عند كل عقدة.
- المشغل ومفاتيح الترقية والجسر وتوفر البيانات نقاط ثقة يجب أن تعرفها قبل البناء، والجسور كانت أهدافاً لسرقات موثقة.

[![](https://arabroot.io/content/images/2026/10/need-blockchain-test-cover-cover.webp)دليلهل تحتاج بلوكتشين أم قاعدة بيانات؟ اختبار من خمسة أسئلةخمسة أسئلة تحدد هل يحتاج مشروعك بلوكتشين، مع تجربة تبني البديل الأرخص في PostgreSQL وتقيس تكلفة العقد الذكي.](https://arabroot.io/articles/%D9%87%D9%84-%D8%AA%D8%AD%D8%AA%D8%A7%D8%AC-%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D8%A7%D9%85-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D9%85%D9%86-%D8%AE%D9%85%D8%B3%D8%A9-%D8%A7%D8%B3%D8%A6%D9%84%D8%A9/)[![](https://arabroot.io/content/images/2026/10/lightning-network-cover-cover.webp)دليلشبكة Lightning: مدفوعات Bitcoin السريعة من الداخلكيف تعمل قنوات Lightning والدفع عبر الوسطاء، مع شبكة من ثلاث عقد LND تفتح قنوات وتدفع وتغلق، وما الذي يعنيه تشغيلها.](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A9-lightning-%D9%85%D8%AF%D9%81%D9%88%D8%B9%D8%A7%D8%AA-bitcoin-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9%D8%A9/)[![](https://arabroot.io/content/images/2026/10/defi-basics-cover-cover.webp)دليلالتمويل اللامركزي DeFi: صناديق السيولة ومخاطرهانبني صندوق سيولة وعقد إقراض بأيدينا ونرى بالأرقام أثر السعر والخسارة غير الدائمة وهجوم الساندويتش والتلاعب بالأسعار.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D9%85%D9%88%D9%8A%D9%84-%D8%A7%D9%84%D9%84%D8%A7%D9%85%D8%B1%D9%83%D8%B2%D9%8A-defi-%D8%A7%D9%84%D9%85%D9%86%D8%B5%D8%A7%D8%AA-%D8%A7%D9%84%D8%A7%D9%84%D9%8A%D8%A9-%D9%88%D8%B5%D9%86%D8%A7%D8%AF%D9%8A%D9%82-%D8%A7%D9%84%D8%B3%D9%8A%D9%88%D9%84%D8%A9-%D9%88%D9%85%D8%AE%D8%A7%D8%B7%D8%B1%D9%87%D8%A7/)[![](https://arabroot.io/content/images/2026/10/blockchain-basics-cover-cover.webp)دليلكيف يعمل البلوكتشين Blockchain ومتى لا تحتاجهما المشكلة التي يحلها البلوكتشين أصلاً وكيف يعمل من الداخل، ومتى تكون قاعدة البيانات العادية هي الحل الأصح.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D9%88%D9%85%D8%AA%D9%89-%D9%84%D8%A7-%D8%AA%D8%AD%D8%AA%D8%A7%D8%AC%D9%87/)[![](https://arabroot.io/content/images/2026/10/consensus-cover-cover.webp)دليلكيف تتفق شبكة البلوكتشين؟ إثبات العمل وإثبات الحصةكيف تتفق آلاف العقد التي لا يثق بعضها ببعض على سجل واحد، ولماذا يكلف الغش كهرباء في شبكة ومالاً محجوزاً في أخرى.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A7%D8%AC%D9%85%D8%A7%D8%B9-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D8%A7%D8%AB%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%88%D8%A7%D8%AB%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D8%AD%D8%B5%D8%A9/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، واختبار التجارب على Docker Engine 29.8.0 مع Foundry v1.8.6 وSolc 0.8.30، وPython 3.13.16 ومكتبة pycryptodome 3.23.0.