للمطوّرين

دمج المزوّد

قراءة في 10 دقائقعرض بصيغة Markdown ↗

شغّل مزوّداً من URnetwork داخل تطبيقك. يشارك كل تثبيت اتصاله بوصفه عميلاً في شبكتك، وتذهب أرباحه إلى محفظة Bittensor واحدة تضبطها مرة واحدة.

دمج المزوّد — تُعدّ واجهتك الخلفية كل تثبيت مزوّد لتطبيقك على جهاز المستخدم، ويشارك التطبيق اتصاله عبر URnetwork مع العملاء، وموافقة الشبكة من مالك coldkey، الموقَّعة مرة واحدة دون اتصال والمقدَّمة من واجهتك الخلفية، توجّه دفعات الشبكة الفرعية إلى coldkey الدفعات الثابت؛ ولا يُشحن أي مفتاح داخل التطبيق

يشارك المزوّد اتصالَ الجهاز بالإنترنت: تخرج حركة مستخدمي URnetwork الآخرين عبره. وفي أثناء عمله يعرض تطبيقك أربعة أمور: هل يشارك الاتصال، والعملاء الذين خدمهم، والبيانات التي قدّمها، ومحفظة الدفعات للقراءة فقط. وتأتي إجماليات الأرباح من واجهتك الخلفية. وتغطي الأمثلة القابلة للتشغيل اثنتي عشرة لغة وثلاث منصات قائمة بذاتها: Electron وTauri وAndroid. وكلها تطبّق عقد المزوّد الذي يحدد القواعد بدقة؛ وتشرح هذه الصفحة كيف تتكامل الأجزاء.

من يحمل ماذا

يقسّم دمجُ المزوّد العملَ بين ثلاثة أطراف، ولا يُشحن أي مفتاح داخل تطبيق.

  • واجهتك الخلفية تحمل JWT الجذر لشبكتك. فهي تُعدّ كل تثبيت بوصفه تثبيت مزوّد (provider install)، وتسلّم كل تثبيت JWT العميل المقيَّد الخاص به، وتطلب رسائل الموافقة الخاصة بالمحفظة.
  • مالك coldkey يوقّع كل موافقة للمحفظة دون اتصال، بأداة محفظته الخاصة مثل btcli أو امتداد polkadot.js أو subkey. ولا يصل سرّ coldkey أبداً إلى واجهتك الخلفية ولا إلى التطبيق.
  • التطبيق يشغّل جهاز Device المحلي من SDK باستخدام JWT العميل الخاص به، ويشارك الاتصال علناً، ويعرض الحالة. ولا يحمل JWT الجذر أبداً، ولا يوقّع موافقة ولا يرسلها أبداً.
القيمةتُحفظ لدىالغرض
URNETWORK_ROOT_JWTمخزن الأسرار في واجهتك الخلفيةيُعدّ تثبيتات المزوّد ويربطها بمحفظة الدفعات
عنوان coldkey للدفعات (ss58)إعدادات واجهتك الخلفيةمحفظة الدفعات الثابتة: علنية، لكن الواجهة الخلفية وحدها تضبطها
سرّ coldkeyمالكه، دون اتصاليوقّع موافقات المحفظة
client.jwtالحالة الخاصة بالتثبيتبيانات الاعتماد المقيَّدة للتثبيت، ويجدّدها SDK

وتصل الدفعات إلى coldkey بوصفها دفعات من الشبكة الفرعية في Bittensor. ويقرأ التطبيق محفظة الدفعات لعرضها فقط، وليست له إعدادات دفع خاصة به.

الموافقة أولاً

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

Text
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/auth-client مع JWT الجذر، فترسل وصفاً ومواصفة جهاز والعلَم provide_intent، من دون client_id ولا source_client_id:

JSON
{
  "description": "provider user:alice:laptop-1",
  "device_spec": "urnetwork-examples/go-provider",
  "provide_intent": true
}

يحمل الرد client_id الجديد وby_client_jwt المقيَّد الخاص به، فتسلّمه الواجهة الخلفية إلى التثبيت بوصفه client.jwt الخاص به. ويحتفظ الخادم بالعلَم مع العميل طوال عمره:

  • لا يظهر تثبيت المزوّد أبداً في قائمة الأقران في شبكتك (GET /network/peers وتدفّق تغييرات الأقران)، ولا يتلقى تلك القائمة أبداً.
  • لا يُحتسب ضمن حدّ 100 عميل من المستوى الأعلى، ولا ضمن 100 عميل من المستوى الأعلى نشطين مؤخراً يُبقون قائمة الأقران مفعّلة.
  • إنشاؤه مستثنى من ذلك الحد ومن فحص العملاء المتزامنين في خطتك.

لا يجعل العلَم الجهازَ يشارك اتصاله. إذ يعلن SDK بنفسه نية المشاركة (provide intent) على اتصالاته متى شارك الجهاز اتصاله علناً، وتنطبق قواعد حدّ العملاء أدناه على الاتصالات التي تعلنها. ولا تضبط التطبيقات شيئاً لذلك. وتثبيت المزوّد الذي يتصل من دون مشاركة علنية يُحتسب عميلاً عادياً.

لإعادة إصدار رمز، أرسل client_id المحفوظ مع الوصف ومواصفة الجهاز. ويتجاهل الخادم provide_intent عند إعادة الإصدار، لذا أرسل العلَم عند الإنشاء فقط. والعميل الذي لم يتصل طوال 30 يوماً يُعطَّل: فيُجاب طلب إعادة إصداره بـClient does not exist.، وعندها تُعدّ الواجهة الخلفية تثبيت مزوّد جديداً.

ثمة نوعان من العملاء ليسا من تثبيتات المزوّد. فجهاز الشخص نفسه الذي يشارك اتصاله أيضاً يعلن النية بنفسه ويبقى قريناً في الشبكة، فلا تضبط العلَم له. والعميل الابن، المُعدّ بـsource_client_id، لا يُعرض أبداً على مستخدمين آخرين بوصفه مزوّداً ولا يكسب شيئاً.

وتؤدي أداة الواجهة الخلفية بلغة 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_epoch، بحد أقصى 65,536 حقبة، ولا تسري إلا إلى الأمام. ولتغيير coldkey، وقّع موافقة جديدة من النوع نفسه؛ وتحتفظ الحقب السابقة بالـcoldkey السابق. ولا يوجد إلغاء من دون توقيع.

تمرّ كل موافقة بأربع خطوات، ولا يأتي من خارج واجهتك الخلفية سوى التوقيع:

  1. اقرأ الحقبة الحالية E من GET /sn/epoch.
  2. اطلب الرسالة باستخدام JWT الجذر: POST /sn/wallet/network-consent مع coldkey والحقب من E+1 إلى E+65536، أو POST /sn/wallet/consent مع client_id أيضاً.
  3. يوقّع مالك coldkey البايتات الدقيقة للرسالة خلال خمس دقائق.
  4. أرسل coldkey والرسالة والتوقيع إلى POST /sn/wallet، مع client_id نفسه في حالة الموافقة لكل مزوّد. ثم تحقّق من الربط باستخدام GET /sn/wallet.

التوقيع من نوع sr25519 في سياق التوقيع substrate على بايتات الرسالة بترميز UTF-8، إما خاماً وإما ملفوفاً بصيغة <Bytes>…</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/wallet/network-consent بالرمز 404؛ وتظل الموافقات لكل مزوّد تعمل عليه. وسيأتي نمط ثالث أدنى مرتبة، هو تفويض hotkey، مع تغيير لاحق في الخادم وفي SDK. ولا تفعل التطبيقات سوى وسمه بـ(hotkey).

داخل التطبيق

يحفظ كل تثبيت حالته في دليل خاص واحد: client.jwt وinstance-id وidentity.json وlogs/ المحدودة الحجم الخاصة بـSDK. وتأخذ أمثلة سطر الأوامر المسار المطلق للدليل من URNETWORK_PROVIDER_STATE_DIR؛ أما تطبيقات الواجهة الرسومية وتطبيق Android فتستخدم بيانات التطبيق الخاصة بها. وعلى POSIX تكون صلاحيات الدليل 0700 وصلاحيات ملفاته 0600، ويُستبدل كل ملف ذرّياً. ويحفظ identity.json مفاتيح المزوّد عبر مرات إعادة التشغيل.

دورة حياة التطبيق قصيرة:

  1. اعرض إخلاء المسؤولية الخاص بالموافقة، وحمّل حالة التثبيت.
  2. أنشئ جهاز Device بالمُنشئ الداعم للموسّع في SDK وبمادة مفاتيح التثبيت. وفي التشغيل الأول لا توجد مادة مفاتيح: فينشئ الجهاز هوية جديدة يحفظها التطبيق باسم identity.json.
  3. احفظ كل رمز مجدَّد باسم client.jwt، وتوقّف عند تسجيل الخروج.
  4. اضبط وضع مشاركة الاتصال على العلني. وعندها يعلن SDK نية المشاركة بنفسه.
  5. اقرأ الحالة كل ثانية، واقرأ محفظة الدفعات عند البدء وكل 10 دقائق.

يعرض كل مثال الحقول الأربعة نفسها بالقواعد نفسها:

الحقلما يعرضه
الحالةstopped أو client limit أو paused أو providing أو starting، وتُفحص بهذا الترتيب
العملاء المخدومونالعملاء المختلفون الذين فتحوا عقداً مع هذا المزوّد منذ بدء التطبيق
البيانات المقدَّمةالبايتات المُرحَّلة للعملاء في الاتجاهين، بالوحدات الثنائية
محفظة الدفعاتcoldkey مع نطاقه: (this provider) أو (network) أو (hotkey) أو (another provider)

تطبع أمثلة سطر الأوامر سطر حالة كلما تغيّرت الحالة أو عدد العملاء المخدومين أو محفظة الدفعات، وإلا فمرة كل دقيقة:

Text
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.

طبقة الربطالإيقاف افتراضياً
GoDeviceLocalSettings.DefaultProvideExtender = false، أو false وسيطاً أخيراً للمُنشئ
gomobileSdk.newDeviceLocalWithProvideExtender ‏(Java وKotlin) أو SdkNewDeviceLocalWithProvideExtender ‏(Swift)، مع القيمة الافتراضية false
C ABIurnet_new_device_local_with_provide_extender مع default_provide_extender بالقيمة false
C++‎urnet::newDeviceLocalWithProvideExtender مع القيمة الافتراضية 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مزوّد CC ABI
C++‎مزوّد C++‎C ABI
C#‎مزوّد C#‎C ABI عبر .NET
Goمزوّد GoGo SDK
Javaمزوّد JavaC ABI عبر JNA
JavaScriptمزوّد JavaScriptNode والمرافق الأصلي (native companion)
Kotlinمزوّد KotlinC ABI عبر JNA
Pythonمزوّد PythonC ABI عبر ctypes
Rubyمزوّد RubyC ABI عبر FFI
Rustمزوّد RustC ABI عبر حزمة crate الخاصة بـRust
Swiftمزوّد SwiftC ABI عبر وحدة C في SwiftPM
TypeScriptمزوّد TypeScriptNode والمرافق الأصلي
Electronمزوّد Electronتطبيق سطح مكتب على المرافق الأصلي
Tauriمزوّد Tauriتطبيق سطح مكتب يشغّل حزمة Rust في العملية نفسها
Androidمزوّد Androidملف AAR من gomobile في خدمة في المقدّمة

توفّر الإصدار: تحتاج أمثلة المزوّد إلى إصدار من SDK يتضمن واجهات API الخاصة بالمزوّد: المُنشئ الداعم للموسّع، وحالة حدّ العملاء، ونطاق موافقة المحفظة. وهي موجودة في SDK بدءاً من أول إصدار بعد sdk c638dfa8. ويشرح تثبيت SDK الحزم، ويضم متصفّح الأمثلة بقية أمثلة كل لغة.