دمج المزوّد
شغّل مزوّداً من URnetwork داخل تطبيقك. يشارك كل تثبيت اتصاله بوصفه عميلاً في شبكتك، وتذهب أرباحه إلى محفظة Bittensor واحدة تضبطها مرة واحدة.
يشارك المزوّد اتصالَ الجهاز بالإنترنت: تخرج حركة مستخدمي URnetwork الآخرين عبره. وفي أثناء عمله يعرض تطبيقك أربعة أمور: هل يشارك الاتصال، والعملاء الذين خدمهم، والبيانات التي قدّمها، ومحفظة الدفعات للقراءة فقط. وتأتي إجماليات الأرباح من واجهتك الخلفية. وتغطي الأمثلة القابلة للتشغيل اثنتي عشرة لغة وثلاث منصات قائمة بذاتها: Electron وTauri وAndroid. وكلها تطبّق عقد المزوّد الذي يحدد القواعد بدقة؛ وتشرح هذه الصفحة كيف تتكامل الأجزاء.
من يحمل ماذا
يقسّم دمجُ المزوّد العملَ بين ثلاثة أطراف، ولا يُشحن أي مفتاح داخل تطبيق.
- واجهتك الخلفية تحمل JWT الجذر لشبكتك. فهي تُعدّ كل تثبيت بوصفه تثبيت مزوّد (provider install)، وتسلّم كل تثبيت JWT العميل المقيَّد الخاص به، وتطلب رسائل الموافقة الخاصة بالمحفظة.
- مالك coldkey يوقّع كل موافقة للمحفظة دون اتصال، بأداة محفظته الخاصة مثل btcli أو امتداد polkadot.js أو subkey. ولا يصل سرّ coldkey أبداً إلى واجهتك الخلفية ولا إلى التطبيق.
- التطبيق يشغّل جهاز Device المحلي من SDK باستخدام JWT العميل الخاص به، ويشارك الاتصال علناً، ويعرض الحالة. ولا يحمل JWT الجذر أبداً، ولا يوقّع موافقة ولا يرسلها أبداً.
| القيمة | تُحفظ لدى | الغرض |
|---|---|---|
URNETWORK_ | مخزن الأسرار في واجهتك الخلفية | يُعدّ تثبيتات المزوّد ويربطها بمحفظة الدفعات |
| عنوان coldkey للدفعات (ss58) | إعدادات واجهتك الخلفية | محفظة الدفعات الثابتة: علنية، لكن الواجهة الخلفية وحدها تضبطها |
| سرّ coldkey | مالكه، دون اتصال | يوقّع موافقات المحفظة |
client.jwt | الحالة الخاصة بالتثبيت | بيانات الاعتماد المقيَّدة للتثبيت، ويجدّدها SDK |
وتصل الدفعات إلى coldkey بوصفها دفعات من الشبكة الفرعية في Bittensor. ويقرأ التطبيق محفظة الدفعات لعرضها فقط، وليست له إعدادات دفع خاصة به.
الموافقة أولاً
عند مشاركة الاتصال تخرج حركة مستخدمين آخرين عبر اتصال المستخدم، لذا يجب أن يطلب تطبيقك موافقته قبل أن يبدأ، وأن يتيح له الإيقاف في أي وقت. ويعرض كل مثال إخلاء المسؤولية هذا حرفياً، ويثبّت العقد قيمة SHA-256 الخاصة به:
Consent disclaimer: an app that integrates a URnetwork provider must collect the user's consent before it provides.
Providing shares the user's internet connection: other URnetwork users' traffic exits through the user's device and IP address.
This example starts providing without asking, because the consent screen belongs to your app.تطبعه أمثلة سطر الأوامر عند البدء. وتعرضه تطبيقات الواجهة الرسومية وتطبيق Android بجوار عنصر التحكم الذي يبدأ المشاركة. وليست للأمثلة شاشة موافقة خاصة بها، لأن تلك الشاشة تخص تطبيقك، ولا تحدّ الأمثلة من عرض النطاق الترددي.
إعداد تثبيتات المزوّد
كل تثبيت هو تثبيت مزوّد (provider install): عميل من المستوى الأعلى في شبكتك لا يفعل سوى مشاركة اتصاله. وتنشئه واجهتك الخلفية بالطلب POST /network/ مع JWT الجذر، فترسل وصفاً ومواصفة جهاز والعلَم provide_، من دون client_id ولا source_:
{
"description": "provider user:alice:laptop-1",
"device_spec": "urnetwork-examples/ go-provider",
"provide_intent": true
}يحمل الرد client_id الجديد وby_ المقيَّد الخاص به، فتسلّمه الواجهة الخلفية إلى التثبيت بوصفه client.jwt الخاص به. ويحتفظ الخادم بالعلَم مع العميل طوال عمره:
- لا يظهر تثبيت المزوّد أبداً في قائمة الأقران في شبكتك (
GET /network/وتدفّق تغييرات الأقران)، ولا يتلقى تلك القائمة أبداً.peers - لا يُحتسب ضمن حدّ 100 عميل من المستوى الأعلى، ولا ضمن 100 عميل من المستوى الأعلى نشطين مؤخراً يُبقون قائمة الأقران مفعّلة.
- إنشاؤه مستثنى من ذلك الحد ومن فحص العملاء المتزامنين في خطتك.
لا يجعل العلَم الجهازَ يشارك اتصاله. إذ يعلن SDK بنفسه نية المشاركة (provide intent) على اتصالاته متى شارك الجهاز اتصاله علناً، وتنطبق قواعد حدّ العملاء أدناه على الاتصالات التي تعلنها. ولا تضبط التطبيقات شيئاً لذلك. وتثبيت المزوّد الذي يتصل من دون مشاركة علنية يُحتسب عميلاً عادياً.
لإعادة إصدار رمز، أرسل client_id المحفوظ مع الوصف ومواصفة الجهاز. ويتجاهل الخادم provide_ عند إعادة الإصدار، لذا أرسل العلَم عند الإنشاء فقط. والعميل الذي لم يتصل طوال 30 يوماً يُعطَّل: فيُجاب طلب إعادة إصداره بـClient does not exist.، وعندها تُعدّ الواجهة الخلفية تثبيت مزوّد جديداً.
ثمة نوعان من العملاء ليسا من تثبيتات المزوّد. فجهاز الشخص نفسه الذي يشارك اتصاله أيضاً يعلن النية بنفسه ويبقى قريناً في الشبكة، فلا تضبط العلَم له. والعميل الابن، المُعدّ بـsource_، لا يُعرض أبداً على مستخدمين آخرين بوصفه مزوّداً ولا يكسب شيئاً.
وتؤدي أداة الواجهة الخلفية بلغة Go كل ذلك بالأمر wallet provision <installation-key> <client-jwt-file>، ويعرض العقد الطلبات نفسها باستخدام curl.
المزوّدون وحدّ العملاء
المزوّدون مستثنَون من حدّ العملاء المتزامنين في خطتك ما داموا في طور التأهّل، وبعد أن يتأهّلوا. ويتأهّل المزوّد بثلاثة أمور:
- وضع المشاركة العلنية، مع تسجيل مفاتيح المشاركة الخاصة به (provide keys)؛
- اجتياز فحص لسلامة الخروج (egress-health)؛
- وبعد أن يصبح له سجلّ مدته 12 ساعة، موثوقية على مدى 12 ساعة لا تقل عن 0.7.
للمزوّد الجديد مهلة مدتها 1 ساعة كي يتأهّل، ولا تنتهي المهلة أبداً قبل محاولة فحص خروج واحد على الأقل. ويُعاد فحص المزوّد المتأهّل كل 4 ساعات. ولكل عميل محاولة تأهّل واحدة كل 8 ساعات.
المزوّد الذي لا يتأهّل يشغل مقعد عميل عادياً إن وُجد مقعد شاغر. وإن لم يوجد، فقد تقطع المنصة اتصاله. وعندها يوقف SDK كل اتصالات ذلك العميل بالمنصة مدة 15 دقيقة مضافاً إليها ما يصل إلى 5 دقائق من التأخير العشوائي (jitter)، ثم يعيد الاتصال من تلقاء نفسه. وفي أثناء ذلك يعرض التطبيق client limit, retry at HH:MM UTC: وقت إعادة المحاولة بتوقيت UTC، مقرَّباً إلى الأعلى لدقيقة كاملة. ولا تخبر المنصة العميل بشيء آخر عن التأهّل.
ربط محفظة الدفعات
محفظة الدفعات هي coldkey الثابت الخاص بك على Bittensor، بصيغة عنوان ss58. ولا يدفع الخادم لعميل مزوّد إلا عبر موافقة على ربط المحفظة وقّعها مالك coldkey. وهي نوعان:
| الموافقة | توقيع واحد يغطي | استخدمها من أجل |
|---|---|---|
| موافقة الشبكة | كل عملاء المزوّد في شبكتك، الحاليين والمستقبليين | التطبيقات التي تضمّن مزوّداً: يوقّع المالك مرة واحدة لكل شبكة |
| موافقة لكل مزوّد | عميل مزوّد واحد | عميل يكسب إلى coldkey آخر، مثل عميل يديره موقّع مستقل |
لكل عميل مزوّد ولكل حقبة، تتقدّم الموافقةُ السارية لذلك المزوّد؛ وإلا فموافقة الشبكة هي التي تدفع. وتغطي الموافقة الحقب من from_epoch الخاص بها حتى through_، بحد أقصى 65,536 حقبة، ولا تسري إلا إلى الأمام. ولتغيير coldkey، وقّع موافقة جديدة من النوع نفسه؛ وتحتفظ الحقب السابقة بالـcoldkey السابق. ولا يوجد إلغاء من دون توقيع.
تمرّ كل موافقة بأربع خطوات، ولا يأتي من خارج واجهتك الخلفية سوى التوقيع:
- اقرأ الحقبة الحالية
EمنGET /sn/epoch. - اطلب الرسالة باستخدام JWT الجذر:
POST /sn/مع coldkey والحقب منwallet/ network-consent E+1إلىE+65536، أوPOST /sn/معwallet/ consent client_idأيضاً. - يوقّع مالك coldkey البايتات الدقيقة للرسالة خلال خمس دقائق.
- أرسل coldkey والرسالة والتوقيع إلى
POST /sn/wallet، معclient_idنفسه في حالة الموافقة لكل مزوّد. ثم تحقّق من الربط باستخدامGET /sn/wallet.
التوقيع من نوع sr25519 في سياق التوقيع substrate على بايتات الرسالة بترميز UTF-8، إما خاماً وإما ملفوفاً بصيغة <Bytes>…</. والصيغة الملفوفة هي ما يوقّعه signRaw من النوع bytes في امتداد Polkadot. ويبلغ طول التوقيع 64 بايت، ويُرسل بالترميز الست عشري. وتبدأ رسالة موافقة الشبكة بـApprove URnetwork network wallet mapping، وتبدأ رسالة الموافقة لكل مزوّد بـApprove URnetwork provider wallet mapping.
وتتعامل أداة المحفظة بلغة Go مع النوعين: network-challenge وnetwork-accept لموافقة الشبكة، وchallenge وaccept لعميل واحد، وshow لعرضها. والخادم الذي لا يدعم موافقات الشبكة يرد على POST /sn/ بالرمز 404؛ وتظل الموافقات لكل مزوّد تعمل عليه. وسيأتي نمط ثالث أدنى مرتبة، هو تفويض hotkey، مع تغيير لاحق في الخادم وفي SDK. ولا تفعل التطبيقات سوى وسمه بـ(hotkey).
داخل التطبيق
يحفظ كل تثبيت حالته في دليل خاص واحد: client.jwt وinstance-id وidentity.json وlogs/ المحدودة الحجم الخاصة بـSDK. وتأخذ أمثلة سطر الأوامر المسار المطلق للدليل من URNETWORK_؛ أما تطبيقات الواجهة الرسومية وتطبيق Android فتستخدم بيانات التطبيق الخاصة بها. وعلى POSIX تكون صلاحيات الدليل 0700 وصلاحيات ملفاته 0600، ويُستبدل كل ملف ذرّياً. ويحفظ identity.json مفاتيح المزوّد عبر مرات إعادة التشغيل.
دورة حياة التطبيق قصيرة:
- اعرض إخلاء المسؤولية الخاص بالموافقة، وحمّل حالة التثبيت.
- أنشئ جهاز Device بالمُنشئ الداعم للموسّع في SDK وبمادة مفاتيح التثبيت. وفي التشغيل الأول لا توجد مادة مفاتيح: فينشئ الجهاز هوية جديدة يحفظها التطبيق باسم
identity.json. - احفظ كل رمز مجدَّد باسم
client.jwt، وتوقّف عند تسجيل الخروج. - اضبط وضع مشاركة الاتصال على العلني. وعندها يعلن SDK نية المشاركة بنفسه.
- اقرأ الحالة كل ثانية، واقرأ محفظة الدفعات عند البدء وكل 10 دقائق.
يعرض كل مثال الحقول الأربعة نفسها بالقواعد نفسها:
| الحقل | ما يعرضه |
|---|---|
| الحالة | stopped أو client limit أو paused أو providing أو starting، وتُفحص بهذا الترتيب |
| العملاء المخدومون | العملاء المختلفون الذين فتحوا عقداً مع هذا المزوّد منذ بدء التطبيق |
| البيانات المقدَّمة | البايتات المُرحَّلة للعملاء في الاتجاهين، بالوحدات الثنائية |
| محفظة الدفعات | coldkey مع نطاقه: (this provider) أو (network) أو (hotkey) أو (another provider) |
تطبع أمثلة سطر الأوامر سطر حالة كلما تغيّرت الحالة أو عدد العملاء المخدومين أو محفظة الدفعات، وإلا فمرة كل دقيقة:
status: providing | clients served: 3 | data provided: 12.4 MiB | payout wallet: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY (network)تتطلب الحالة providing أن تكون المشاركة مفعّلة وأن يكون هناك اتصال بالمنصة. وتعرض المحفظة checking قبل القراءة الأولى، وunavailable إن فشلت تلك القراءة، وnot set حين لا تكون هناك محفظة مربوطة. والعنوان أعلاه هو حساب التطوير العام في Substrate، ويُستخدم بيانات نموذجية فقط. وتحدد المتجهات المرجعية في العقد كل نص بدقة.
دور الموسّع
في أثناء المشاركة، يشغّل الجهاز أيضاً دور الموسّع الخاص بالمزوّد، وهو مفعّل افتراضياً. ويستمع على TCP 443 وعلى UDP 443 و4053، كي يتصل عبر هذا المزوّد العملاءُ الذين لا يستطيعون الوصول إلى المنصة مباشرة. ولا يستمع على UDP 53 أيضاً إلا مزوّد سطر الأوامر الخاص بالشبكة الفرعية (provider provide)، ويجرّب العملاء كلاً من UDP 53 و4053. وقد يطلب Windows وmacOS من المستخدم في المرة الأولى السماح بالاتصالات الواردة. وعلى Linux، تواصل العملية التي لا تملك صلاحية ربط هذه المنافذ المشاركةَ من دون هذا الدور. ولا تتضمن بناءات SDK لنظامَي Android وiOS هذا الدور.
إعداد واحد يوقف القيمة الافتراضية: DefaultProvideExtender بالقيمة false.
| طبقة الربط | الإيقاف افتراضياً |
|---|---|
| Go | DeviceLocalSettings.، أو false وسيطاً أخيراً للمُنشئ |
| gomobile | Sdk. (Java وKotlin) أو SdkNewDeviceLocalWithProvideExtender (Swift)، مع القيمة الافتراضية false |
| C ABI | urnet_ مع default_ بالقيمة false |
| C++ | urnet:: مع القيمة الافتراضية false |
ويظل اختيار المستخدم نفسه، المضبوط بـSetProvideExtender، مقدَّماً على القيمة الافتراضية. أما ProvideExtenderEnabled بالقيمة false فهو المفتاح الرئيسي: لا يعمل الدور أبداً. والإعدادان، والمُنشئ الذي يقبلهما، موجودة في SDK بدءاً من أول إصدار بعد sdk c638dfa8.
التشغيل في الخلفية
تتشارك أمثلة سطر الأوامر ثلاثة قوالب للتشغيل في الخلفية: خدمة مستخدم لـsystemd على Linux، ووكيل launchd على macOS، ومهمة مجدولة على Windows. ويسمّي كل قالب دليل حالة التثبيت، لذا يحتوي تعريف الخدمة على مسار، لا على بيانات اعتماد أبداً. وتستخدم كل الأمثلة رموز الخروج نفسها:
| رمز الخروج | المعنى |
|---|---|
| 0 | إيقاف مطلوب، مثل Ctrl-C أو SIGTERM |
| 78 | مشكلة في الإعدادات أو بيانات الاعتماد لا تصلحها إعادة التشغيل |
| 1 | أي فشل آخر |
تعيد أدوات الإشراف تشغيل المزوّد بعد الفشل، لكن ليس بعد 0 أو 78. وليس لدى launchd قاعدة لكل رمز خروج: فبعد 78 يواصل المحاولة مرة كل دقيقة إلى أن تُصلَح الحالة.
وتواصل تطبيقات الواجهة الرسومية وتطبيق Android المشاركة من تلقاء نفسها. فيبقى Electron وTauri في علبة النظام عند إغلاق النافذة، ويعرضان البدء عند تسجيل الدخول. ويشارك Android الاتصال من خدمة في المقدّمة (foreground service) يبدؤها المستخدم، ويوقف المشاركة مؤقتاً على الشبكات محدودة الاستهلاك.
الأمثلة
يتضمن ملف README لكل مثال خطوات البناء والتشغيل لكل نظام تشغيل يدعمه، واختباراً ذاتياً لا يحتاج إلى بيانات اعتماد. وتعمل أمثلة سطر الأوامر على Windows وmacOS وLinux. ومزوّد Go هو التطبيق المرجعي.
| المنصة | المثال | مبني على |
|---|---|---|
| C | مزوّد C | C ABI |
| C++ | مزوّد C++ | C ABI |
| C# | مزوّد C# | C ABI عبر .NET |
| Go | مزوّد Go | Go SDK |
| Java | مزوّد Java | C ABI عبر JNA |
| JavaScript | مزوّد JavaScript | Node والمرافق الأصلي (native companion) |
| Kotlin | مزوّد Kotlin | C ABI عبر JNA |
| Python | مزوّد Python | C ABI عبر ctypes |
| Ruby | مزوّد Ruby | C ABI عبر FFI |
| Rust | مزوّد Rust | C ABI عبر حزمة crate الخاصة بـRust |
| Swift | مزوّد Swift | C ABI عبر وحدة C في SwiftPM |
| TypeScript | مزوّد TypeScript | Node والمرافق الأصلي |
| Electron | مزوّد Electron | تطبيق سطح مكتب على المرافق الأصلي |
| Tauri | مزوّد Tauri | تطبيق سطح مكتب يشغّل حزمة Rust في العملية نفسها |
| Android | مزوّد Android | ملف AAR من gomobile في خدمة في المقدّمة |
توفّر الإصدار: تحتاج أمثلة المزوّد إلى إصدار من SDK يتضمن واجهات API الخاصة بالمزوّد: المُنشئ الداعم للموسّع، وحالة حدّ العملاء، ونطاق موافقة المحفظة. وهي موجودة في SDK بدءاً من أول إصدار بعد sdk c638dfa8. ويشرح تثبيت SDK الحزم، ويضم متصفّح الأمثلة بقية أمثلة كل لغة.