لنفرض أنك في شركة ناشئة تكتب برنامج محاسبة سحابياً للمتاجر التي تقبل الدفع بـ Bitcoin، مثل المتجر الذي تتبعنا معاملاته في مقال كيف يعمل Bitcoin، وأن البرنامج يقرأ المعاملات من واجهة API يشغلها طرف ثالث. وبعد شهرين يرسل المتجر سؤالاً بسيطاً: «من يضمن أن الأرقام في تقريركم صحيحة؟ أنتم تعرضون ما يقوله مزود لا نعرفه». ثم يتوقف المزود يوماً كاملاً للصيانة، فتتوقف تقارير كل العملاء، ويكتشف الفريق أيضاً أن المزود يرى كل العناوين التي يسأل عنها البرنامج، أي كل عملاء الشركة. وعندها يأتي القرار الذي يؤجله أغلب الفرق: أن تشغل الشركة عقدة Bitcoin كاملة Full Node على سيرفرها، تتحقق من كل كتلة بنفسها، ويقرأ منها البرنامج مباشرة.
وهذا المقال دليل عملي لذلك القرار وتنفيذه: لماذا تشغل عقدة، وما الذي تحتاجه من قرص وذاكرة وشبكة، وكيف تثبت Bitcoin Core بملف Docker Compose بإصدار محدد، وكيف تكتب ملف الإعدادات بحيث لا تفتح واجهة RPC للعالم، وماذا يحدث في المزامنة الأولى، وكيف يعمل التقليم Pruning، وكيف تراقب العقدة وتحدثها. وقد جربنا كل الخطوات على عقدة حقيقية متصلة بشبكة الاختبار signet بدلاً من الشبكة الرئيسية، والسبب أن المزامنة الكاملة للشبكة الرئيسية تحتاج مئات الجيجابايت، والخطوات نفسها واحدة في الشبكتين إلا سطراً واحداً في الإعدادات. والمقال تعليمي تقني، ولا يتضمن أي نصيحة استثمارية.
وسوف نناقش في هذا المقال ما يلي:
- لماذا تشغل عقدة كاملة بدلاً من واجهة API أو عميل خفيف، وما الذي تثق به في كل حالة.
- الموارد المطلوبة: القرص والذاكرة والشبكة، والفرق بين العقدة الكاملة والمقلمة Pruned.
- اختيار الشبكة: الرئيسية mainnet وsignet وregtest، ولماذا جربنا على signet.
- التثبيت بـ Docker Compose وملف bitcoin.conf، والتحقق من ملفات البرنامج.
- المزامنة الأولى Initial Block Download من الداخل بسجلات حقيقية: مزامنة الرؤوس ثم تحميل الكتل والتحقق منها.
- تأمين واجهة RPC بأربعة أقفال، والمشكلة التي ظهرت لنا مع شبكة Docker.
- خدمة الشبكة بالاتصالات الواردة وضبط الرفع، وجدول قرار لنوع العقدة، والانتقال إلى الشبكة الرئيسية.
- ربط التطبيقات بالعقدة، والتشغيل اليومي: الإيقاف الآمن، والتحديث، والنسخ الاحتياطي، والمراقبة بسكربت.
لماذا تشغل عقدة كاملة؟
عندما يسأل برنامج المحاسبة «هل وصلت الدفعة إلى هذا العنوان؟» فهو يحتاج من يجيبه، والسؤال الحقيقي هو من يصدق. والخيارات ثلاثة، والشكل التالي يضعها جنباً إلى جنب:

- واجهة API لطرف ثالث: الأسهل، فلا يوجد سيرفر ولا مزامنة، ولكنك تثق بأن المزود يعرض الحقيقة، وأنه لن يتوقف، وأنه يحفظ ما تسأل عنه بأمانة، وكل عناوين عملائك تمر عليه.
- العميل الخفيف: يحمل رؤوس الكتل فقط ويتحقق من إثبات العمل فيها، ثم يطلب إثبات Merkle لمعاملته كما رأينا في مقال التشفير في البلوكتشين. والقسم الثامن من ورقة Bitcoin يصف هذه الطريقة باسم التحقق المبسط Simplified Payment Verification، ويذكر أنها موثوقة ما دامت العقد الشريفة تسيطر على الشبكة، ولكنها أضعف إذا سيطر مهاجم عليها، ويقترح أن تشغل الشركات التي تستلم مدفوعات كثيرة عقدها الخاصة.
- العقدة الكاملة: تحمل كل كتلة وتتحقق من كل معاملة فيها بكل قواعد الإجماع، كما رأينا عندما رفضت العقدة كتلة فيها إثبات عمل صحيح ومكافأة زائدة بساتوشي واحد في مقال التعدين. فهي لا تثق بالمعدنين ولا بأي عقدة أخرى، ولا يعرف أحد أي العناوين تهمك، لأنها تحمل كل شيء ولا تسأل عن شيء بعينه.
وقد يتساءل البعض: إذا كانت العقدة الكاملة تحمل كل شيء، فهل كل شركة تقبل Bitcoin تحتاجها؟ والإجابة لا، فمتجر صغير يستلم دفعات قليلة قد يكفيه مزود موثوق مع عدد تأكيدات معقول. ولكن عندما تقدم خدمة لعملاء آخرين، وتكون أرقامك هي أساس تقاريرهم المالية، يصبح الاعتماد على طرف ثالث غير مقبول، وهذا وضع الشركة في قصتنا.
ماذا تحتاج من الموارد؟
صفحة تشغيل عقدة كاملة على bitcoin.org تذكر أن العقدة تحتاج عند تشغيلها أول مرة تحميل قرابة 740 جيجابايت للسلسلة كلها، وأن الحد الأدنى للعقدة المقلمة قرابة 7 جيجابايت من القرص و2 جيجابايت من الذاكرة، وأن العقد على الاتصالات السريعة كثيراً ما ترفع 200 جيجابايت أو أكثر في الشهر وتحمل قرابة 20 جيجابايت. وهذه الأرقام تتغير مع نمو السلسلة، فارجع إلى الصفحة عند التخطيط. والقرار الأول هو نوع العقدة:
| البند | عقدة كاملة غير مقلمة | عقدة مقلمة Pruned |
|---|---|---|
| القرص | كل الكتل منذ 2009، مئات الجيجابايت وتزيد | آخر الكتل فقط حتى الحد الذي تحدده، وأقله 550 ميجابايت، مع مجموعة القطع غير المنفقة |
| التحقق | كامل | كامل أيضاً، فكل كتلة تحمل وتتحقق منها ثم تحذف |
| قراءة كتل قديمة | ممكنة | غير ممكنة لما حذف |
| فهرس كل المعاملات txindex | ممكن | غير ممكن |
| خدمة الكتل القديمة للعقد الأخرى | نعم | لا |
| يناسب | مستكشف كتل، أو خدمة تحتاج تاريخاً كاملاً | محفظة، أو نظام استلام يتابع عناوينه من الآن فصاعداً |

ولاحظ أن التقليم لا يضعف الأمان: العقدة المقلمة تحمل كل كتلة وتتحقق منها كاملة مثل غيرها، وتحتفظ بمجموعة القطع غير المنفقة كاملة، ثم تحذف ملفات الكتل القديمة فقط. والذي تخسره هو القدرة على العودة إلى كتلة قديمة، فإذا أضفت إلى العقدة لاحقاً محفظة تحتاج البحث في معاملات قديمة، احتاجت العقدة أن تحمل السلسلة كلها من جديد. ولبرنامج المحاسبة في قصتنا، الذي يحتاج أن يبحث عن أي معاملة قديمة بمعرفها، العقدة الكاملة مع txindex=1 هي الخيار، أما في تجربتنا فسوف نستخدم عقدة مقلمة حتى نبقى في مساحة صغيرة.
والقرص أهم من حجمه سرعته: المزامنة الأولى قراءات وكتابات عشوائية كثيرة على مجموعة القطع غير المنفقة، والقرص الميكانيكي HDD يجعلها أبطأ بكثير، لذلك استخدم SSD. والذاكرة تؤثر عبر الخيار dbcache، وهو حجم الذاكرة المؤقتة لمجموعة القطع، وكما يذكر توثيق تقليل الذاكرة فقيمته الافتراضية 1024 ميجابايت على الأجهزة التي فيها 4 جيجابايت أو أكثر، وكلما زاد قلت الكتابات على القرص أثناء المزامنة الأولى.
أي شبكة: mainnet أم signet أم regtest؟
Bitcoin Core يتصل بإحدى أربع شبكات بحسب الإعدادات: الشبكة الرئيسية mainnet التي فيها القيمة الحقيقية، وشبكات الاختبار testnet، وsignet، وregtest التي استخدمناها في كل المقالات السابقة، وهي شبكة خاصة على جهازك لا تتصل بأحد. أما signet فهي شبكة اختبار عامة لها عقد حقيقية حول العالم، والفرق كما يصف BIP325 أن الكتل فيها تحتاج توقيعاً يحقق شرطاً برمجياً يسمى Challenge، وهو نص برمجي يحدده من أطلق الشبكة، بالإضافة إلى إثبات العمل، فلا يستطيع أي أحد أن يعدن فيها، والكتل تأتي بانتظام، والعملات فيها بلا قيمة. لذلك هي مكان مناسب لتجربة عقدة حقيقية تتصل بالإنترنت وتتزامن مع عقد أخرى، دون مئات الجيجابايت ودون مال.
والفرق في الإعدادات بين signet والشبكة الرئيسية سطر واحد signet=1 واسم القسم [signet] والمنافذ: الشبكة الرئيسية تستخدم 8333 للاتصال بالعقد و8332 لواجهة RPC، وsignet تستخدم 38333 و38332، كما في توثيق ملف الإعدادات.
التثبيت بـ Docker Compose
مشروع Bitcoin Core لا ينشر Docker Image رسمية، ويوزع البرنامج كملفات تنفيذية من صفحة التحميل الرسمية، ومعها ملف SHA256SUMS بهاش كل ملف، وملف SHA256SUMS.asc بتوقيعات عدد من المطورين على ملف الهاشات، وتشرح الصفحة كيف تستورد مفاتيحهم وتتحقق من التوقيعات قبل التشغيل. وDocker Image التي نستخدمها bitcoin/bitcoin غير رسمية كما ذكرنا في المقالات السابقة، وصفحتها تقول إنها تتحقق من توقيعات الملفات أثناء البناء، وهي مناسبة للتجربة. أما في الإنتاج فلديك طريقان: أن تبني Image بنفسك من الملفات الرسمية بعد التحقق منها، أو أن تراجع ملف بناء Docker Image التي تستخدمها وتثبت إصدارها بالهاش وليس بالوسم فقط.
التحقق من ملفات البرنامج الرسمية
إذا اخترت أن تبني Docker Image بنفسك من الملفات الرسمية، فالخطوة الأولى أن تتأكد أن الملف الذي حملته هو نفسه الذي نشره المشروع، وأن ملف الهاشات موقع من مطوريه. وجربنا ذلك على إصدارنا نفسه بأدوات sha256sum وgpg، بعد أن استوردنا مفاتيح المطورين من مجلد builder-keys في مستودع guix.sigs الذي تشير إليه صفحة التحميل:
V=31.1
curl -sSO https://bitcoincore.org/bin/bitcoin-core-$V/SHA256SUMS
curl -sSO https://bitcoincore.org/bin/bitcoin-core-$V/SHA256SUMS.asc
curl -sSO https://bitcoincore.org/bin/bitcoin-core-$V/bitcoin-$V-x86_64-linux-gnu.tar.gz
sha256sum --ignore-missing --check SHA256SUMS
mkdir -p keys
for k in $(curl -s https://api.github.com/repos/bitcoin-core/guix.sigs/contents/builder-keys | grep -o '"name": "[^"]*\.gpg"' | cut -d'"' -f4); do
curl -sS -o keys/$k https://raw.githubusercontent.com/bitcoin-core/guix.sigs/main/builder-keys/$k
done
gpg --import keys/*.gpg 2>&1 | grep "Total number"
gpg --verify SHA256SUMS.asc SHA256SUMS 2>&1 | grep -c "Good signature"والمخرج سوف يكون كما يلي:
bitcoin-31.1-x86_64-linux-gnu.tar.gz: OK
gpg: Total number processed: 40
11في المخرج أعلاه لاحظ التالي:
- هاش الملف المحمل، وحجمه قرابة 90 ميجابايت، يطابق السطر المكتوب له في
SHA256SUMS، وهذا يحمي من ملف تالف أو معدل في الطريق، ولكنه وحده لا يكفي، لأن من يعدل الملف يستطيع أن يعدل ملف الهاشات معه. - لذلك الخطوة الثانية هي التي تهم: ملف الهاشات عليه 11 توقيعاً صحيحاً من مطورين مختلفين بنوا البرنامج كل على جهازه ووصلوا إلى نفس الملفات، وهذا معنى البناء القابل للتكرار Reproducible Builds.
- أظهر
gpgمع كل توقيع تحذيراً بأن المفتاح غير موثق بتوقيع موثوق، والسبب أننا استوردنا المفاتيح دون أن نقرر أننا نثق بأصحابها. والقرار هنا لك: كم توقيعاً تطلب، ومن أي مطورين، وهل تتحقق من بصمات مفاتيحهم من مصدر ثان. والمهم أن تكون هذه الخطوة جزءاً مكتوباً من إجراء التحديث، وليس شيئاً يفعله شخص واحد مرة.
نجهز مجلداً فيه ملف Compose ومجلد للبيانات، وملف الإعدادات داخل مجلد البيانات. وأول شيء نحتاجه مستخدم RPC لتطبيقاتنا، وBitcoin Core يأتي بأداة صغيرة rpcauth.py تولد كلمة مرور عشوائية وسطراً للإعدادات فيه هاشها فقط، فلا تكتب كلمة المرور نفسها في الملف:
mkdir -p bitcoin-node/data && cd bitcoin-node
curl -sL -o rpcauth.py https://raw.githubusercontent.com/bitcoin/bitcoin/master/share/rpcauth/rpcauth.py
docker run --rm --name lab-node-py -v "$PWD:/lab" -w /lab python:3.13.16-slim python rpcauth.py appreader > rpcauth.out
head -2 rpcauth.outوالمخرج سوف يكون كما يلي، وكلمة المرور في السطر الرابع من الملف تحفظها في مدير أسرار Secrets Manager ثم تحذف الملف:
String to be appended to bitcoin.conf:
rpcauth=appreader:7d54408c9f567b825963d925911f32c5$ec2cfd3b4e5ee747a72f75aed8963c5a4d8fea3cce28423a0c50c034269430baثم ملف الإعدادات data/bitcoin.conf:
# Bitcoin Core settings for a pruned signet node (lab). On mainnet: remove signet=1 and use the [main] section.
signet=1
prune=550
dbcache=1000
[signet]
# RPC listens inside the container; Docker publishes it on 127.0.0.1 only
rpcbind=0.0.0.0
rpcallowip=127.0.0.1
rpcallowip=10.88.0.0/24
# a read-only user for applications (hash made by share/rpcauth/rpcauth.py)
rpcauth=appreader:7d54408c9f567b825963d925911f32c5$ec2cfd3b4e5ee747a72f75aed8963c5a4d8fea3cce28423a0c50c034269430ba
rpcwhitelist=appreader:getblockchaininfo,getblockcount,getblockhash,getblock,getbestblockhash,getrawtransaction,getmempoolinfo,estimatesmartfee
rpcwhitelistdefault=0في الإعداد أعلاه لاحظ التالي:
- الإعدادات العامة مثل الشبكة والتقليم في أعلى الملف، أما إعدادات RPC فيجب أن تكون في قسم الشبكة
[signet]، والسبب أن Bitcoin Core يتجاهل بعض الإعدادات في أعلى الملف لغير الشبكة الرئيسية، حتى لا تنتقل إعدادات الشبكة الرئيسية بالخطأ إلى شبكة اختبار. prune=550هو أقل حد مسموح للتقليم بالميجابايت، وdbcache=1000قريب من القيمة الافتراضية، وتستطيع أن ترفعه أثناء المزامنة الأولى على سيرفر فيه ذاكرة كافية ثم تخفضه.rpcbind=0.0.0.0يجعل RPC يستمع على كل الواجهات داخل الحاوية Container حتى يصله المنفذ المنشور، والذي يحمي فعلاً هوrpcallowipوالنشر على 127.0.0.1 في Compose كما سنرى.rpcwhitelistيحدد الأوامر التي يستطيع المستخدمappreaderأن يستدعيها، وكلها أوامر قراءة، وrpcwhitelistdefault=0يجعل المستخدمين الذين ليس لهم قائمة يملكون كل الصلاحيات، وهذا يخص ملف cookie الذي يستخدمهbitcoin-cliداخل الحاوية Container للإدارة.
ثم ملف docker-compose.yml:
name: btc-node
services:
bitcoind:
image: bitcoin/bitcoin:31.1
container_name: btc-node-bitcoind
command: ["bitcoind", "-printtoconsole=1"]
volumes:
- ./data:/home/bitcoin/.bitcoin
ports:
- "127.0.0.1:19100:38332"
healthcheck:
test: ["CMD", "bitcoin-cli", "-datadir=/home/bitcoin/.bitcoin", "getblockcount"]
interval: 60s
timeout: 10s
retries: 3
restart: unless-stopped
stop_grace_period: 2m
networks: [node]
networks:
node:
ipam:
config:
- subnet: 10.88.0.0/24في الإعداد أعلاه لاحظ التالي:
- الإصدار مثبت
31.1، ولا تستخدمlatestأبداً لعقدة، لأن التحديث يجب أن يكون قراراً تقرأ قبله ملاحظات الإصدار. - منفذ RPC منشور على
127.0.0.1فقط، أي لا يصل إليه إلا ما يعمل على نفس السيرفر، ولم ننشر منفذ الاتصال بالعقد 38333، لأن العقدة تتصل بالآخرين بنفسها ولا تحتاج اتصالات واردة لتعمل، وإذا أردت أن تخدم الشبكة باتصالات واردة فانشره على كل الواجهات وافتحه في جدار الحماية Firewall. stop_grace_period: 2mيعطي العقدة دقيقتين لتكتب ذاكرتها المؤقتة على القرص قبل أن يقتلها Docker، لأن القيمة الافتراضية عشر ثوان، وإيقاف العقدة بالقوة أثناء الكتابة قد يجبرها على إعادة بناء قاعدة البيانات عند التشغيل التالي.- شبكة Compose محددة بالمدى
10.88.0.0/24، وهو نفس المدى فيrpcallowip، والسبب في قسم تأمين RPC أدناه. - فحص الصحة Health Check يسأل العقدة عن عدد الكتل كل دقيقة، فإذا توقفت عن الإجابة ظهر ذلك في
docker psوفي أي مراقبة تقرأ حالته.
docker compose up -d
docker logs btc-node-bitcoind 2>&1 | grep -E "Config file arg|Command-line arg|Loaded best chain|init message: Done" | tail -12والمخرج سوف يكون كما يلي، وهو من إعادة تشغيل بعد أن بدأت المزامنة:
2026-10-28T02:13:13Z Config file arg: prune="550"
2026-10-28T02:13:13Z Config file arg: signet="1"
2026-10-28T02:13:13Z Config file arg: [signet] rpcallowip="127.0.0.1"
2026-10-28T02:13:13Z Config file arg: [signet] rpcallowip="172.16.0.0/12"
2026-10-28T02:13:13Z Config file arg: [signet] rpcauth=****
2026-10-28T02:13:13Z Config file arg: [signet] rpcbind="0.0.0.0"
2026-10-28T02:13:13Z Config file arg: [signet] rpcwhitelist="appreader:getblockchaininfo,getblockcount,getblockhash,getblock,getbestblockhash,getrawtransaction,getmempoolinfo,estimatesmartfee"
2026-10-28T02:13:13Z Config file arg: [signet] rpcwhitelistdefault="0"
2026-10-28T02:13:13Z Command-line arg: datadir="/home/bitcoin/.bitcoin"
2026-10-28T02:13:13Z Command-line arg: printtoconsole="1"
2026-10-28T02:13:24Z Loaded best chain: hashBestChain=0000004bd47dde9bceac8edcd717ab9e592e75bf29fe5c7d374bfa0ba8d8c8ac height=15356 date=2020-12-09T22:06:40Z progress=0.000522
2026-10-28T02:13:24Z init message: Done loadingلاحظ أن العقدة تطبع كل إعداد قرأته، وتخفي قيمة rpcauth بنجوم، وهذا السجل أول ما تراجعه إذا لم يعمل إعداد كما تتوقع. ولاحظ أيضاً سطر rpcallowip="172.16.0.0/12"، فهذه كانت قيمتنا الأولى قبل أن نكتشف المشكلة التي سنشرحها، ثم غيرناها إلى 10.88.0.0/24. والسطر Loaded best chain … height=15356 يعني أن العقدة أكملت من حيث توقفت قبل إعادة التشغيل، ولم تبدأ من جديد.
المزامنة الأولى Initial Block Download من الداخل
عندما تبدأ العقدة أول مرة لا تملك إلا كتلة البداية Genesis المكتوبة في كودها، وكل ما بعدها يجب أن تحمله من عقد لا تعرفها وتتحقق منه بنفسها، وهذه المرحلة اسمها المزامنة الأولى Initial Block Download، واختصاراً IBD. وتمر بمرحلتين واضحتين في السجل: الرؤوس أولاً ثم الكتل. والسبب أن الرأس Header صغير، 80 بايت فقط، وفيه إثبات العمل وهاش الكتلة السابقة، فتستطيع العقدة أن تحمل رؤوس السلسلة كلها بسرعة، وتعرف أي سلسلة فيها أكبر عمل متراكم، قبل أن تحمل غيغابايتات الكتل نفسها.
وفي الإصدارات الحديثة تحمل العقدة الرؤوس مرتين: مرة أولى «فحص مسبق» Pre-synchronization تتحقق فيها أن السلسلة التي يعرضها الطرف الآخر فيها عمل متراكم كاف دون أن تحفظ الرؤوس في الذاكرة، ثم مرة ثانية تحفظها فعلاً. والهدف حماية العقدة من طرف يرسل لها ملايين الرؤوس الرخيصة ليملأ ذاكرتها. وهذا ما ظهر في سجل عقدتنا:
2026-10-28T02:04:57Z Pre-synchronizing blockheaders, height: 2000 (~0.62%)
...
2026-10-28T02:06:31Z Pre-synchronizing blockheaders, height: 292000 (~88.94%)
2026-10-28T02:06:37Z Synchronizing blockheaders, height: 276 (~0.09%)
...
2026-10-28T02:07:42Z UpdateTip: new best=00000086d6b2636cb2a392d45edc4ec544a10024d30141c9adf4bfd9de533b53 height=1 version=0x20000000 log2_work=23.206105 tx=2 date='2020-09-01T00:12:01Z' progress=0.000000 cache=0.3MiB(1txo)
2026-10-28T02:07:48Z Synchronizing blockheaders, height: 328303 (~100.00%)الفحص المسبق مر على قرابة 292 ألف رأس في دقيقة ونصف، ثم بدأت المرحلة الثانية التي تحفظ الرؤوس، وقبل أن تنتهي بدأت العقدة تحمل الكتل الأولى وتضيفها، والكتلة 1 من سبتمبر 2020 وهو تاريخ بداية signet الحالية. وفي 02:07:48 وصلت كل الرؤوس، أكثر من 328 ألف رأس، أي في أقل من ثلاث دقائق من البداية. وبعدها يطبع السجل سطراً لكل كتلة جديدة يضيفها إلى السلسلة، وهذا مثال من منتصف المزامنة:
2026-10-28T03:58:01Z UpdateTip: new best=000000c592d20f6608163e495c503fb2eacc249f5a09684ffc1355c9a3c70aae height=230069 version=0x20000000 log2_work=41.385527 tx=9882267 date='2025-01-09T10:50:25Z' progress=0.328330 cache=1151.0MiB(8667545txo)في السطر أعلاه لاحظ التالي:
heightرقم الكتلة التي وصلت إليها، وdateتاريخ تلك الكتلة، أي أن العقدة تعيش الآن في يناير 2025 وتتقدم نحو اليوم.txعدد المعاملات الكلي منذ البداية حتى هذه الكتلة، وprogressنسبة التقدم، وهي محسوبة بالمعاملات وليس بالكتل، ولذلك كانت 33% فقط مع أن العقدة تجاوزت 70% من الكتل، لأن الكتل الحديثة أكثر امتلاء بكثير من القديمة.cacheحجم الذاكرة المؤقتة لمجموعة القطع غير المنفقة UTXO Set، وعدد القطع فيها، وعندما تمتلئ تكتب العقدة محتواها على القرص ثم تكمل، ولهذا كلما كانتdbcacheأكبر كانت الكتابات أقل.
وسجلنا ارتفاع العقدة كل فترة لنرى السرعة الحقيقية على جهاز مكتبي متوسط واتصال منزلي:
| الوقت UTC | الكتل | الملاحظة |
|---|---|---|
| 02:04:57 | 0 | بداية الفحص المسبق للرؤوس |
| 02:07:48 | 1 | كل الرؤوس 328,303 وصلت، وتحميل الكتل بدأ قبلها بثوان |
| 02:12 | 14,568 | كتل 2020 الأولى شبه فارغة |
| 02:29 | 64,261 | |
| 02:57 | 131,336 | التقليم بدأ يحذف ملفات الكتل القديمة |
| 03:35 | 206,023 | السرعة تقل مع امتلاء الكتل |
| 03:57 | 229,879 | التقدم 32% بالمعاملات، و3.96 جيجابايت محملة |
| 04:07 | 231,967 | قرابة 200 كتلة في الدقيقة، والتقدم 40% |
ولاحظ أن السرعة في الساعة الأولى كانت قرابة 2,500 كتلة في الدقيقة، ثم نزلت إلى قرابة 1,000، ليس لأن الشبكة أبطأ، وإنما لأن كل كتلة أصبحت تحمل معاملات أكثر يجب أن تتحقق منها. وفي الشبكة الرئيسية يتكرر نفس النمط بحجم أكبر بكثير، فالكتل منذ سنوات ممتلئة تقريباً، والمزامنة الأولى قد تأخذ من ساعات على سيرفر قوي إلى أيام على جهاز صغير، وأغلب الوقت يذهب في القرص والمعالج وليس في الشبكة.
وخلال المزامنة نزلت العقدة 3.96 جيجابايت من الشبكة ورفعت 12 ميجابايت فقط، كما في getnettotals، لأنها في المزامنة الأولى تأخذ ولا تعطي، ولا تقبل اتصالات واردة أصلاً لأننا لم ننشر منفذ الاتصال. واتصلت بعشر عقد كلها صادرة Outbound، منها ثمان بنقل كامل للمعاملات والكتل وعقدتان للكتل فقط Block-relay-only، وهذا تصميم في Bitcoin Core يصعب على المهاجم أن يعرف كل اتصالات عقدتك. والعقد التي اتصلنا بها تشغل إصدارات مختلفة من 29.0 إلى 31.1، وهذا طبيعي، فلا يجبر أحد أحداً على التحديث في Bitcoin، وكل الإصدارات تطبق نفس قواعد الإجماع.
عندما تبطئ المزامنة فجأة
بعد الكتلة 230 ألف تقريباً نزلت السرعة مرة أخرى، من قرابة 1,000 كتلة في الدقيقة إلى قرابة 200، والسبب يظهر في سجل UpdateTip نفسه: العداد tx كان 9,882,267 عند الكتلة 230,069، وأصبح 12,116,553 عند الكتلة 231,980، أي أكثر من 2.2 مليون معاملة في 1,911 كتلة، بمتوسط يزيد على 1,100 معاملة في كل كتلة من كتل يناير 2025 في signet. والعقدة تتحقق من كل معاملة منها وتحدث مجموعة القطع غير المنفقة لكل مدخل ومخرج، فالذي يحدد السرعة هو عدد المعاملات وليس عدد الكتل. وفي نفس الوقت كان استهلاك المعالج للحاوية قرابة 35% والذاكرة قرابة 1.5 جيجابايت، فالعقدة لم تكن متوقفة ولا تنتظر الشبكة، وإنما تعمل على كتل أثقل.
وعند الكتلة 231,967، بعد قرابة ساعتين من البداية ونسبة تقدم 40%، أوقفنا التجربة هنا، لأن ما نريد أن نراه من المزامنة الأولى قد ظهر كله: الرؤوس ثم الكتل، والتحقق، والتقليم، وتغير السرعة. والدرس العملي من هذا الرقم أن تقدير الوقت المتبقي من عدد الكتل خطأ، وأن الأفضل أن تراقب verificationprogress مع الوقت وتحسب معدله، وأن تتوقع أن الجزء الأخير من السلسلة قد يكون الأثقل، وهذا أوضح في الشبكة الرئيسية التي امتلأت كتلها منذ سنوات.
لماذا لا تتحقق العقدة من كل توقيع قديم؟ assumevalid
في بداية السجل يوجد هذا السطر:
2026-10-28T02:14:10Z Assuming ancestors of block 00000008414aab61092ef93f1aacc54cf9e9f16af29ddad493b908a01ff5c329 have valid signatures.والخيار assumevalid كما في bitcoind -help يعني: إذا كانت هذه الكتلة في السلسلة فافترض أنها وما قبلها صحيحة، وتجاوز التحقق من النصوص البرمجية Scripts فيها، أي التوقيعات. والكتلة في سجلنا عند الارتفاع 293,175 في signet، وقيمتها الافتراضية مكتوبة في كود كل إصدار ويراجعها المطورون قبل الإصدار. وهذا لا يعني أن العقدة تثق بأحد في كل شيء: هي ما زالت تتحقق من إثبات العمل في كل كتلة، ومن أن مجموع المخرجات لا يتجاوز المدخلات، ومن أن كل مدخل ينفق قطعة موجودة، وتبني مجموعة القطع غير المنفقة بنفسها، والذي تتجاوزه هو التحقق من التوقيعات في الكتل القديمة التي دفنت تحت عمل ضخم. وبعد هذه الكتلة يعود التحقق الكامل، ولذلك تتوقع أن تبطئ المزامنة بعدها. وإذا أردت أن تتحقق من كل توقيع منذ البداية فاكتب assumevalid=0، والثمن وقت أطول للمزامنة الأولى.
وفي الإصدارات الحديثة يوجد خيار أسرع اسمه assumeutxo، تحمل فيه نسخة جاهزة من مجموعة القطع غير المنفقة Snapshot وتحملها بالأمر loadtxoutset، فتصبح العقدة قابلة للاستخدام عند ارتفاع تلك النسخة خلال وقت قصير، ثم تكمل التحقق من السلسلة كلها من البداية في الخلفية. ويقول توثيق assumeutxo إنه لا يوجد مصدر رسمي للنسخ حالياً، ولكن أي نسخة تحملها تقارن بهاش مكتوب في كود البرنامج، وإنك تستطيع أن تولدها بنفسك من عقدة متزامنة بالأمر dumptxoutset، وإن العقدة المقلمة تستخدم مع هذه الميزة 1,100 ميجابايت على الأقل بدلاً من 550، وإن مجلدي حالة سيوجدان مؤقتاً أثناء التحقق في الخلفية. ولم نستخدمها في تجربتنا، لأن هدفنا أن نرى المزامنة الأولى كما هي.
التقليم Pruning في العقدة نفسها
عقدتنا مقلمة بالحد الأدنى 550 ميجابايت، وفي منتصف المزامنة سألناها عن حالتها وعن كتلة قديمة وكتلة حديثة:
C="docker exec --user bitcoin btc-node-bitcoind bitcoin-cli -signet"
$C getblockchaininfo | grep -E '"(blocks|size_on_disk|pruned|pruneheight|prune_target_size)"'
H=$($C getblockhash 1000)
$C getblock $H 1
$C getblockheader $H | grep -E '"(height|nTx|merkleroot)"'
$C getblock $($C getblockhash 230000) 1 | grep -E '"(height|nTx|size)"'
docker exec btc-node-bitcoind du -sh /home/bitcoin/.bitcoin/signet/blocks /home/bitcoin/.bitcoin/signet/chainstateوالمخرج سوف يكون كما يلي:
"blocks": 230002,
"size_on_disk": 340877297,
"pruned": true,
"pruneheight": 229148,
"prune_target_size": 576716800,
error code: -1
error message:
Block not available (pruned data)
"height": 1000,
"merkleroot": "436bcf79ac698ba221f2f67ed7c9e42c5ddb6fb8b5f6f6322491f45bd08d8c97",
"nTx": 1,
"height": 230000,
"nTx": 863,
"size": 307069,
396M /home/bitcoin/.bitcoin/signet/blocks
550M /home/bitcoin/.bitcoin/signet/chainstateفي المخرج أعلاه لاحظ التالي:
pruneheightهو أقدم كتلة ما زالت ملفاتها على القرص، وكل ما قبلها حذف بعد أن تحققت منه العقدة، ولذلك أرجعgetblockللكتلة 1000 رسالةBlock not available (pruned data).- رأس الكتلة نفسها ما زال موجوداً، وفيه جذر Merkle وعدد المعاملات، لأن العقدة تحتفظ بكل الرؤوس دائماً، وهذا يكفي لأن تتحقق من إثبات Merkle لمعاملة قديمة إذا أعطاك أحد المعاملة وإثباتها.
- الكتلة 230000 الحديثة كاملة، وفيها 863 معاملة في 307 كيلوبايت، مقابل معاملة واحدة في الكتلة 1000.
prune_target_sizeهو 550 ميجابايت بوحدة البايت، وهو حد لملفات الكتل وملفات التراجع Undo فقط، أما مجموعة القطع غير المنفقة في مجلدchainstateفلا تقلم أبداً، لأنها الحالة التي تتحقق بها العقدة من كل معاملة جديدة. لذلك عند التخطيط للقرص احسب حجمchainstateفي الشبكة الرئيسية، وهو عدة جيجابايتات، فوق حد التقليم.
ولاحظ أيضاً أن bitcoind -help يقول إن التقليم لا يعمل مع txindex، وإن الرجوع عنه يحتاج تحميل السلسلة كلها من جديد، وهذا ما ستراه في التدريب العملي الثاني.

هل تخدم الشبكة؟ الاتصالات الواردة والرفع
عقدتنا في التجربة لا تقبل اتصالات واردة، لأننا لم ننشر منفذ الاتصال، ولذلك كانت كل اتصالاتها العشرة صادرة. وهذا يكفي لتتحقق العقدة وتخدم تطبيقاتك، فالعقدة لا تحتاج أن يتصل بها أحد لتعمل. ولكن الشبكة نفسها تعيش على العقد التي تقبل الاتصالات الواردة، لأن العقد الجديدة تحتاج من تحمل منه الكتل. فإذا قررت أن تخدم الشبكة فانشر المنفذ 8333 على الشبكة الرئيسية، أو 38333 على signet، على الواجهة العامة، وافتحه في جدار الحماية، مع بقاء RPC كما هو على 127.0.0.1.
والخوف المعتاد هنا هو الرفع، وBitcoin Core يعطيك خيارات لضبطه، ونقلها كما في bitcoind -help لإصدارنا:
maxuploadtarget: يحاول أن يبقي الرفع تحت حد معين كل 24 ساعة، والحد لا يطبق على الكتل التي صدرت خلال الأسبوع الأخير، ولا على العقد التي لها صلاحية التحميل، والقيمة الافتراضية صفر أي بلا حد. وفيgetnettotalsلعقدتنا ظهر هذا الحد في الحقلuploadtargetبقيمة صفر.blocksonly: يرفض المعاملات القادمة من العقد الأخرى ويستقبل الكتل فقط، فيقل استهلاك الشبكة كثيراً، ولكن العقدة عندها لا تعرف المعاملات المنتظرة في Mempool، فلا يناسب نظام استلام يريد أن يرى الدفعة قبل تأكيدها، ولا يناسب تقدير الرسوم.prune: العقدة المقلمة لا تخدم الكتل القديمة، لأنها لا تملكها، ولكنها تخدم الكتل الحديثة والمعاملات.
وفي قصتنا الشركة تشغل عقدة كاملة غير مقلمة لبرنامج المحاسبة، وتستطيع أن تخدم الشبكة دون أن تؤثر على عملها إذا حددت سقفاً للرفع، أما إذا كان السيرفر على اتصال بسعة محدودة أو مدفوعة بالحجم، فالعقدة دون اتصالات واردة خيار مقبول تماماً.
أي عقدة لأي حاجة؟ جدول قرار
| الحاجة | الخيار الأقرب | لماذا |
|---|---|---|
| متجر صغير يستلم دفعات قليلة | مزود API موثوق مع عدد تأكيدات معقول | تكلفة العقدة أكبر من الخطر |
| محفظة شخصية أو محفظة مشاهدة | عقدة مقلمة | تحقق كامل بقرص صغير، والمحفظة تبدأ من الآن |
| نظام استلام مدفوعات لعملاء آخرين | عقدة كاملة مع txindex | أرقامك أساس تقاريرهم، وتحتاج البحث في التاريخ |
| مستكشف كتل أو تحليلات | عقدة كاملة مع txindex وفهارس إضافية | كل التاريخ مطلوب |
| عقدة Lightning | عقدة كاملة أو مقلمة بحسب برنامج Lightning، مع ZMQ | تحتاج مراقبة السلسلة باستمرار |
| تطوير واختبار | regtest محلياً، أو signet لتجربة اتصال حقيقي | بلا قيمة ولا مئات الجيجابايت |
| سيرفر باتصال محدود | دون اتصالات واردة، أو maxuploadtarget | العقدة تعمل دون أن تخدم الشبكة |
من التجربة إلى الشبكة الرئيسية
كل ما فعلناه على signet ينتقل إلى الشبكة الرئيسية بتغييرات قليلة، ولكن الحجم يغير التخطيط. ففي ملف الإعدادات تحذف signet=1، وتنقل إعدادات RPC إلى قسم [main] أو أعلى الملف، وتغير المنفذ المنشور إلى 8332. وإذا كانت العقدة لبرنامج المحاسبة فتحذف prune=550 وتضيف txindex=1، لأن التقليم لا يعمل مع فهرس المعاملات كما يقول bitcoind -help. وأثناء المزامنة الأولى ارفع dbcache إلى ما تسمح به ذاكرة السيرفر حتى تقل الكتابات على القرص، ثم أعده إلى قيمة معتدلة بعد انتهائها، لأن الذاكرة الكبيرة تعني أيضاً وقت إيقاف أطول. وقبل أن تبدأ، قدر الوقت: سرعة عقدتنا نزلت إلى أقل من نصف السرعة الأولى عندما امتلأت كتل signet، وكتل الشبكة الرئيسية ممتلئة منذ سنوات، فخطط لأن تأخذ المزامنة الأولى وقتاً طويلاً، ولا تعد العميل بتاريخ تشغيل قبل أن تنتهي.
وفي الإنتاج اجعل مجلد البيانات على قرص منفصل عن قرص النظام، حتى لا يوقف امتلاؤه النظام كله، وراقب المساحة الحرة، وحدد للحاوية حداً من الذاكرة أكبر من dbcache بهامش واضح، حتى لا يقتلها النظام أثناء المزامنة. وإذا كان السيرفر نفسه يحتاج تأميناً من البداية فقد شرحنا ذلك في مقال تأمين خادم VPS.
تأمين واجهة RPC: أربعة أقفال
واجهة RPC هي ما يجعل العقدة مفيدة لتطبيقاتك، وهي أيضاً أخطر جزء فيها، فمن يملك صلاحية كاملة عليها يستطيع أن يوقف العقدة، وإذا كانت فيها محفظة يستطيع أن يرسل منها، ولهذا يطبع Bitcoin Core عند التشغيل تحذيراً صريحاً بأن RPC ليس آمناً للتعرض للشبكات غير الموثوقة مثل الإنترنت. لذلك لا نعتمد على قفل واحد، وإنما أربعة، وقد جربنا كل واحد منها من السيرفر نفسه بأداة curl:
PW=$(sed -n '/Your password:/{n;p}' rpcauth.out)
curl -s -u "appreader:$PW" --data-binary '{"jsonrpc":"1.0","id":"lab","method":"getblockcount","params":[]}' http://127.0.0.1:19100/; echo
curl -s -o /dev/null -w '%{http_code}\n' -u "appreader:wrong" --data-binary '{"jsonrpc":"1.0","id":"lab","method":"getblockcount","params":[]}' http://127.0.0.1:19100/
curl -s -w '\nHTTP %{http_code}\n' -u "appreader:$PW" --data-binary '{"jsonrpc":"1.0","id":"lab","method":"stop","params":[]}' http://127.0.0.1:19100/والمخرج سوف يكون كما يلي:
{"result":18367,"error":null,"id":"lab"}
401
HTTP 403المستخدم الصحيح قرأ عدد الكتل، وكلمة المرور الخاطئة أرجعت 401، والأمر stop الذي ليس في قائمة المستخدم أرجع 403 دون أن يوقف شيئاً. ولكن الوصول إلى هذه النتيجة لم يكن من أول محاولة، فقد كان في الإعدادات الأولى rpcallowip=172.16.0.0/12 لأنه المدى المعتاد لشبكات Docker، وأرجع الطلب الأول بكلمة المرور الصحيحة 403. ولمعرفة السبب فعلنا سجلات HTTP في العقدة مؤقتاً دون إعادة تشغيل، ثم أعدنا الطلب:
docker exec --user bitcoin btc-node-bitcoind bitcoin-cli -signet logging '["http"]'
curl -s -o /dev/null -w '%{http_code}\n' -u "appreader:wrong" --data-binary '{"jsonrpc":"1.0","id":"lab","method":"getblockcount","params":[]}' http://127.0.0.1:19100/
docker logs btc-node-bitcoind 2>&1 | grep -i "http" | tail -1والمخرج سوف يكون كما يلي، بعد سطر نتيجة أمر logging:
403
2026-10-28T02:13:57Z [http] HTTP request from 192.168.80.1:52604 rejected: Client network is not allowed RPC accessالطلب لم يصل إلى العقدة من 127.0.0.1، وإنما من 192.168.80.1، وهو عنوان بوابة شبكة Compose على هذا الجهاز، لأن Docker يمرر الطلبات الواردة على المنفذ المنشور عبر بوابة الشبكة، والمدى الذي اختاره Docker هنا لم يكن 172.16.0.0/12 الذي افترضناه. والحل الصحيح ليس أن نوسع rpcallowip ليشمل كل شيء، وإنما أن نحدد مدى شبكة Compose بأنفسنا كما في الملف أعلاه، ونسمح بهذا المدى وحده، فيصبح السلوك واحداً على كل سيرفر. والشكل التالي يجمع الأقفال الأربعة:

rpcallowip=0.0.0.0/0 لتحل مشكلة اتصال، والسبب أن من يصل إلى RPC بصلاحية كاملة يستطيع أن يوقف العقدة أو يستخدم محفظتها إن وجدت. وإذا احتاج تطبيق على سيرفر آخر أن يقرأ من العقدة فاستخدم شبكة خاصة VPN أو نفقاً مشفراً، ومستخدماً بقائمة أوامر محدودة كما فعلنا.ربط التطبيقات بالعقدة
بعد أن أصبح لدينا مستخدم قراءة، يستطيع برنامج المحاسبة أن يقرأ من العقدة بنفس طلب JSON-RPC الذي أرسلناه بـ curl، وكل لغة برمجة تستطيع ذلك بمكتبة HTTP عادية. والأوامر التي يحتاجها نظام استلام المدفوعات عادة قليلة: getblockcount وgetbestblockhash ليعرف أين وصلت السلسلة، وgetblock ليقرأ معاملات كل كتلة جديدة، وgetrawtransaction ليقرأ معاملة بعينها، وestimatesmartfee ليقترح معدل رسوم. وفي العقدة المقلمة لا يعمل getrawtransaction للمعاملات القديمة إلا إذا كانت في الكتل التي لم تحذف بعد، وهذا سبب إضافي لأن يكون نظام المحاسبة على عقدة كاملة مع txindex=1.
ولاحظ أننا لم ننشئ أي محفظة في هذه العقدة، فالعقدة هنا للتحقق والقراءة فقط، والمحافظ ومفاتيحها في مكان آخر كما شرحنا في مقال المحافظ والمفاتيح، وإذا احتجت محفظة مشاهدة على نفس العقدة فهي لا تحمل مفاتيح خاصة أصلاً. وبدلاً من أن يسأل البرنامج العقدة كل ثانية عن كتل جديدة، يستطيع Bitcoin Core أن ينشر تنبيهات فورية عبر ZMQ عند كل كتلة أو معاملة جديدة، وهذا ما تستخدمه أنظمة مثل شبكة Lightning التي سوف نشغلها في المقال القادم.
المخطط الكامل على السيرفر
المخطط التالي يبين ما بنيناه، وما الذي يتصل بالعالم وما الذي يبقى داخلياً:

التشغيل اليومي: الإيقاف والتحديث والنسخ والمراقبة
- الإيقاف الآمن: أوقف العقدة بالأمر
docker compose stopأوbitcoin-cli stop، وليس بقتل العملية، لأن العقدة تكتب ذاكرةdbcacheعلى القرص عند الإيقاف، ولهذا وضعناstop_grace_period: 2m. وكلما كانتdbcacheأكبر احتاج الإيقاف وقتاً أطول. وفي نهاية التجربة أوقفنا عقدتنا بالأمرdocker compose stopفطبع السجلShutdown in progress...في 04:08:15، ثم حفظ عناوين اتصالي الكتل فقط فيanchors.datوحفظ الطابور، ثمShutdown doneفي 04:08:17، أي في ثانيتين، لأن ذاكرة القطع كانت قرابة 331 ميجابايت وكتبت للتو، وعلى الشبكة الرئيسية مع ذاكرة كبيرة ممتلئة قد يأخذ الإيقاف دقائق. - التحديث: اقرأ ملاحظات الإصدار في bitcoincore.org، وخاصة قسم التغييرات التي تؤثر على الإعدادات أو الأوامر، ثم غير الوسم في ملف Compose وأعد التشغيل. وBitcoin Core يحافظ على توافق البيانات بين الإصدارات في الغالب، ولكن بعض التحديثات تعيد بناء الفهارس أو تغير قيماً افتراضية كما رأينا مع خيار الاستبدال في الإصدار 28.0.
- النسخ الاحتياطي: لا تأخذ نسخة من السلسلة نفسها، فهي تتزامن من الشبكة عند الحاجة، وخذ نسخة من ملف الإعدادات وملف Compose، ومن أي محفظة إذا وجدت. وإذا كانت المزامنة الأولى تستغرق أياماً على سيرفرك وكان التوقف مكلفاً، فاحتفظ بعقدة ثانية متزامنة بدلاً من نسخ ملفات قاعدة البيانات.
- المراقبة: راقب حالة فحص الصحة، والفرق بين
blocksوheadersفيgetblockchaininfo، وعدد الاتصالات فيgetnetworkinfo، ووقت آخر كتلة، والمساحة الحرة على القرص. وتنبيه واحد مهم: إذا لم تصل كتلة جديدة منذ أكثر من ساعة على الشبكة الرئيسية فقد يكون هذا طبيعياً أحياناً، ولكن إذا كانتheadersتتقدم وblocksلا تتقدم فالمشكلة في عقدتك. - الحماية: لا تشغل على نفس السيرفر خدمات مكشوفة للإنترنت لا تحتاجها، وتابع التنبيهات الأمنية في صفحة bitcoincore.org، فالعقدة برنامج يستقبل بيانات من غرباء طوال الوقت.
سكربت مراقبة بمستخدم القراءة
المراقبة التي ذكرناها تحتاج من يسأل العقدة بانتظام، والأفضل أن يسألها نفس مستخدم القراءة المحدود وليس مستخدم الإدارة، حتى لا تحمل أداة المراقبة صلاحية إيقاف العقدة. والسكربت التالي بلغة Python دون أي مكتبات إضافية يقرأ حالة السلسلة ويطبع سطراً واحداً، وشغلناه على نفس الجهاز بـ Python 3.13.3:
# Reads getblockchaininfo and getnetworkinfo from the node with the read-only RPC user and prints a one-line health status.
import base64, json, sys, time, urllib.request
URL = "http://127.0.0.1:19100/"
USER, PW = "appreader", open("rpcpass.txt").read().strip()
def rpc(method):
body = json.dumps({"jsonrpc": "1.0", "id": "mon", "method": method, "params": []}).encode()
req = urllib.request.Request(URL, body, {"Content-Type": "application/json"})
req.add_header("Authorization", "Basic " + base64.b64encode(f"{USER}:{PW}".encode()).decode())
try:
with urllib.request.urlopen(req, timeout=10) as r:
return json.load(r)["result"]
except urllib.error.HTTPError as e:
return {"http_error": e.code}
info = rpc("getblockchaininfo")
lag = info["headers"] - info["blocks"]
age_min = (time.time() - info["time"]) / 60
state = "SYNCING" if info["initialblockdownload"] else ("OK" if lag <= 2 else "BEHIND")
print(f"{state} blocks={info['blocks']} headers={info['headers']} lag={lag} tip_age_min={age_min:.0f} "
f"progress={info['verificationprogress']:.3f} disk_mb={info['size_on_disk']/1e6:.0f}")
print("getnetworkinfo ->", rpc("getnetworkinfo"))والمخرج سوف يكون كما يلي، وهو أثناء المزامنة الأولى:
SYNCING blocks=231039 headers=328316 lag=97277 tip_age_min=935956 progress=0.362 disk_mb=253
getnetworkinfo -> {'http_error': 403}في المخرج أعلاه لاحظ التالي:
- الحالة
SYNCINGلأن العقدة ما زالت في المزامنة الأولى، والفرق بين الرؤوس والكتل 97 ألف كتلة، وعمر آخر كتلة بالدقائق كبير جداً لأنها من يناير 2025، وكل هذا طبيعي الآن، ولكن بعد المزامنة يجب أن يكون الفرق كتلة أو اثنتين، وعمر آخر كتلة دقائق. - السطر الثاني فشل بـ 403، لأن
getnetworkinfoليس في قائمة الأوامر المسموحة لـappreader، وهذا هو القفل الرابع يعمل. فإذا أردت أن تراقب عدد الاتصالات أيضاً فأضف مستخدماً ثانياً للمراقبة بقائمة فيهاgetnetworkinfoوgetblockchaininfoفقط، ولا توسع قائمة مستخدم التطبيق لأن أداة المراقبة تحتاجها. - كلمة المرور في ملف منفصل يقرؤه السكربت، وليس داخل الكود، وفي الإنتاج تأتي من مدير الأسرار.
أخطاء شائعة
- نشر RPC للعالم: أخطر خطأ، ويحدث غالباً عندما يكتب أحدهم
-p 8332:8332ليختبر الاتصال من جهازه ثم ينساه. - قتل العقدة بدلاً من إيقافها: يكلفك إعادة فحص قاعدة البيانات عند التشغيل، وأحياناً إعادة بنائها.
- التقليم ثم الحاجة إلى التاريخ: العقدة المقلمة لا تستطيع أن تجيب عن معاملة قديمة، ولا أن تفحص محفظة قديمة من البداية، فقرر قبل المزامنة الأولى.
- قرص بطيء أو صغير: المزامنة على قرص ميكانيكي تأخذ وقتاً أطول بكثير، والقرص الذي يمتلئ يوقف العقدة، فراقب المساحة.
- قراءة نسبة التقدم خطأ:
verificationprogressمحسوبة بالمعاملات، فقد تبقى منخفضة مع أن أغلب الكتل وصلت، والمزامنة تبطئ في آخرها، فلا تقدر الوقت المتبقي من عدد الكتل. - توسيع صلاحيات مستخدم التطبيق للمراقبة: أنشئ مستخدماً منفصلاً للمراقبة بقائمته الخاصة.
- Docker Image غير معروفة المصدر: العقدة هي ما تثق به للتحقق، فإذا شغلت برنامجاً لا تعرف من بناه فقد نقلت ثقتك إلى من بناه، وهذا عكس سبب تشغيل العقدة أصلاً.
قائمة تحقق قبل الإنتاج
| البند | كيف تتحقق |
|---|---|
| البرنامج من مصدر موثوق | ملفات رسمية بعد التحقق من SHA256SUMS والتوقيعات، أو Image تعرف من بناها وتثبتها بالهاش |
| نوع العقدة مناسب للتطبيق | كاملة مع txindex لنظام يبحث في التاريخ، ومقلمة لمحفظة أو نظام يبدأ من الآن |
| القرص | SSD بمساحة تكفي النمو، مع تنبيه عند امتلاء 80% |
| RPC | منشور على 127.0.0.1، وrpcallowip بمدى شبكة محدد، ومستخدم rpcauth بقائمة أوامر |
| الإيقاف | مهلة إيقاف كافية في Compose، ولا يوجد أمر يقتل العملية في أي سكربت |
| المراقبة | فحص صحة، والفرق بين blocks وheaders، وعدد الاتصالات، والمساحة |
| التحديث | إجراء مكتوب: قراءة ملاحظات الإصدار، ثم تغيير الوسم، ثم مراقبة السجل بعد التشغيل |
إزالة التجربة
docker compose down
rm -rf data rpcauth.out
docker image rm bitcoin/bitcoin:31.1تدريب عملي
- أضف إلى العقدة مستخدماً ثانياً بالأداة
rpcauth.pyيستطيع فقطgetmempoolinfo، وتأكد أنه لا يستطيعgetblock. - غير
prune=550إلىprune=0وأعد التشغيل، واقرأ رسالة الخطأ التي تظهر في السجل، وفسر لماذا ترفض العقدة إلغاء التقليم دون إعادة المزامنة. - اكتب سكربتاً صغيراً يقرأ
getblockchaininfoكل دقيقة عبر RPC بالمستخدمappreaderويطبع الفرق بين blocks وheaders.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- العقدة الكاملة تتحقق من كل كتلة بنفسها ولا تثق بأي طرف، ولا يعرف أحد أي العناوين تهمك، وهي الخيار الصحيح لخدمة تبني عليها أرقام الآخرين.
- التقليم يوفر القرص دون أن يضعف التحقق، ولكنه يمنع قراءة الكتل القديمة وفهرسة كل المعاملات، فقرر قبل المزامنة.
- signet شبكة اختبار عامة بكتل موقعة، وتسمح بتجربة عقدة حقيقية بنفس الإعدادات تقريباً دون مئات الجيجابايت.
- التثبيت بـ Docker Compose بإصدار مثبت ومهلة إيقاف كافية وفحص صحة، وRPC منشور على 127.0.0.1 فقط.
- المزامنة الأولى تبدأ بالرؤوس مع فحص مسبق لعملها، ثم تحمل الكتل وتتحقق منها كاملة، وسرعتها يحددها عدد المعاملات في الكتل وليس عدد الكتل، فلا تقدر الوقت المتبقي من الكتل وحدها.
- تحقق من ملفات البرنامج بالهاش وتوقيعات المطورين قبل أي تثبيت أو تحديث، واجعل ذلك إجراء مكتوباً.
- أمن RPC أربعة أقفال: النشر المحلي، وrpcallowip بمدى محدد، وrpcauth، وrpcwhitelist، والسجلات تكشف أي قفل يرفض.
دليلكيف يعمل Bitcoin؟ المعاملة والمخرجات غير المنفقة UTXOنبني معاملة Bitcoin بأيدينا ونتتبعها حتى الكتلة، لنفهم لماذا لا يوجد رصيد، وأين تذهب الرسوم، ومتى تصبح الدفعة دفعة فعلاً.
دليلالتعدين Mining في Bitcoin: المكافأة والتنصيف Halvingما الذي يحسبه جهاز التعدين، ومن أين يأتي دخله ولماذا ينقص للنصف، مع تعدين كتلة باليد على عقدة Bitcoin Core.
دليلالمحافظ والمفاتيح: العبارة السرية وكيف تحمي أصولكما الذي تحفظه المحفظة فعلاً، وكيف تولد اثنتا عشرة كلمة كل مفاتيحك، وكيف تفصل شركة صغيرة بين معرفة الأرصدة والتوقيع.
دليلالتشفير في البلوكتشين: الهاش والمنحنيات والتوقيعاتكيف تصنع المفاتيح والعناوين والتوقيعات في Bitcoin وEthereum، ولماذا يكشف رقم مكرر واحد في التوقيع المفتاح الخاص.
دليلشبكة Lightning: مدفوعات Bitcoin السريعة من الداخلكيف تعمل قنوات Lightning والدفع عبر الوسطاء، مع شبكة من ثلاث عقد LND تفتح قنوات وتدفع وتغلق، وما الذي يعنيه تشغيلها.سجل التحديثات
- أكتوبر 2026: كتابة المقال، واختبار التجارب على Docker Engine 29.8.0 مع Bitcoin Core v31.1.0 على شبكة signet بعقدة مقلمة.