عندما تحول مبلغاً من حسابك إلى حساب صديقك فأنت لا ترسل له شيئاً فعلياً، وإنما يقوم البنك بتعديل سطرين في قاعدة بياناته: ينقص رصيدك ويزيد رصيده، وأنت تقبل هذه النتيجة لأنك تثق بالبنك، وتثق أن أحداً في داخله لن يعدل السجل غداً، وأن هناك جهة رقابية تراجع عمله. ولكن ماذا لو كانت هناك عدة جهات تحتاج سجلاً واحداً تتفق عليه، ولا تقبل أي منها أن تملكه جهة أخرى؟ هذه هي المشكلة التي جاء البلوكتشين Blockchain ليحلها، وفهمها هو الذي يجعلك تعرف متى يفيدك ومتى يكون مجرد قاعدة بيانات بطيئة ومكلفة.
وهذا المقال تعليمي تقني فقط، ولا يتضمن أي نصيحة استثمارية أو مالية أو توقعاً لأسعار أي عملة رقمية. وسوف نبني الفكرة من الصفر كما يبني المطور أي نظام: نبدأ بالمشكلة، ثم نجرب الحلول البسيطة ونرى لماذا تفشل، ثم نضيف لبنة بعد لبنة حتى نصل إلى البلوكتشين، ونجرب كل لبنة بكود حقيقي ومخرجات حقيقية.
وسوف نناقش في هذا المقال ما يلي:
- المشكلة الأصلية: سجل مشترك دون مالك مركزي، ومشكلة الإنفاق المزدوج Double Spending.
- لبنات البناء خطوة خطوة: دالة الهاش SHA-256، وسلسلة الكتل، وشجرة Merkle، والتوقيع الرقمي، والعنوان.
- كيف يتفق غرباء على سجل واحد: مشكلة الجنرالات البيزنطيين، وإثبات العمل Proof of Work، وإثبات الحصة Proof of Stake، والإجماع في الشبكات الخاصة.
- التاريخ بتواريخ موثقة، وأنواع الشبكات، والعقود الذكية ومخاطرها.
- متى لا تحتاج بلوكتشين، باختبار مبني على إرشادات NIST.
- تجارب عملية: سلسلة هاش في Python، وشبكة Ethereum محلية مع Anvil وcast وforge، وBitcoin Core في وضع regtest.
- خريطة الأدوات مفتوحة المصدر ورخصها، وأساسيات الأمان.
المشكلة: سجل مشترك دون مالك
لنفرض أن أربع جهات، بنكين وشركتين، تريد سجلاً واحداً يبين من يملك ماذا. والحل الأول الذي يخطر على البال هو قاعدة بيانات مركزية Central Database يشغلها طرف واحد ويكتب فيها الجميع من خلال واجهة API، وهذا الحل يعمل فعلاً وهو ما تعمل به البنوك اليوم، ولكن شرطه أن يثق الجميع بالمشغل Operator، والسبب أن المشغل يستطيع أن يعدل أي سطر أو يحذفه، ويستطيع أن يرفض عمليات طرف لا يحبه، وأنت لا ترى إلا ما يعرضه عليك. فإذا لم توجد جهة يقبلها الجميع، أو كانت الجهات متنافسة، تتوقف القصة هنا.
والحل الثاني أن يحتفظ كل طرف بنسخته الخاصة من السجل، ويرسل كل عملية إلى الآخرين ليضيفوها. وهذا الحل يفشل بسرعة، لأن الرسائل تصل بترتيب مختلف إلى كل طرف، وبعض الأطراف قد تكذب، فتصبح عندك أربع نسخ مختلفة ولا أحد يعرف أيها الصحيحة. والمثال الأشهر على ذلك هو الإنفاق المزدوج Double Spending: النقود الورقية لا يمكن أن تعطيها لشخصين، أما الوحدة الرقمية فهي في النهاية ملف أو سطر يمكن نسخه، فتستطيع Alice أن ترسل نفس الوحدة إلى Bob وإلى Carol في نفس اللحظة، فتسجلها نسخة Bob له وتسجلها نسخة Carol لها، ولا توجد جهة تقول أي العمليتين جاءت أولاً. وهذه المشكلة هي التي وصفها Satoshi Nakamoto في بداية ورقة Bitcoin، وكان الحل المعروف قبلها هو وجود طرف موثوق في المنتصف، وهو ما أراد التخلص منه.
إذن ما نحتاجه بالضبط أربعة أشياء: ألا يتغير الماضي دون أن يلاحظ أحد، وألا يصرف أحد إلا ما يملكه، وأن تكون عند كل طرف نسخة كاملة يتحقق منها بنفسه، وأن يتفق الجميع على ترتيب واحد للعمليات. والصورة التالية تبين المشكلة والمتطلبات الأربعة، وكل متطلب منها له لبنة سوف نبنيها في الأقسام التالية:

لبنات البناء خطوة خطوة
دالة الهاش SHA-256: بصمة البيانات
دالة الهاش Hash Function هي خوارزمية تأخذ أي مدخل بأي طول، رسالة قصيرة أو ملف بحجم عدة جيجابايت، وترجع مخرجاً بطول ثابت يسمى الهاش أو ال Digest، وفي SHA-256 يكون المخرج 256 بت دائماً، أي 64 حرفاً بترميز Hex. وهي دالة باتجاه واحد One-Way، أي أنك تستطيع حساب الهاش لأي مدخل بسهولة، ولكنك لا تستطيع الرجوع من الهاش إلى المدخل الأصلي، ولا تستطيع عملياً أن تجد مدخلين مختلفين لهما نفس الهاش، وهذه الخاصية تسمى مقاومة التصادم Collision Resistance. وإذا كنت تخلط بين الهاش والترميز والتشفير فقد شرحنا الفرق بينها في مقال الترميز.
والخاصية التي تهمنا هنا أكثر من غيرها هي أن أي تغيير في المدخل ولو كان حرفاً واحداً يعطي هاشاً مختلفاً تماماً، وهذا يسمى تأثير الانهيار Avalanche Effect. والمثال التالي يحسب الهاش لعمليتين لا يختلفان إلا في الرقم الأخير، ثم يحسب كم بتاً تغير بينهما:
docker run --rm --name lab-chain-py -v "$PWD:/lab" -w /lab python:3.13-slim bash -c "
echo -n 'Alice pays Bob 10' | sha256sum
echo -n 'Alice pays Bob 11' | sha256sum
python bits.py"وملف bits.py في نفس المجلد يقارن الهاشين بتاً ببت:
import hashlib
a = hashlib.sha256(b"Alice pays Bob 10").digest()
b = hashlib.sha256(b"Alice pays Bob 11").digest()
diff = sum(bin(x ^ y).count("1") for x, y in zip(a, b))
print(f"{diff} of 256 bits changed")والمخرج سوف يكون كما يلي:
69ad18dfd1ac4b3d711b02ee62caa00a82f0f67cc11ebacdd0c0ede0d8b6eaed -
83e970b5a467ce2dea0a573ee54380252a1d193b2625e2c29ad43ad6600990aa -
128 of 256 bits changedلاحظ أن تغيير رقم واحد غير نصف البتات بالضبط في هذه التجربة، ولا يوجد أي تشابه بين الهاشين يدلك على أن المدخلين متقاربان، وهذا ما يجعل الهاش بصمة للبيانات: إذا تطابق الهاش فالبيانات لم تتغير، وإذا اختلف فقد تغيرت. وهذه هي نفس الفكرة التي تستخدمها عندما تقارن هاش ملف ISO بعد تحميله بالقيمة المنشورة في موقع التوزيعة.
سلسلة الكتل: كل كتلة تحمل هاش ما قبلها
الآن نجمع العمليات في كتل Blocks، ونضع داخل كل كتلة هاش الكتلة التي قبلها، ثم نحسب هاش الكتلة نفسها من كل محتواها بما فيه هاش السابقة. وبهذا الشكل تصبح الكتل سلسلة، وكل حلقة فيها مربوطة بالتي قبلها، ومن هنا جاء الاسم. والكود التالي يبني ثلاث كتل في Python دون أي مكتبة خارجية، ثم يتحقق من السلسلة، ثم يعدل الكتلة الأولى ويتحقق مرة أخرى. احفظ الملف باسم chain.py:
import hashlib, json
def block_hash(block):
# hash the block's content in a fixed order (sort_keys) so the same block always gives the same hash
data = json.dumps(block, sort_keys=True, ensure_ascii=False).encode()
return hashlib.sha256(data).hexdigest()
def make_block(index, transactions, prev_hash):
block = {"index": index, "transactions": transactions, "prev_hash": prev_hash}
block["hash"] = block_hash(block)
return block
def verify(chain):
for i, block in enumerate(chain):
content = {k: v for k, v in block.items() if k != "hash"}
if block_hash(content) != block["hash"]:
print(f"block {i}: content does not match its hash")
return False
if i > 0 and block["prev_hash"] != chain[i - 1]["hash"]:
print(f"block {i}: prev_hash does not match the hash of block {i - 1}")
return False
print("chain is valid")
return True
chain = [make_block(0, ["genesis"], "0" * 64)]
chain.append(make_block(1, ["Alice -> Bob: 10"], chain[-1]["hash"]))
chain.append(make_block(2, ["Bob -> Carol: 4"], chain[-1]["hash"]))
for b in chain:
print(b["index"], b["transactions"], "prev:", b["prev_hash"][:16], "hash:", b["hash"][:16])
verify(chain)
print("\n--- someone edits block 1: 10 becomes 100 ---")
chain[1]["transactions"] = ["Alice -> Bob: 100"]
verify(chain)
print("\n--- he recomputes the hash of block 1 too ---")
chain[1]["hash"] = block_hash({k: v for k, v in chain[1].items() if k != "hash"})
print("block 1 new hash:", chain[1]["hash"][:16])
verify(chain)ونشغله في Container مؤقت حتى لا نحتاج تثبيت أي شيء:
docker run --rm --name lab-chain-py -v "$PWD:/lab" -w /lab python:3.13-slim python chain.pyوالمخرج سوف يكون كما يلي:
0 ['genesis'] prev: 0000000000000000 hash: 2c487f4265902cee
1 ['Alice -> Bob: 10'] prev: 2c487f4265902cee hash: e310f52deb850901
2 ['Bob -> Carol: 4'] prev: e310f52deb850901 hash: bdde43538dd05773
chain is valid
--- someone edits block 1: 10 becomes 100 ---
block 1: content does not match its hash
--- he recomputes the hash of block 1 too ---
block 1 new hash: 1df45dde963437a8
block 2: prev_hash does not match the hash of block 1في الإعداد أعلاه لاحظ التالي:
- الكتلة الأولى في أي سلسلة لا توجد قبلها كتلة، لذلك نضع مكان هاش السابقة أصفاراً، وتسمى كتلة التكوين Genesis Block.
- قيمة
prevفي كل سطر هي نفس قيمةhashفي السطر الذي قبله، وهذا هو الرابط بين الكتل. - عندما غيرنا 10 إلى 100 لم يعد محتوى الكتلة يطابق الهاش المحفوظ فيها، فاكتشف التحقق التعديل فوراً.
- وعندما أعاد المعتدي حساب هاش الكتلة المعدلة ليخفي أثره، انكسر الرابط في الكتلة التالية لأنها ما زالت تحمل الهاش القديم
e310f52d…، ولكي يخفي التعديل عليه أن يعيد حساب كل الكتل التي بعدها. - استخدمنا
sort_keys=Trueفيjson.dumps، والسبب أن الهاش يتغير مع ترتيب الحقول، فإذا لم نثبت الترتيب قد يحسب طرفان هاشين مختلفين لنفس الكتلة.
والصورة التالية تبين السلسلة قبل التعديل وبعده بنفس القيم التي ظهرت في المخرج:

وقد يتساءل البعض: ما الذي يمنع المعتدي من إعادة حساب كل الكتل بعد الكتلة التي عدلها؟ على جهازه لا شيء، فالبرنامج أعلاه يحسب ثلاث كتل في أجزاء من الثانية. والإجابة أن السلسلة وحدها تجعل التعديل ظاهراً Tamper Evident ولا تجعله مستحيلاً، وتقرير NIST IR 8202 يستخدم نفس الوصف في القسم 7.1، ويقول إن سجلات البلوكتشين مقاومة للتعديل وتظهره، ولكنها ليست غير قابلة للتغيير بشكل مطلق. والذي يمنع إعادة الكتابة فعلاً شيئان سوف نصل إليهما: أن آلاف النسخ موجودة عند أطراف أخرى، وأن الإجماع يجعل إنتاج Production الكتلة الواحدة مكلفاً.
شجرة Merkle: هاش واحد لآلاف المعاملات
الكتلة الحقيقية فيها آلاف المعاملات وليست معاملة واحدة، ولو وضعنا هاش كل المعاملات في رأس الكتلة لأصبح كبيراً جداً. والحل هو شجرة Merkle Tree: نحسب هاش كل معاملة، ثم نحسب هاش كل زوجين معاً، ثم هاش كل زوجين من النتائج، حتى يبقى هاش واحد يسمى الجذر Merkle Root، ويوضع هذا الهاش وحده في رأس الكتلة. والمثال التالي يحسب الجذر لأربع معاملات ثم يغير معاملة واحدة، واحفظه باسم merkle.py:
import hashlib
def h(x: bytes) -> bytes:
return hashlib.sha256(x).digest()
def merkle_root(txs):
level = [h(t.encode()) for t in txs]
while len(level) > 1:
if len(level) % 2: # odd count: repeat the last one (as Bitcoin does)
level.append(level[-1])
level = [h(level[i] + level[i + 1]) for i in range(0, len(level), 2)]
return level[0].hex()
txs = ["Alice -> Bob: 10", "Bob -> Carol: 4", "Carol -> Dan: 1", "Dan -> Alice: 2"]
print("root:", merkle_root(txs))
txs[2] = "Carol -> Dan: 9"
print("root after changing one transaction:", merkle_root(txs))والمخرج سوف يكون كما يلي:
root: 17808365ef854919f416ad3d560b86fa0419c0b4df1b059e00ac3d9af57eeacc
root after changing one transaction: 8927c51c7b3c9265b07c712a386faa87dfa95b5c622fbbfca6e17b07213e180eلاحظ أن تغيير معاملة واحدة من أربع غير الجذر كله، وبالتالي يكفي أن يتطابق الجذر لتعرف أن كل المعاملات لم تتغير. والفائدة الثانية أن جهازاً خفيفاً مثل محفظة على الهاتف يستطيع أن يتأكد أن معاملته موجودة في كتلة دون أن يحمل كل معاملاتها، وإنما يحتاج فقط الهاشات التي على طريقها إلى الجذر، وهي بضع مئات من البايتات حتى لو كانت الكتلة فيها آلاف المعاملات. وسوف ترى حقل merkleroot في رأس كتلة Bitcoin الحقيقي في التجربة العملية، وحقل transactionsRoot في Ethereum.
المفتاح الخاص والتوقيع: من يملك حق الصرف؟
السلسلة تحمي الماضي، ولكنها لا تمنع أي شخص من كتابة معاملة «Alice تدفع لي 100» وإرسالها. والحل هو التوقيع الرقمي Digital Signature: لكل مستخدم زوج من المفاتيح، مفتاح خاص Private Key يبقى عنده ولا يغادر جهازه، ومفتاح عام Public Key ينشره للجميع، وما يوقعه المفتاح الخاص يتحقق منه أي شخص بالمفتاح العام، ولا يستطيع أحد أن ينتج توقيعاً صحيحاً دون المفتاح الخاص. وقد شرحنا هذه الفكرة في دقيقة في مقال الشهادات الرقمية، وهي نفس الفكرة التي تجعل مفاتيح Passkeys تقاوم التصيد. والفرق هنا أنه لا توجد جهة إصدار شهادات تربط المفتاح باسم شخص، فالمفتاح العام هو الهوية نفسها، ومن يملك المفتاح الخاص يملك الرصيد.
وBitcoin وEthereum يستخدمان المنحنى الإهليلجي secp256k1، وأشهر خوارزمية توقيع عليه هي ECDSA، ونستطيع أن نجرب ذلك بأداة OpenSSL نفسها. احفظ الملف التالي باسم sign.sh:
set -e
apt-get update -qq >/dev/null && apt-get install -y -qq openssl >/dev/null
openssl version
cd /tmp
openssl ecparam -name secp256k1 -genkey -noout -out alice.key
openssl ec -in alice.key -pubout -out alice.pub 2>/dev/null
cat alice.pub
echo -n 'Alice -> Bob: 10' > tx.txt
openssl dgst -sha256 -sign alice.key -out tx.sig tx.txt
echo "signature: $(od -An -tx1 tx.sig | tr -d ' \n' | cut -c1-48)... ($(stat -c %s tx.sig) bytes)"
openssl dgst -sha256 -verify alice.pub -signature tx.sig tx.txt
echo -n 'Alice -> Bob: 100' > tx.txt
openssl dgst -sha256 -verify alice.pub -signature tx.sig tx.txt 2>/dev/null || trueثم شغله:
docker run --rm --name lab-chain-py -v "$PWD:/lab" -w /lab python:3.13-slim bash sign.shوالمخرج سوف يكون كما يلي، والمفتاح يختلف في كل تشغيل لأنه عشوائي:
OpenSSL 3.5.7 9 Jun 2026 (Library: OpenSSL 3.5.7 9 Jun 2026)
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE+GsgSlmF24aaz4JyfskuUfmcQQHHHtlV
vjHx51N+TbIYKrgWsMMzU2FaDh4HMHB9zw9OjyMGq6EHB/EjCZT3QQ==
-----END PUBLIC KEY-----
signature: 3045022054e3a7ecb2dbf33c1648b39fb188166f448e0932... (71 bytes)
Verified OK
Verification failureفي الإعداد أعلاه لاحظ التالي:
- الأمر
openssl dgst -sha256 -signيحسب هاش المعاملة أولاً ثم يوقع الهاش، والسبب أن التوقيع يعمل على مدخل بطول ثابت، وهذا هو المكان الذي يلتقي فيه الهاش بالتوقيع. - التحقق الأول نجح لأن النص والتوقيع والمفتاح العام متطابقة، ولم نحتج المفتاح الخاص للتحقق.
- عندما غيرنا المبلغ إلى 100 واستخدمنا نفس التوقيع فشل التحقق، لأن التوقيع مربوط بهاش النص الأصلي، وبالتالي لا يستطيع أحد أن يعدل معاملة موقعة، ولا أن يكتب معاملة باسم Alice دون مفتاحها.
والصورة التالية تبين مسار المعاملة من محفظة Alice إلى أي عقدة Node في الشبكة، ومن أين يأتي العنوان:

العنوان: هاش المفتاح العام بترميز يقرؤه الإنسان
المفتاح العام طويل، لذلك لا يستخدم مباشرة كعنوان، وإنما نأخذ هاشه ثم نرمزه بشكل مناسب للنسخ والكتابة. ففي Bitcoin العناوين القديمة التي تبدأ بالرقم 1 هي هاش المفتاح العام بترميز Base58Check، وقد شرحنا هذا الترميز ومجموع التحقق الذي يكتشف الخطأ في حرف واحد خطوة خطوة في مقال الترميز، فلن نكرره هنا. والعناوين الحديثة التي تبدأ بـ bc1 تستخدم ترميز Bech32 الموصوف في BIP 173، وفي شبكة regtest التي سوف نشغلها تكون البادئة bcrt1 كما في القيمة bech32_hrp في ملف chainparams.cpp. أما في Ethereum فالعنوان كما في توثيق الحسابات هو آخر 20 بايت من هاش Keccak-256 للمفتاح العام مكتوبة بترميز Hex مع البادئة 0x. وفي الحالات الثلاث لا يمكن الرجوع من العنوان إلى المفتاح العام أو الخاص، لأن بينهما دالة هاش.
الإجماع بين غرباء
مشكلة الجنرالات البيزنطيين
بقي المتطلب الأصعب: كيف تتفق آلاف النسخ على ترتيب واحد للمعاملات، والأطراف لا يعرف بعضهم بعضاً، وبعضهم قد يكذب عمداً؟ وهذه المشكلة لها اسم منذ سنة 1982، عندما نشر Leslie Lamport وRobert Shostak وMarshall Pease ورقة The Byzantine Generals Problem في مجلة ACM Transactions on Programming Languages and Systems، وصورتها جنرالات يحاصرون مدينة ويتواصلون بالرسل فقط، وبعضهم خونة يرسلون رسائل متناقضة، والمطلوب أن يتفق المخلصون على خطة واحدة. وأثبتت الورقة أن الاتفاق بالرسائل الشفهية ممكن فقط إذا كان أكثر من ثلثي الجنرالات مخلصين. وأنظمة مثل etcd وRaft التي شرحناها في مقال الأنظمة الموزعة تفترض أن السيرفر قد يتوقف ولكنه لا يكذب، أما البلوكتشين العام فيجب أن يتحمل الكاذب أيضاً، وفوق ذلك يجب أن يتحمل أطرافاً لا تعرف عددهم، لأن أي شخص يستطيع أن يشغل آلاف العقد بأسماء مختلفة، وهذا ما يسمى هجوم Sybil. لذلك لا يصلح التصويت بعدد العقد، ونحتاج شيئاً مكلفاً لا يمكن تزويره.
إثبات العمل Proof of Work
فكرة Bitcoin أن حق إضافة الكتلة التالية لا يأتي بالتصويت، وإنما بحل لغز حسابي: على من يقوم بالتعدين Mining أن يجد رقماً اسمه Nonce بحيث يكون هاش رأس الكتلة أصغر من هدف Target معين، أي يبدأ بعدد كاف من الأصفار. ولا توجد طريقة لإيجاد هذا الرقم إلا التجربة رقماً بعد رقم، لأن الهاش لا يمكن توقعه كما رأينا، أما التحقق من الحل فهو حساب هاش واحد. والكود التالي يحاكي ذلك بشكل مبسط بعدد أصفار متزايد، واحفظه باسم pow.py:
import hashlib, time
def mine(prev_hash, data, zeros):
target = "0" * zeros
nonce = 0
start = time.perf_counter()
while True:
h = hashlib.sha256(f"{prev_hash}{data}{nonce}".encode()).hexdigest()
if h.startswith(target):
return nonce, h, time.perf_counter() - start
nonce += 1
prev = "8d2c4f3a" * 8
for zeros in range(3, 7):
nonce, h, t = mine(prev, "Bob -> Carol: 4", zeros)
print(f"zeros={zeros} nonce={nonce:>9} time={t:7.3f}s hash={h[:24]}...")
# checking the work is one hash, whatever the difficulty was
print("verify:", hashlib.sha256(f"{prev}Bob -> Carol: 4{nonce}".encode()).hexdigest() == h)والمخرج سوف يكون كما يلي، والأوقات للتوضيح فقط وتختلف من جهاز لآخر:
zeros=3 nonce= 1170 time= 0.003s hash=0008216a57180cffb7264a72...
zeros=4 nonce= 261106 time= 0.521s hash=0000d15e4291e9e5876dfc9c...
zeros=5 nonce= 832980 time= 2.161s hash=00000fe4f71956d5112c0230...
zeros=6 nonce= 5484417 time= 16.069s hash=00000026ea3a8671857fc3e5...
verify: Trueلاحظ أن كل صفر إضافي بترميز Hex يضرب عدد المحاولات المتوقع في 16، وأن الرقم الفعلي يختلف من مرة لأخرى لأنه حظ، فإيجاد الحل مكلف أما التحقق منه فسطر واحد. وفي Bitcoin يسمى هذا الهدف الصعوبة Difficulty، ويتم تعديله تلقائياً كل أسبوعين من الكتل، أي كل 2016 كتلة، حتى يبقى متوسط الوقت قرابة عشر دقائق للكتلة كما في القيمتين nPowTargetTimespan وnPowTargetSpacing في chainparams.cpp، فإذا زادت أجهزة التعدين صعب اللغز، وإذا قلت سهل.
وعندما تظهر سلسلتان متنافستان تتبنى العقد السلسلة التي فيها أكبر عمل متراكم، وبالتالي لكي يعيد المعتدي كتابة كتلة قديمة عليه أن يعيد تعدينها وتعدين كل ما بعدها، ثم يسبق الشبكة كلها التي تستمر في إضافة كتل جديدة. وهذا ممكن فقط إذا ملك أكثر من نصف قوة التعدين في الشبكة، وهذا ما يسمى هجوم 51% Attack، وفيه يستطيع أن يعيد كتابة الكتل الأخيرة ويصرف نفس المبلغ مرتين، ولكنه لا يستطيع أن يوقع معاملة باسم غيره. ولهذا السبب ينتظر الناس عدة كتل قبل أن يعتبروا الدفعة نهائية، كما يذكر تقرير NIST في القسم 7.1. والثمن الواضح لهذه الطريقة هو الكهرباء، فالأمان نفسه قائم على أن العمل مكلف.
إثبات الحصة Proof of Stake
إثبات الحصة يستبدل الكهرباء بالمال المحجوز: يودع المدقق Validator مبلغاً كضمان، وفي Ethereum يجب أن يودع 32 ETH ويشغل ثلاثة برامج هي عميل التنفيذ Execution Client وعميل الإجماع Consensus Client وعميل المدقق Validator Client كما في توثيق Ethereum. والزمن مقسم إلى خانات Slots مدة كل واحدة 12 ثانية، وفي كل خانة يختار البروتوكول مدققاً عشوائياً ليقترح الكتلة، وتصوت لجنة من المدققين Attestation على صحتها، وعندما تجتمع الأصوات الكافية تصبح الكتل نهائية Finalized. وإذا غش المدقق، بأن اقترح كتلتين في نفس الخانة أو صوت تصويتين متناقضين، يخسر جزءاً من ضمانه أو كله، وهذا يسمى الإقصاء والمصادرة Slashing، فالذي يحمي الشبكة هنا أن الغش يكلف الغاش ماله.
وقد انتقلت Ethereum من إثبات العمل إلى إثبات الحصة في الحدث المعروف باسم The Merge يوم 15 سبتمبر 2022 عند الكتلة 15,537,394 كما في السجل التقني لترقيات Ethereum، وتنقل صفحة استهلاك الطاقة عن تقديرات CCRI أن هذا الانتقال خفض استهلاك الكهرباء السنوي للشبكة بأكثر من 99.988%. والصورة التالية تضع الطريقتين جنباً إلى جنب:

الإجماع في الشبكات الخاصة
عندما يكون المشاركون معروفين ومعدودين، مثل خمسة بنوك، لا نحتاج اللغز ولا الضمان المالي، لأن هجوم Sybil غير ممكن أصلاً، فكل عقدة لها هوية معروفة. لذلك تعود الشبكات الخاصة إلى خوارزميات التصويت الكلاسيكية: Raft للأعطال العادية التي شرحناها في مقال الأنظمة الموزعة، وعائلة BFT للأطراف التي قد تكذب، وأشهرها PBFT التي نشرها Miguel Castro وBarbara Liskov في مؤتمر OSDI سنة 1999. ففي Hyperledger Fabric تكون خدمة الترتيب Ordering Service مبنية على Raft التي تتحمل الأعطال فقط، ومنذ الإصدار 3.0 يوجد خيار BFT مبني على مكتبة SmartBFT يتحمل كذب أقل من ثلث العقد كما في توثيق خدمة الترتيب، وBesu يدعم في الشبكات الخاصة QBFT وIBFT 2.0 كما في توثيقه، ويذكر أنه أوقف دعم Clique. والنتيجة سرعة أعلى ونهائية فورية بعد التصويت، ولكن بشرط أنك تعرف من يصوت، وقاعدة الثلثين هي نفس نتيجة ورقة الجنرالات، ولماذا نختار ثلاث أو خمس عقد شرحناها في قسم النصاب.
من أين جاء كل هذا؟ تواريخ موثقة
أغلب الأفكار السابقة أقدم من Bitcoin، والذي فعله Bitcoin أنه جمعها في نظام واحد يعمل. وفيما يلي أهم المحطات بمصادرها:
| التاريخ | الحدث | المصدر |
|---|---|---|
| يوليو 1982 | ورقة مشكلة الجنرالات البيزنطيين لـ Lamport وShostak وPease | الورقة |
| 1994 | Nick Szabo يعرف العقد الذكي Smart Contract بأنه بروتوكول معاملات محوسب ينفذ شروط العقد | مقال Smart Contracts |
| 31 أكتوبر 2008 | Satoshi Nakamoto ينشر ورقة Bitcoin في القائمة البريدية للتشفير | رسالة القائمة |
| 3 يناير 2009 | كتلة التكوين في Bitcoin، وفيها عنوان صحيفة The Times من ذلك اليوم | chainparams.cpp |
| 27 نوفمبر 2013 | Vitalik Buterin ينشر ورقة Ethereum | سجل الترقيات |
| 30 يوليو 2015 | إطلاق شبكة Ethereum الأولى Frontier | سجل الترقيات |
| 17 ديسمبر 2015 | Linux Foundation تعلن مشروع البلوكتشين المفتوح الذي صار Hyperledger | البيان الصحفي |
| 20 يوليو 2016 | التفرع الصلب Hard Fork بعد حادثة The DAO | سجل الترقيات |
| 15 سبتمبر 2022 | The Merge: انتقال Ethereum إلى إثبات الحصة | صفحة The Merge |
| 16 سبتمبر 2024 | إطلاق LF Decentralized Trust، وانتقلت إليها مشاريع Hyperledger Foundation | البيان الصحفي |
والطريف أن الوقت المسجل في كتلة التكوين هو 1231006505، أي 3 يناير 2009 الساعة 18:15:05 بتوقيت UTC، والنص المكتوب داخلها هو «The Times 03/Jan/2009 Chancellor on brink of second bailout for banks»، فأول كتلة في Bitcoin تحمل عنوان صحيفة عن إنقاذ البنوك.
أنواع الشبكات: عامة ومصرح بها
الشبكة العامة غير المقيدة Public Permissionless مثل Bitcoin وشبكة Ethereum الرئيسية يستطيع أي شخص أن ينضم إليها ويشغل عقدة ويقرأ كل ما فيها ويرسل المعاملات، والهوية فيها عنوان وليس اسماً، والإجماع بإثبات العمل أو الحصة. أما الشبكة المصرح بها Permissioned، وتسمى أيضاً شبكة الاتحاد Consortium عندما تجمع عدة مؤسسات، فلا يدخلها إلا من له هوية صادرة من جهة معروفة، وهي في Hyperledger Fabric شهادات X.509 تديرها خدمة العضوية Membership Service Provider، والإجماع فيها بالتصويت كما رأينا. ومن أمثلتها Hyperledger Fabric، وشبكات Besu الخاصة التي تشغل نفس آلة Ethereum الافتراضية EVM ولكن بين أعضاء معروفين.
وقد يتساءل البعض: ما الذي يعطيه البلوكتشين الخاص أكثر من قاعدة بيانات PostgreSQL منسوخة على عدة سيرفرات؟ والإجابة أن الفرق كله في من يملك السيرفرات. فإذا كانت كل العقد عند شركة واحدة فأنت تملك قاعدة بيانات منسوخة أبطأ وأعقد، والسبب أن الشركة تستطيع أن تعيد بناء السلسلة كلها متى شاءت، وبالتالي لا يضيف الهاش ولا الإجماع أي ضمان لا يعطيه سجل تدقيق Audit Log عادي. أما إذا كانت العقد موزعة على مؤسسات مستقلة، كل واحدة تشغل عقدتها وتحمل نسخة كاملة، فهنا يصبح للبلوكتشين معنى: كل معاملة موقعة من صاحبها، ولا تستطيع أي مؤسسة أن تعدل الماضي دون أن تلاحظ الأخرى، والمنطق المشترك مثل قواعد التسوية يعمل كعقد ذكي واحد يراه الجميع بدل أن يكتبه كل طرف في نظامه. والصورة التالية تبين الفرق:

العقود الذكية Smart Contracts
العقد الذكي برنامج يخزن على البلوكتشين نفسه وتنفذه كل عقدة في الشبكة، فإذا أرسلت له معاملة نفذت كل عقدة نفس الكود على نفس الحالة ووصلت إلى نفس النتيجة، ولذلك لا يحتاج الأطراف إلى الثقة بسيرفر يشغل المنطق نيابة عنهم. والفكرة قديمة كما رأينا عند Nick Szabo، وجاءت Ethereum بآلة افتراضية EVM تنفذ هذه البرامج، وأشهر لغة لكتابتها هي Solidity، وكل عملية في التنفيذ لها تكلفة بوحدة الغاز Gas يدفعها المرسل، والسبب أن الشبكة لا تستطيع أن تسمح ببرنامج يدور في حلقة لا نهائية على آلاف العقد مجاناً. وسوف ترى عقداً حقيقياً صغيراً في التجربة العملية.
والخطر الأكبر في العقود الذكية أن الكود بعد نشره لا يمكن تعديله في الأصل، فإذا كان فيه خطأ فالخطأ منشور أمام الجميع ويستطيع أي شخص أن يستغله، ولا يوجد مسؤول يستطيع أن يوقف التحويل أو يعيده كما يفعل البنك. لذلك يمر كل عقد يحمل قيمة حقيقية بمراجعة أمنية Audit، ويعتمد المطورون على مكتبات مراجعة مثل OpenZeppelin Contracts بدلاً من كتابة كل شيء من الصفر، ويكتبون اختبارات أكثر بكثير مما يكتبون لتطبيق ويب عادي.
متى لا تحتاج بلوكتشين؟
هذا هو أهم قسم في المقال، والسبب أن أغلب مشاريع البلوكتشين في المؤسسات تبدأ من السؤال الخطأ. ويصف تقرير NIST IR 8202: Blockchain Technology Overview الصادر في أكتوبر 2018 هذه المشكلة في القسم 8، فيقول إن كثيراً من المؤسسات تبدأ بسؤال «نريد أن نستخدم البلوكتشين في مكان ما، فأين؟» وإن هذا يقود إلى الإحباط، وإن الأفضل أن تفهم التقنية أولاً ثم تبحث عن الأنظمة التي تناسبها. ويذكر التقرير أن البلوكتشين قد يناسب عندما يكون هناك مشاركون كثيرون وموزعون، ولا يوجد طرف ثالث موثوق أو لا يراد وجوده، والعمل في أصله معاملات تنقل أصولاً أو معلومات بين أطراف، ويحتاج الجميع تاريخاً كاملاً مشتركاً. ثم ينقل مخطط قرار من مديرية العلوم والتقنية في وزارة الأمن الداخلي الأمريكية، ويشير إلى ورقة «Do you need a Blockchain?» من جامعة ETH Zurich، ويلاحظ أن أغلب المسارات في مخططها تنتهي بالإجابة «لا»، ويقول إن هذه النظرة النقدية هي التي يجب أن تأخذها أغلب المؤسسات.
وبناء على ذلك فإن أغلب مشاكل الأعمال تحلها قاعدة بيانات عادية بشكل أفضل وأرخص وأسرع، ولا تستخدم البلوكتشين إلا إذا اجتزت الأسئلة التالية بالترتيب:
- هل هناك عدة أطراف مستقلة تكتب في نفس السجل؟ إذا كان الكاتب واحداً فلا يوجد ما تتفق عليه، وقاعدة البيانات هي الحل.
- هل توجد جهة يثق بها الجميع، أو يمكن أن يتفقوا عليها؟ إذا وجدت فلتشغل هي قاعدة البيانات مع صلاحيات ونسخ احتياطي وسجل تدقيق، وهذا هو نموذج أغلب الأنظمة المالية اليوم.
- هل يكفي سجل تدقيق موقع لا يقبل إلا الإضافة؟ يمكنك أن تبني سلسلة هاش على سجلات قاعدة البيانات كما فعلنا في
chain.py، وترسل الهاش الأخير موقعاً لكل طرف دورياً، فيستطيع أي طرف أن يكتشف التعديل دون شبكة إجماع. - هل البيانات شخصية أو سرية أو قد يطلب منك حذفها؟ يذكر تقرير NIST أن البلوكتشين لا يعرف من عمليات CRUD إلا الإنشاء والقراءة، وأن المعاملة التي تعلم بيانات قديمة كمحذوفة تترك البيانات نفسها في السلسلة، وينبه إلى البيانات الشخصية والأنظمة التي تحكمها. وفي المملكة تخضع البيانات الشخصية لنظام حماية البيانات الشخصية PDPL، لذلك لا تضع البيانات الشخصية في السلسلة أصلاً، وضع فيها هاشاً أو مرجعاً على الأكثر، وراجع المستشار القانوني قبل أي تصميم.
- هل يجب أن يتحقق أي شخص في العالم دون إذن؟ إذا كان الجواب نعم فهذه حالة الشبكة العامة مع كل تكاليفها: البيانات علنية للأبد، ولكل معاملة رسوم، والكتابة أبطأ بكثير من قاعدة بيانات، والمفتاح الضائع لا يستعيده أحد. وإذا كان الجواب لا والأطراف مؤسسات مستقلة معروفة، فالشبكة المصرح بها هي الخيار.
والصورة التالية تجمع هذه الأسئلة في مخطط قرار:

ولاحظ أن الأداء والخصوصية تكلفتان ثابتتان وليستا عيباً في تطبيق معين، فكل عقدة تنفذ كل معاملة وتحفظها، وكل طرف يرى كل شيء إلا إذا أضفت طبقات خصوصية تزيد التعقيد، لذلك إذا وصلت إلى نهاية المخطط واخترت البلوكتشين فاختره وأنت تعرف ما تدفعه.
ابدأ بيدك: تجارب عملية
جربنا في الأقسام السابقة الهاش والسلسلة والتوقيع وإثبات العمل بملفات Python صغيرة، والآن نشغل شبكات حقيقية على جهازك. وكل ما يلي يعمل على شبكات تطوير محلية فقط، والحسابات فيها حسابات اختبار ليس لها أي قيمة حقيقية، ولا تستخدم أي مفتاح منها على شبكة حقيقية، ولا تضع مفتاحاً حقيقياً في سطر الأوامر كما نفعل هنا، والسبب أن الأمر يحفظ في سجل ال Shell ويظهر في قائمة العمليات.
شبكة Ethereum محلية مع Anvil
Foundry مجموعة أدوات مفتوحة المصدر لتطوير العقود الذكية مكتوبة بلغة Rust، ورخصتها MIT أو Apache-2.0 بحسب اختيارك كما في مستودعها، وفيها ثلاث أدوات نحتاجها: anvil وهي عقدة Ethereum محلية للتطوير، وcast للتعامل مع العقدة من سطر الأوامر، وforge لبناء العقود واختبارها ونشرها. والمشروع ينشر Docker Image رسمية على GitHub Container Registry، لذلك نشغل Anvil مباشرة:
docker run -d --name lab-chain-anvil -p 127.0.0.1:18545:8545 \
ghcr.io/foundry-rs/foundry:v1.8.1 "anvil --host 0.0.0.0"
docker logs lab-chain-anvilوالمخرج سوف يكون كما يلي، وقد اختصرنا الشعار في بدايته والحسابات من 2 إلى 9 وما بعد Chain ID:
1.8.1 (982849d314 2026-08-28T17:47:51.123690936Z)
https://github.com/foundry-rs/foundry
Available Accounts
==================
(0) 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000.000000000000000000 ETH)
(1) 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000.000000000000000000 ETH)
...
Private Keys
==================
(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
...
Wallet
==================
Mnemonic: test test test test test test test test test test test junk
Derivation path: m/44'/60'/0'/0/
Chain ID
==================
31337في الإعداد أعلاه لاحظ التالي:
- ال Image تبدأ كل أمر بـ
/bin/sh -c، لذلك نمرر الأمر كاملاً كنص واحد بين علامتي تنصيص. --host 0.0.0.0يجعل Anvil يستمع داخل ال Container على كل الواجهات حتى يصله المنفذ المنشور، والمنفذ منشور على127.0.0.1فقط حتى لا يصل إليه أحد من الشبكة.- Anvil ينشئ عشرة حسابات في كل واحد منها 10000 ETH وهمية، ومفاتيحها مشتقة من العبارة
test test … junkالمعروفة للجميع، وبالتالي أي مبلغ حقيقي يرسل إلى هذه العناوين على شبكة حقيقية يسرق فوراً. - رقم الشبكة Chain ID هو
31337، وهو جزء من التوقيع، فالمعاملة الموقعة لهذه الشبكة لا تصلح على شبكة أخرى.
والآن نسأل العقدة بأداة cast الموجودة في نفس ال Image، وهي تتصل افتراضياً بالعنوان http://localhost:8545 داخل ال Container:
docker exec lab-chain-anvil cast chain-id
docker exec lab-chain-anvil cast block-number
docker exec lab-chain-anvil cast balance 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --etherوالمخرج سوف يكون كما يلي:
31337
0
10000.000000000000000000السلسلة فيها الآن كتلة التكوين فقط ورقمها 0. والآن نرسل 1 ETH من الحساب 0 إلى الحساب 1، ونوقع المعاملة بالمفتاح الخاص للحساب 0:
docker exec lab-chain-anvil cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --value 1ether \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80والمخرج سوف يكون كما يلي، وهو إيصال المعاملة Transaction Receipt، وقد حذفنا منه سطر logsBloom الطويل لأنه أصفار:
blockHash 0xf171ef7201eac75ca1e6da10c6017713c0dec8d1d1a067054630d4b84f17bf5c
blockNumber 1
contractAddress
cumulativeGasUsed 21000
effectiveGasPrice 1000000001
from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
gasUsed 21000
logs []
root
status 1 (success)
transactionHash 0xce09985d94caad02fc26f64a68a95d3ae7ec58fa0ef5208c672d4f42052488dc
transactionIndex 0
type 2
blobGasPrice 1
blobGasUsed
to 0x70997970C51812dc3A010C7d01b50e0d17dc79C8ثم نتأكد من النتيجة:
docker exec lab-chain-anvil cast block-number
docker exec lab-chain-anvil cast balance 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --ether
docker exec lab-chain-anvil cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --ether
docker exec lab-chain-anvil cast nonce 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266والمخرج سوف يكون كما يلي:
1
10001.000000000000000000
9998.999978999999979000
1لاحظ أن Anvil أنشأ كتلة جديدة رقمها 1 فوراً لأن المعاملة وصلت، وهذا سلوكه الافتراضي في التطوير، وأن الحساب 0 نقص منه 1 ETH ومعه رسوم الغاز، فالتحويل البسيط يكلف 21000 وحدة غاز ضرب سعر الوحدة، وأن عداد المعاملات Nonce للحساب أصبح 1، وهذا العداد هو الذي يمنع إعادة إرسال نفس المعاملة الموقعة مرة ثانية. والآن نقرأ الكتلة 1 ونقارنها بكتلة التكوين:
docker exec lab-chain-anvil cast block 1 | grep -E '^(number|hash|parentHash|miner|difficulty|transactionsRoot|timestamp) '
docker exec lab-chain-anvil cast block 0 --field hashوالمخرج سوف يكون كما يلي:
difficulty 0
hash 0xf171ef7201eac75ca1e6da10c6017713c0dec8d1d1a067054630d4b84f17bf5c
miner 0x0000000000000000000000000000000000000000
number 1
parentHash 0xe10df75d50ceb5aeda2ffcbb9ba8446b17c0f69a80e2916312964bf12d2cd047
transactionsRoot 0xc2fa5e69ccbfa99caed7bbbb7c0c14017cb9b77655d7f9aac9375d16ef50666a
timestamp 1791244129 (Mon, 5 Oct 2026 23:48:49 +0000)
0xe10df75d50ceb5aeda2ffcbb9ba8446b17c0f69a80e2916312964bf12d2cd047في الإعداد أعلاه لاحظ التالي:
- حقل
parentHashفي الكتلة 1 هو نفس هاش الكتلة 0 في السطر الأخير، وهذا هو بالضبط حقلprev_hashالذي بنيناه فيchain.py. transactionsRootهو جذر شجرة المعاملات، أي فكرة Merkle، وفي Ethereum تستخدم شجرة مختلفة قليلاً اسمها Merkle Patricia Trie.difficultyصفر وminerعنوان أصفار، والسبب أن Ethereum لم تعد تستخدم إثبات العمل منذ The Merge.
وأخيراً نقرأ المعاملة نفسها لنرى التوقيع:
docker exec lab-chain-anvil cast tx 0xce09985d94caad02fc26f64a68a95d3ae7ec58fa0ef5208c672d4f42052488dc \
| grep -E '^(from|chainId|nonce|to|value|r|s|yParity) 'والمخرج سوف يكون كما يلي:
from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
chainId 31337
nonce 0
to 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
value 1000000000000000000
r 0xa243aa6e2c9c556166678a51151b1b1337f21443dc655f146d58187c2fdbd80e
s 0x3df633138a48495a65e6230e1029da2340be19fb5e8f7f5031601e4ea8e6778c
yParity 1القيم r وs وyParity هي توقيع ECDSA على المنحنى secp256k1، نفس نوع التوقيع الذي صنعناه بأداة OpenSSL، والقيمة value مكتوبة بأصغر وحدة وهي Wei، و1 ETH يساوي 10 أس 18 Wei. وكما يذكر توثيق المعاملات فالتوقيع هو الذي يثبت أن المعاملة جاءت من المرسل، والعقدة تتحقق منه قبل أن تقبلها.
والآن نكسر شيئاً عن قصد: ننشئ محفظة جديدة فارغة ونحاول أن نرسل منها:
docker exec lab-chain-anvil cast wallet new
docker exec lab-chain-anvil cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --value 1ether \
--private-key <the new private key>والمخرج سوف يكون كما يلي، وقد اختصرنا المفتاح الخاص لهذه المحفظة المؤقتة التي حذفناها:
Successfully created new keypair.
Address: 0xEc9fB4b057E8C826B38CdccC7cee83698b303993
Private key: 0x509ae2fc…
Error: Failed to estimate gas: server returned an error response: error code -32003: Insufficient funds for gas * price + valueلاحظ أن إنشاء المحفظة لم يحتج أي اتصال بالشبكة، فالمحفظة مجرد مفتاح خاص عشوائي وعنوان مشتق منه، والشبكة لا تعرف بوجودها حتى يصلها شيء. والعقدة رفضت المعاملة لأن العنوان لا يملك رصيداً يغطي المبلغ والرسوم، وبالتالي امتلاك مفتاح صحيح لا يكفي، والعقدة تتحقق من الرصيد في الحالة الحالية للسلسلة قبل أي شيء.
أول عقد ذكي: Counter مع forge
الآن ننشئ مشروعاً بأداة forge داخل نفس ال Container، والأمر forge init ينشئ قالباً فيه عقد بسيط اسمه Counter، ويحتاج اتصالاً بالإنترنت ليحمل مكتبة الاختبار forge-std:
docker exec -w /tmp lab-chain-anvil forge init counter
docker exec -w /tmp/counter lab-chain-anvil cat src/Counter.solوالعقد سوف يكون كما يلي:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}العقد يحفظ رقماً واحداً في حالة السلسلة، وفيه دالة تغيره ودالة تزيده بواحد، والكلمة public على المتغير تنشئ دالة قراءة باسم number() تلقائياً. والقالب فيه اختبارات جاهزة، فنشغلها قبل النشر:
docker exec -w /tmp/counter lab-chain-anvil forge testوالجزء الأخير من المخرج سوف يكون كما يلي:
Ran 2 tests for test/Counter.t.sol:CounterTest
[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 49736, ~: 52541)
[PASS] test_Increment() (gas: 51847)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.55ms (7.10ms CPU time)
Ran 1 test suite in 68.12ms (7.55ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)لاحظ الاختبار الأول من نوع Fuzz، أي أن forge جرب الدالة 256 مرة بأرقام عشوائية، وهذا النوع من الاختبارات مهم في العقود الذكية لأن الخطأ بعد النشر لا يصلح. والآن ننشر العقد على الشبكة المحلية:
docker exec -w /tmp/counter lab-chain-anvil forge create src/Counter.sol:Counter --broadcast \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80والمخرج سوف يكون كما يلي:
Compiling 1 files with Solc 0.8.36
Solc 0.8.36 finished in 5.22ms
Compiler run successful!
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
Transaction hash: 0x57b5503e5d4c4868ad1b152e2ea8f171b87c3b93689a26ba0f6264300b393673النشر نفسه معاملة، والعقد أصبح له عنوان مثل أي حساب. والآن نقرأ الرقم، ثم نزيده من حساب آخر، ثم نقرؤه مرة أخرى:
C=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
docker exec lab-chain-anvil cast call $C "number()(uint256)"
docker exec lab-chain-anvil cast send $C "increment()" \
--private-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
| grep -E '^(blockNumber|from|gasUsed|status|to) '
docker exec lab-chain-anvil cast call $C "number()(uint256)"
docker exec lab-chain-anvil cast code $C | cut -c1-66والمخرج سوف يكون كما يلي:
0
blockNumber 3
from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
gasUsed 43482
status 1 (success)
to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
1
0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063في الإعداد أعلاه لاحظ التالي:
cast callقراءة فقط، تنفذها العقدة محلياً دون معاملة ودون رسوم ودون كتلة جديدة.cast sendمعاملة موقعة تغير الحالة، لذلك أنشأت الكتلة 3 واستهلكت 43482 وحدة غاز، أي أكثر من ضعف التحويل البسيط لأنها تنفذ كوداً وتكتب في التخزين.- الأمر
cast codeيعرض الكود الثنائي Bytecode المخزن على السلسلة، وهذا ما تنفذه كل عقدة في الشبكة، وهو لا يتغير بعد النشر، لذلك لا يمكنك تصحيح خطأ فيه بنشر نسخة على نفس العنوان.
Bitcoin Core في وضع regtest
Bitcoin Core هو البرنامج المرجعي لعقدة Bitcoin ورخصته MIT كما في مستودعه، وفيه وضع اسمه regtest ينشئ سلسلة خاصة على جهازك، صعوبتها منخفضة جداً حتى تنشئ الكتل بأمر واحد كما يشرح دليل المطورين. والمشروع لا ينشر Docker Image رسمية، والـ Image المشهورة bitcoin/bitcoin تقول في صفحتها صراحة إنها غير رسمية ومخصصة لبيئات الاختبار، وإنها تتحقق من توقيعات الملفات أثناء بنائها، وتنصح عند الاستخدام الحقيقي بأن تبني البرنامج بنفسك أو تتحقق من الملف الذي تحمله من صفحة التحميل الرسمية. ولأننا في شبكة اختبار محلية نستخدمها:
docker run -d --name lab-chain-btc bitcoin/bitcoin:31.1 bitcoind -regtest -fallbackfee=0.0001 -printtoconsole=0
alias btc='docker exec --user bitcoin lab-chain-btc bitcoin-cli -regtest'
btc getblockcount
btc createwallet lab
ADDR=$(btc getnewaddress); echo "address: $ADDR"
btc generatetoaddress 101 $ADDR | tail -2
btc getbalanceوالمخرج سوف يكون كما يلي:
0
{
"name": "lab"
}
address: bcrt1q9hlrn5926spq44anar6a2mgk34jxau5qrfj2mm
"20d06448da6ee85d442ce1e55a5fbe8ba908091d0c1743555e6ed4625d7d1376"
]
50.00000000وقد يتساءل البعض: لماذا أنشأنا 101 كتلة وليس كتلة واحدة؟ والإجابة أن مكافأة الكتلة في Bitcoin لا يمكن صرفها حتى تمر عليها 100 كتلة، وهي القيمة COINBASE_MATURITY في consensus.h، والسبب أن الكتلة الأخيرة قد تستبدل بسلسلة أطول كما شرحنا، فإذا صرفت مكافأتها ثم اختفت تختفي معها كل المعاملات التي بنيت عليها. لذلك بعد 101 كتلة أصبحت مكافأة الكتلة الأولى فقط قابلة للصرف، وهي 50 وحدة في هذه الشبكة التجريبية. لاحظ أيضاً أن العنوان يبدأ بـ bcrt1، وهو ترميز Bech32 لشبكة regtest. والآن نرسل معاملة وننشئ كتلة تؤكدها:
TO=$(btc getnewaddress)
TX=$(btc sendtoaddress $TO 1.5); echo "txid: $TX"
btc getmempoolinfo | grep '"size"'
btc generatetoaddress 1 $ADDR > /dev/null
btc gettransaction $TX | grep -E '"(amount|fee|confirmations|blockheight)"' | head -4
btc getblockheader $(btc getblockhash 102) | grep -E '"(hash|height|merkleroot|nonce|bits|nTx|previousblockhash)"'
btc getblockhash 101والمخرج سوف يكون كما يلي:
txid: 47839a31c60ceb07bbb4caee05ebd1210388480aa4c491d09d148880ab75e7bd
"size": 1,
"amount": 0.00000000,
"fee": -0.00001410,
"confirmations": 1,
"blockheight": 102,
"hash": "246614cbadbf2a133a60d97f1807ab45ddd34f19aaa85df9992ef3b896e28159",
"height": 102,
"merkleroot": "49161038c45f73d905285d9b958e8bfb1233e7771b2da68ca80ab42439ac7d5b",
"nonce": 3,
"bits": "207fffff",
"nTx": 2,
"previousblockhash": "20d06448da6ee85d442ce1e55a5fbe8ba908091d0c1743555e6ed4625d7d1376"
20d06448da6ee85d442ce1e55a5fbe8ba908091d0c1743555e6ed4625d7d1376في الإعداد أعلاه لاحظ التالي:
- المعاملة انتظرت في ال Mempool، وهو طابور المعاملات التي لم تدخل كتلة بعد، فكان حجمه 1، ثم دخلت الكتلة 102 فأصبح لها تأكيد واحد.
- المبلغ الصافي صفر لأن العنوانين في نفس المحفظة، فلم يخرج من المحفظة إلا الرسوم.
previousblockhashيساوي هاش الكتلة 101، وmerklerootهو جذر شجرة معاملتي الكتلة، وهما معاملة المكافأة ومعاملتنا، لذلكnTxيساوي 2.- الحقل
nonceيساوي 3، أي أن العقدة وجدت الهاش المطلوب في المحاولة الرابعة، والسبب أن الهدف في regtest سهل جداً كما يبين الحقلbits، أما على الشبكة الحقيقية فالأمر يحتاج عدداً هائلاً من المحاولات من أجهزة متخصصة.
إزالة التجربة
احذف ال Containers مع ال Volumes المجهولة التي أنشأتها، واحذف ال Images إذا لم تعد تحتاجها:
docker rm -f -v lab-chain-anvil lab-chain-btc
docker image rm ghcr.io/foundry-rs/foundry:v1.8.1 bitcoin/bitcoin:31.1خريطة الأدوات مفتوحة المصدر
الجدول التالي يجمع أشهر الأدوات مفتوحة المصدر في هذا المجال، وقد راجعنا رخصة كل واحدة من ملف الرخصة في مستودعها، وإذا كان مفهوم الرخص جديداً عليك فراجع مقال الرخص قبل أن تبني منتجاً على أي منها:
| الأداة | الاستخدام | الرخصة |
|---|---|---|
| Bitcoin Core | العقدة المرجعية لشبكة Bitcoin والمحفظة وأداة bitcoin-cli | MIT |
| Geth | عميل تنفيذ Ethereum بلغة Go، وهو الأقدم | LGPL-3.0 للمكتبة وGPL-3.0 للبرامج التنفيذية |
| Nethermind | عميل تنفيذ Ethereum بلغة C# على .NET | LGPL-3.0 |
| Besu | عميل تنفيذ Ethereum بلغة Java، للشبكة العامة وللشبكات الخاصة مع QBFT وIBFT 2.0 | Apache-2.0 |
| Lighthouse | عميل إجماع Ethereum بلغة Rust | Apache-2.0 |
| Prysm | عميل إجماع Ethereum بلغة Go | GPL-3.0 |
| Teku | عميل إجماع Ethereum بلغة Java | Apache-2.0 |
| Hyperledger Fabric | منصة شبكات مصرح بها للمؤسسات، بعقود ذكية بلغات Go وJava وJavaScript | Apache-2.0 |
| Foundry | بيئة تطوير العقود: anvil وcast وforge التي استخدمناها | MIT أو Apache-2.0 |
| Hardhat | بيئة تطوير العقود بلغة JavaScript وTypeScript، وفيها شبكة محلية مثل Anvil | MIT |
| OpenZeppelin Contracts | مكتبة عقود Solidity مراجعة، مثل الرموز والصلاحيات | MIT |
| Otterscan | مستكشف كتل Block Explorer خفيف تشغله محلياً فوق عقدة Erigon | MIT |
| Blockscout | مستكشف كتل كامل لشبكات EVM | رخصة خاصة منذ 22 أبريل 2026 |
ولاحظ السطر الأخير: Blockscout كان مشروعاً مفتوح المصدر، ولكن ملف الرخصة الحالي هو Blockscout Software Licence التي تبدأ من 22 أبريل 2026، وهي رخصة مؤقتة قابلة للإلغاء Revocable وتشترط عرض اسم Blockscout في أي واجهة تستخدمه، وبالتالي فهو اليوم من نوع المصدر المتاح Source Available وليس مفتوح المصدر بالتعريف الذي شرحناه في قسم رخص المصدر المتاح. ولاحظ أيضاً أن عقدة Ethereum الكاملة تحتاج اليوم برنامجين معاً، عميل تنفيذ وعميل إجماع، كما يشرح توثيق العقد والعملاء، وتنوع العملاء مقصود حتى لا يسقط خطأ في برنامج واحد الشبكة كلها. وفي مقالات قادمة بإذن الله سوف نشغل عقدة Bitcoin كاملة، وعقدة Ethereum بعميليها، وشبكة خاصة بأداة Besu.
أساسيات الأمان
في البلوكتشين العام لا يوجد قسم دعم يعيد لك كلمة المرور، فالمفتاح الخاص هو الملكية نفسها، ومن يحصل عليه يملك ما يملكه، والمعاملة المرسلة لا يمكن إلغاؤها. لذلك القواعد التالية ليست نصائح اختيارية:
- لا تعط أحداً المفتاح الخاص أو عبارة الاسترداد Seed Phrase أبداً: العبارة المكونة من 12 أو 24 كلمة تولد كل مفاتيح المحفظة، وصفحة الأمان في ethereum.org تقول إنه لا يوجد دعم رسمي لـ Ethereum يطلبها منك، وأي شخص يطلبها محتال مهما كان اسمه.
- لا تحفظ العبارة في صورة أو ملف على جهاز متصل: الصور تتزامن تلقائياً مع خدمات التخزين السحابي، وهذا بالضبط ما تبحث عنه البرامج الخبيثة.
- حمل المحافظ والعقد من المصدر الرسمي فقط: هناك تطبيقات محافظ مزيفة في المتاجر وإعلانات البحث، وتحقق من هاش الملف وتوقيعه كما تفعل مع أي برنامج، فهذه بالضبط فائدة الهاش التي بدأنا بها.
- اقرأ ما توقع عليه: من أشهر الحيل أن يطلب منك موقع مزيف توقيع موافقة Approve تعطي عقداً صلاحية سحب رموزك، وتحذر صفحة الأمان نفسها من «هدايا» الرموز Airdrops التي تقودك إلى موقع يطلب هذه الموافقة، فإذا لم تفهم ما تطلبه المحفظة منك فلا توقع.
- افصل بين بيئة التطوير والمال الحقيقي: مفاتيح الاختبار مثل مفاتيح Anvil لا تستخدمها خارج الشبكة المحلية، ولا تضع مفتاحاً حقيقياً في ملف
.envداخل مستودع Git.
تدريب عملي
- عدل
chain.pyحتى يحتاج كل بلوك إلى Nonce يجعل هاشه يبدأ بأربعة أصفار مثلpow.py، ثم عدل الكتلة 1 وقس كم ثانية تحتاج لإعادة تعدين الكتلتين 1 و2. - في شبكة Anvil أرسل معاملتين من نفس الحساب بنفس قيمة
--nonce، وانظر ماذا تقول العقدة عن الثانية، واربط ذلك بمشكلة الإنفاق المزدوج.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- البلوكتشين يحل مشكلة محددة: سجل مشترك تكتب فيه عدة أطراف لا تثق ببعضها ولا تقبل مالكاً مركزياً، ومنها مشكلة الإنفاق المزدوج.
- الهاش يجعل أي تغيير ظاهراً، والسلسلة تجعل تغيير كتلة واحدة يكسر كل ما بعدها، وشجرة Merkle تلخص آلاف المعاملات في هاش واحد.
- التوقيع الرقمي يضمن أن صاحب المفتاح الخاص وحده يصرف، والعنوان هاش للمفتاح العام بترميز مناسب.
- الإجماع يجعل إعادة كتابة التاريخ مكلفة: بالكهرباء في إثبات العمل، وبمال المدقق في إثبات الحصة، وبالتصويت بين أعضاء معروفين في الشبكات الخاصة.
- العقود الذكية برامج لا تتغير بعد النشر، وأخطاؤها تبقى منشورة كما حدث مع The DAO.
- أغلب مشاكل الأعمال تحلها قاعدة بيانات بشكل أفضل، فلا تستخدم البلوكتشين إلا إذا اجتزت أسئلة القرار، ولا تضع فيه بيانات شخصية.
- تستطيع أن تجرب كل ذلك على جهازك بأمان مع Anvil وforge وBitcoin Core في وضع regtest، بحسابات اختبار ليس لها قيمة.
دليلالترميز Encoding للمطورين: Base64 وأخواتهلماذا نحول البايتات إلى حروف حتى تمر في البريد والروابط، وكيف تفك أي سلسلة منها، ولماذا لا يحمي Base64 أي سر.
دليلالأنظمة الموزعة للمبتدئين: ماذا يحدث عندما تنقطع الشبكةلماذا تتصرف الأنظمة الموزعة بشكل غريب عند انقطاع الشبكة، وكيف تتفق السيرفرات على قرار واحد رغم ذلك.
دليلالدخول دون كلمة مرور: شرح Passkeys وWebAuthnلماذا لا تحميك رموز SMS وTOTP من التصيد الحديث، وكيف تحل Passkeys المشكلة بمفتاح خاص لا يغادر جهازك.
دليلالرقم المتسلسل أم UUID؟ كيف تختار المعرف الفريدلماذا يفشل الرقم المتسلسل في الروابط وعند تعدد السيرفرات، وكيف يؤثر نوع المعرف الذي تختاره على فهرس قاعدة البيانات.
دليلما هي البرمجيات مفتوحة المصدر؟ القصة وأشهر الرخصكيف بدأت البرمجيات الحرة والمفتوحة، وما الفرق بين الحر والمجاني، وكيف تعرف ما تسمح به رخصة أي مشروع قبل أن تبني عليه.سجل التحديثات
- أكتوبر 2026: كتابة المقال، واختبار التجارب على Docker Engine 29.8.0 مع Python 3.13.16 وOpenSSL 3.5.7، وFoundry v1.8.1 مع Solc 0.8.36، وBitcoin Core v31.1.0 في وضع regtest.