"Non-custodial" आमतौर पर एक मार्केटिंग शब्द होता है। सटीक ढंग से इस्तेमाल किया जाए तो यह एक आर्किटेक्चरल बाध्यता है, और इस तरह की बाध्यताएँ दिलचस्प इसलिए होती हैं क्योंकि वे काम बढ़ाने के बजाय काम को मिटा देती हैं।
एक trading bot को आपकी ओर से ऑर्डर लगाने की ज़रूरत होती है। एक्सचेंज इसे API keys के ज़रिए संभव बनाते हैं, और API keys के साथ scopes जुड़े होते हैं: read, trade, और ज़्यादातर जगहों पर withdraw। तो डिज़ाइन का सवाल यह नहीं है कि अनुमतियाँ माँगी जाएँ या नहीं। सवाल यह है कि कौन सी माँगी जाएँ, और जब आपको माँगी गई से ज़्यादा मिल जाए तो क्या किया जाए।
मुख्य बातें
- किसी अनुमति का इस्तेमाल न करने का वादा, उस अनुमति के पास ही न होने से कमज़ोर है। हम निकासी अनुमति वाली keys को स्वीकार करके नज़रअंदाज़ करने के बजाय उन्हें अस्वीकार कर देते हैं।
- अस्वीकार करना, भरोसा करने से मज़बूत गारंटी है, क्योंकि यह इस बात पर निर्भर नहीं करता कि हमारा कोड सही है या हमारी टीम ईमानदार है।
- अगर कोई connection flow यूज़र के अकाउंट में कुछ लिखता है, तो वे writes स्वीकार करने के फ़ैसले के बाद होने चाहिए, पहले नहीं। वरना आप किसी को मना करने के रास्ते में ही उसकी settings बदल देते हैं।
- फंड न रखना कठिन समस्याओं की एक पूरी श्रेणी को मिटा देता है: कोई custody ledger नहीं, कोई reconciliation नहीं, कोई withdrawal queue नहीं।
- इसकी जगह जो आता है वह ज़्यादा संकरा और तीखा है: आपका सिस्टम real time में सही होना चाहिए, क्योंकि यह कभी कुछ पलट नहीं सकता।
1. अनुमति का इस्तेमाल न करने के वादे से बेहतर है उसे अस्वीकार करना
सीधा तरीका यह है कि यूज़र से read और trade scopes वाली key माँगी जाए और withdrawal endpoints को बस कभी कॉल न किया जाए। इंडस्ट्री का ज़्यादातर हिस्सा यही करता है, और यह अनुचित नहीं है।
हम इससे सख़्त कुछ करते हैं। अगर कोई key निकासी अनुमति के साथ आती है, तो उसे अस्वीकार कर दिया जाता है। यूज़र को एक संदेश दिखता है जो ठीक-ठीक बताता है कि क्या करना है:
इस API key में निकासी अनुमति चालू है। Freya कभी फंड नहीं निकालता, इसलिए उस अनुमति वाली keys स्वीकार नहीं की जातीं। कृपया सिर्फ़ Read और Trade वाली key बनाएँ।
यही नियम हमारे समर्थित तीनों एक्सचेंजों पर लागू होता है। कोई override नहीं है और कोई admin अपवाद नहीं है।
पहली नज़र में यह बेवजह की अड़चन लगता है। key तो काम कर जाती। यूज़र ने उसे बनाने में मेहनत लगाई। उसे अस्वीकार करने का मतलब है यूज़र को एक और key बनाने के लिए एक्सचेंज पर वापस भेजना।
वजह यह है कि दोनों डिज़ाइन अलग-अलग वादे करते हैं, और यह फ़र्क़ सिर्फ़ आपके सबसे बुरे दिन ही दिखता है।
- "हम withdrawal endpoints कभी कॉल नहीं करते" यह हमारे व्यवहार के बारे में दावा है। यह तब तक टिकता है जब तक हमारा कोड सही है, हमारी dependencies साफ़ हैं, और deploy पहुँच वाला हर व्यक्ति ईमानदार रहता है। ये उम्मीद करने लायक बातें हैं। पर ये ऐसी बातें नहीं हैं जिन्हें यूज़र जाँच सके।
- "key निकासी नहीं कर सकती" यह key के बारे में दावा है। यह इस बात से बेपरवाह टिकता है कि हमारा कोड क्या करता है, क्योंकि इसे एक्सचेंज लागू करता है, और यूज़र इसकी पुष्टि अपनी ही एक्सचेंज settings में ख़ुद कर सकता है।
दूसरा कमज़ोर अनुरोध है पर मज़बूत गारंटी। ऐसे डिज़ाइन को प्राथमिकता दें जिनकी सुरक्षा-विशेषता आपके अलावा कोई और लागू करता हो, ख़ासकर तब जब यूज़र के पास आपकी तरफ़ का ऑडिट करने का कोई रास्ता न हो।
एक छोटा फ़ायदा भी है जो व्यवहार में मायने रखता है: यह failure mode को तेज़ आवाज़ वाला बना देता है। ज़्यादा scope वाली key connection के समय ही पकड़ी जाती है, ऐसे यूज़र के सामने जो सक्रिय रूप से सेटअप कर रहा है और एक मिनट में इसे ठीक कर सकता है। इसका विकल्प यह है कि ज़्यादा scope वाली key वहीं ठीक-ठाक काम करती रहे, और तब तक किसी को कुछ पता न चले जब तक वह मायने न रखने लगे।
2. कुछ भी छूने से पहले मना कर दें
यह वह बारीकी है जिसे मैं किसी दूसरे इंजीनियर से सबसे ज़्यादा साथ ले जाने को कहूँगा, क्योंकि इसे ग़लत करना आसान है और ग़लत होने पर यह लगभग अदृश्य होता है।
एक्सचेंज को कनेक्ट करना पूरी तरह read ऑपरेशन नहीं है। जगह के आधार पर, किसी अकाउंट को काम करने लायक हालत में लाने के लिए उसमें कुछ लिखना पड़ सकता है: account mode बदलना, position mode सेट करना, वगैरह। ये सेटअप के हिस्से के रूप में होने वाले वाजिब कदम हैं।
अब क्रम पर ग़ौर करें। अगर permission check उन writes के बाद होती है, तो अस्वीकृत key फिर भी एक निशान छोड़ जाती है। यूज़र ने आपको एक credential सौंपा, आपने उसके अकाउंट में settings बदल दीं, और फिर आपने उसे मना कर दिया। उसकी तरफ़ से, कुछ भी यह नहीं समझाता कि उसका एक्सचेंज कॉन्फ़िगरेशन उससे अलग क्यों है जैसा उसने छोड़ा था।
तो जिस नियम पर हम पहुँचे: एक अस्वीकृत credential को यूज़र का अकाउंट ठीक वैसा ही छोड़ना चाहिए जैसा वह मिला था। कुछ भी लिखने वाला कदम स्वीकार करने के फ़ैसले के बाद आता है, उससे पहले कभी नहीं। अस्वीकृति बिना किसी side effect के पहुँच योग्य होनी चाहिए।
आम शब्दों में, यह बस किसी और के सिस्टम पर लागू किया गया transactional सोच है। आप किसी third-party अकाउंट में हुई write को पलट नहीं सकते, इसलिए क्रम ही आपकी एकमात्र सुरक्षा है। जब आप पलट नहीं सकते, तो क्रम सावधानी से रखें।
एक implementation नोट जिसने हमें बग्स की एक पूरी श्रेणी से बचाया: अस्वीकृति को एक ख़ास flag के बजाय एक invalid result के रूप में व्यक्त किया जाता है, इसलिए validation result का उपभोग करने वाला हर path इसे बिना जाने कि यह मौजूद है, संभाल लेता है। हमारे पास दो connection flows हैं, और दोनों को यही अस्वीकृति विरासत में मिलती है क्योंकि किसी को भी इसके बारे में पूछना याद रखने की ज़रूरत नहीं थी। किसी नियम को ऐसे type में एन्कोड करना जिसे सभी consumers पहले से पढ़ते हैं, उसे दस्तावेज़ बनाकर उम्मीद करने से बेहतर है।
3. यह बाध्यता क्या मिटा देती है
यहाँ यह सौदा साफ़ तौर पर फ़ायदेमंद हो जाता है।
फंड कभी यूज़र के एक्सचेंज अकाउंट से बाहर नहीं जाते। bots read-and-trade keys से ट्रेड करते हैं, और बैलेंस वहीं रहता है जहाँ शुरू हुआ था। इससे जो निकलता है वह उन चीज़ों की एक लंबी सूची है जो हमारे सिस्टम में बस मौजूद ही नहीं हैं:
- कोई custody ledger नहीं, इसलिए किसी आंतरिक बैलेंस और बाहरी बैलेंस के बीच कोई reconciliation नहीं
- कोई withdrawal queue नहीं, कोई approval workflow नहीं, ट्रेडिंग पूँजी पर कोई दैनिक सीमा नहीं
- उस पूँजी के लिए कोई hot और cold wallet विभाजन नहीं, और उसके आसपास कोई key management नहीं
- अगर कंपनी ग़ायब हो जाए तो यूज़र के फंड का क्या होगा, इसका कोई सवाल नहीं
इनमें से हर एक custodial प्रोडक्ट में एक पूरा subsystem है, और हर एक ऐसी जगह है जहाँ पैसा ग़ायब हो सकता है। ये यहाँ इसलिए अनुपस्थित नहीं हैं कि हमने इन्हें हल कर लिया, बल्कि इसलिए कि हमने वह चीज़ कभी उठाई ही नहीं जो इन्हें पैदा करती है।
हालाँकि मैं व्यापक होने के बजाय सटीक रहना चाहता हूँ: यह ट्रेडिंग पूँजी पर लागू होता है। सब्सक्रिप्शन के लिए इस्तेमाल होने वाले प्लेटफ़ॉर्म बैलेंस एक अलग मामला है जिसकी अपनी अलग हैंडलिंग है। non-custodial विशेषता उस पैसे के बारे में है जो ट्रेड करता है, और वही पैसा सबसे ज़्यादा मायने रखता है।
4. यह क्या ठीक नहीं करती
एक बाध्यता जो समस्याएँ हटाती है, वह उन औज़ारों को भी हटा देती है जिनसे आप ग़लतियाँ सुधारते, और इस बारे में ईमानदार रहना ज़रूरी है।
ट्रेड अंतिम होते हैं। custody के साथ, एक आंतरिक ट्रांसफ़र को सैद्धांतिक रूप से पलटा जा सकता है। किसी और के एक्सचेंज अकाउंट पर भरा गया ऑर्डर पलटा नहीं जा सकता। सारा इंजीनियरिंग बोझ रिक्वेस्ट के निकलने के बाद reconcile करने के बजाय उसके निकलने से पहले सही होने पर आ जाता है। यह जितना सुनाई देता है उससे कठिन अनुशासन है, और यही वजह है कि हमारा इतना सारा काम validation, precision, और idempotency के बारे में है।
Trade अनुमति भी असली अनुमति है। लीक हुई read-and-trade key किसी अकाउंट को किसी बाहरी पते पर खाली नहीं कर सकती, जो नतीजा लोग असल में डरते हैं। पर वह ऑर्डर लगा सकती है। नुक़सान का दायरा सीमित है, शून्य नहीं, और इसे शून्य बताना बेईमानी होगी।
यूज़र पर अब भी ज़िम्मेदारियाँ हैं। key rotation, सेवाओं के बीच keys दोबारा इस्तेमाल न करना, और यह देखना कि उसी अकाउंट तक और किसकी पहुँच है, ये उसी की ज़िम्मेदारियाँ रहती हैं। हमारा आर्किटेक्चर नुक़सान को सीमित करता है; यह key को हानिरहित नहीं बनाता।
किसी दूसरी टीम को मैं क्या कहूँगा
- सबसे संकरा scope माँगें, फिर पुष्टि करें कि आपको वही मिला। कम माँगना, कम पाने के बराबर नहीं है।
- ऐसी गारंटियों को प्राथमिकता दें जो आपके अपने कोड के बाहर लागू होती हैं, ख़ासकर वे जिन्हें यूज़र आप पर भरोसा किए बिना जाँच सके।
- अस्वीकृति को side-effect मुक्त बनाएँ। अगर आपका flow किसी third-party अकाउंट में लिखता है, तो लिखने वाली हर चीज़ स्वीकार करने के फ़ैसले के बाद आती है।
- नियम को वहीं एन्कोड करें जहाँ consumers पहले से देखते हैं। किसी साझा type में व्यक्त किया गया नियम हर caller को उसे याद रखने के लिए मजबूर नहीं करता।
बड़ी बात यह है कि "हम X नहीं करेंगे" और "हम X नहीं कर सकते" एक लैंडिंग पेज पर एक जैसे पढ़े जाते हैं और किसी incident में पूरी तरह अलग होते हैं। जब भी आप किसी वादे को पहली श्रेणी से दूसरी श्रेणी में ले जा सकें, लागत आम तौर पर कम होती है और फ़ायदा ठीक उसी वक़्त दिखता है जब बाक़ी सब ग़लत हो रहा होता है।
