वेब3 ॲप्ससाठी सर्व्हर घटक आणि एजंट्स
परिचय
बहुतेक प्रकरणांमध्ये, विकेंद्रित ॲप्लिकेशन (dapp) सॉफ्टवेअर वितरित करण्यासाठी सर्व्हरचा वापर करते, परंतु सर्व प्रत्यक्ष संवाद क्लायंट (सामान्यतः, वेब ब्राउझर) आणि ब्लॉकचेन दरम्यान होतो.
तथापि, काही प्रकरणांमध्ये ॲप्लिकेशनला स्वतंत्रपणे चालणाऱ्या सर्व्हर घटकाचा फायदा होऊ शकतो. असा सर्व्हर घटनांना आणि API सारख्या इतर स्रोतांकडून येणाऱ्या विनंत्यांना व्यवहार जारी करून प्रतिसाद देण्यास सक्षम असेल.
असा सर्व्हर पूर्ण करू शकेल अशी अनेक संभाव्य कार्ये आहेत.
-
गुप्त स्थितीचा धारक. गेमिंगमध्ये अनेकदा गेमला माहीत असलेली सर्व माहिती खेळाडूंना उपलब्ध नसणे उपयुक्त ठरते. तथापि, ब्लॉकचेनवर कोणतीही गुपिते नसतात, ब्लॉकचेनमध्ये असलेली कोणतीही माहिती कोणालाही शोधणे सोपे असते. म्हणून, जर गेमच्या स्थितीचा काही भाग गुप्त ठेवायचा असेल, तर तो इतरत्र संग्रहित केला पाहिजे (आणि शक्यतो त्या स्थितीचे परिणाम झिरो-नॉलेज प्रुफ्स वापरून सत्यापित केले जावेत).
-
केंद्रीकृत ओरॅकल. जर धोके पुरेसे कमी असतील, तर एक बाह्य सर्व्हर जो ऑनलाइन काही माहिती वाचतो आणि नंतर ती चेनवर पोस्ट करतो, तो ओरॅकल म्हणून वापरण्यासाठी पुरेसा चांगला असू शकतो.
-
एजंट. ब्लॉकचेनवर सक्रिय करण्यासाठी व्यवहाराशिवाय काहीही होत नाही. जेव्हा संधी मिळते तेव्हा आर्बिट्रेज सारख्या क्रिया करण्यासाठी सर्व्हर वापरकर्त्याच्या वतीने कार्य करू शकतो.
नमुना प्रोग्राम
तुम्ही GitHub वर (नवीन टॅबमध्ये उघडते) एक नमुना सर्व्हर पाहू शकता. हा सर्व्हर या कॉन्ट्रॅक्टमधून (नवीन टॅबमध्ये उघडते) येणाऱ्या घटना ऐकतो, जे Hardhat च्या Greeter ची सुधारित आवृत्ती आहे. जेव्हा ग्रीटिंग (अभिवादन) बदलले जाते, तेव्हा तो ते परत बदलतो.
ते चालवण्यासाठी:
-
रिपॉझिटरी क्लोन करा.
git clone https://github.com/qbzzt/20240715-server-component.git cd 20240715-server-component -
आवश्यक पॅकेजेस इन्स्टॉल करा. जर तुमच्याकडे ते आधीपासून नसेल, तर प्रथम Node इन्स्टॉल करा (नवीन टॅबमध्ये उघडते).
npm install -
होल्स्की टेस्टनेटवर ETH असलेल्या खात्याची खाजगी की निर्दिष्ट करण्यासाठी
.envसंपादित करा. जर तुमच्याकडे होल्स्कीवर ETH नसेल, तर तुम्ही हा फॉसेट वापरू शकता (नवीन टॅबमध्ये उघडते).PRIVATE_KEY=0x <private key goes here> -
सर्व्हर सुरू करा.
npm start -
ब्लॉक एक्सप्लोररवर (नवीन टॅबमध्ये उघडते) जा, आणि खाजगी की असलेल्या पत्त्यापेक्षा वेगळा पत्ता वापरून ग्रीटिंग सुधारा. ग्रीटिंग आपोआप परत सुधारले जाते हे पहा.
हे कसे काम करते?
सर्व्हर घटक कसा लिहायचा हे समजून घेण्याचा सर्वात सोपा मार्ग म्हणजे नमुना ओळीनुसार तपासणे.
src/app.ts
प्रोग्रामचा बहुतांश भाग src/app.ts (नवीन टॅबमध्ये उघडते) मध्ये समाविष्ट आहे.
आवश्यक ऑब्जेक्ट्स तयार करणे
import {
createPublicClient,
createWalletClient,
getContract,
http,
Address,
} from "viem"
हे आपल्याला आवश्यक असलेले Viem (नवीन टॅबमध्ये उघडते) घटक, फंक्शन्स आणि Address प्रकार (नवीन टॅबमध्ये उघडते) आहेत. हा सर्व्हर TypeScript (नवीन टॅबमध्ये उघडते) मध्ये लिहिला आहे, जो JavaScript चा एक विस्तार आहे ज्यामुळे तो स्ट्रॉंगली टाईप्ड (strongly typed) (नवीन टॅबमध्ये उघडते) बनतो.
import { privateKeyToAccount } from "viem/accounts"
हे फंक्शन (नवीन टॅबमध्ये उघडते) आपल्याला खाजगी कीशी संबंधित पत्त्यासह वॉलेट माहिती तयार करू देते.
import { holesky } from "viem/chains"
Viem मध्ये ब्लॉकचेन वापरण्यासाठी तुम्हाला त्याची व्याख्या (definition) इम्पोर्ट करणे आवश्यक आहे. या प्रकरणात, आपल्याला होल्स्की (नवीन टॅबमध्ये उघडते) टेस्ट ब्लॉकचेनशी कनेक्ट करायचे आहे.
// अशा प्रकारे आपण .env मधील व्याख्या process.env मध्ये जोडतो.
import * as dotenv from "dotenv"
dotenv.config()
अशा प्रकारे आपण .env ला एन्व्हायरन्मेंटमध्ये वाचतो. आपल्याला खाजगी कीसाठी याची आवश्यकता आहे (पुढे पहा).
const greeterAddress : Address = "0xB8f6460Dc30c44401Be26B0d6eD250873d8a50A6"
const greeterABI = [
{
"inputs": [
{
"internalType": "string",
"name": "_greeting",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
.
.
.
{
"inputs": [
{
"internalType": "string",
"name": "_greeting",
"type": "string"
}
],
"name": "setGreeting",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
] as const
कॉन्ट्रॅक्ट वापरण्यासाठी आपल्याला त्याचा पत्ता आणि त्यासाठी आवश्यक आहे. आपण येथे दोन्ही प्रदान करतो.
JavaScript मध्ये (आणि त्यामुळे TypeScript मध्ये) तुम्ही कॉन्स्टंटला (constant) नवीन मूल्य नियुक्त करू शकत नाही, परंतु तुम्ही त्यात संग्रहित केलेला ऑब्जेक्ट सुधारू शकता. as const प्रत्यय वापरून आपण TypeScript ला सांगत आहोत की यादी स्वतःच कॉन्स्टंट आहे आणि ती बदलली जाऊ शकत नाही.
const publicClient = createPublicClient({
chain: holesky,
transport: http(),
})
Viem पब्लिक क्लायंट (नवीन टॅबमध्ये उघडते) तयार करा. पब्लिक क्लायंट्सना खाजगी की जोडलेली नसते, आणि त्यामुळे ते व्यवहार पाठवू शकत नाहीत. ते view फंक्शन्स (नवीन टॅबमध्ये उघडते) कॉल करू शकतात, खात्यातील शिल्लक वाचू शकतात, इ.
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
एन्व्हायरन्मेंट व्हेरिएबल्स process.env (नवीन टॅबमध्ये उघडते) मध्ये उपलब्ध आहेत. तथापि, TypeScript स्ट्रॉंगली टाईप्ड आहे. एन्व्हायरन्मेंट व्हेरिएबल कोणतीही स्ट्रिंग असू शकते, किंवा रिक्त असू शकते, त्यामुळे एन्व्हायरन्मेंट व्हेरिएबलचा प्रकार string | undefined आहे. तथापि, Viem मध्ये की 0x${string} (0x आणि त्यानंतर स्ट्रिंग) म्हणून परिभाषित केली आहे. येथे आपण TypeScript ला सांगतो की PRIVATE_KEY एन्व्हायरन्मेंट व्हेरिएबल त्या प्रकारचा असेल. जर तसे नसेल, तर आपल्याला रनटाइम एरर मिळेल.
त्यानंतर privateKeyToAccount (नवीन टॅबमध्ये उघडते) फंक्शन संपूर्ण खाते ऑब्जेक्ट तयार करण्यासाठी या खाजगी कीचा वापर करते.
const walletClient = createWalletClient({
account,
chain: holesky,
transport: http(),
})
पुढे, आपण वॉलेट क्लायंट (नवीन टॅबमध्ये उघडते) तयार करण्यासाठी खाते ऑब्जेक्ट वापरतो. या क्लायंटकडे खाजगी की आणि पत्ता असतो, त्यामुळे त्याचा वापर व्यवहार पाठवण्यासाठी केला जाऊ शकतो.
const greeter = getContract({
address: greeterAddress,
abi: greeterABI,
client: { public: publicClient, wallet: walletClient },
})
आता आपल्याकडे सर्व पूर्वअटी असल्याने, आपण शेवटी कॉन्ट्रॅक्ट इन्स्टन्स (नवीन टॅबमध्ये उघडते) तयार करू शकतो. आपण या कॉन्ट्रॅक्ट इन्स्टन्सचा वापर ऑनचेन कॉन्ट्रॅक्टशी संवाद साधण्यासाठी करू.
ब्लॉकचेनवरून वाचणे
console.log(`Current greeting:`, await greeter.read.greet())
केवळ वाचण्यायोग्य (read only) असलेली कॉन्ट्रॅक्ट फंक्शन्स (view (नवीन टॅबमध्ये उघडते) आणि pure (नवीन टॅबमध्ये उघडते)) read अंतर्गत उपलब्ध आहेत. या प्रकरणात, आपण त्याचा वापर greet (नवीन टॅबमध्ये उघडते) फंक्शन ॲक्सेस करण्यासाठी करतो, जे ग्रीटिंग परत करते.
JavaScript सिंगल-थ्रेडेड आहे, त्यामुळे जेव्हा आपण दीर्घकाळ चालणारी प्रक्रिया सुरू करतो तेव्हा आपल्याला ती असिंक्रोनसपणे (asynchronously) करत असल्याचे निर्दिष्ट करणे (नवीन टॅबमध्ये उघडते) आवश्यक असते. ब्लॉकचेनला कॉल करण्यासाठी, अगदी केवळ वाचण्यायोग्य ऑपरेशनसाठीही, संगणक आणि ब्लॉकचेन नोड दरम्यान राऊंड-ट्रिप आवश्यक असते. याच कारणामुळे आपण येथे निर्दिष्ट करतो की कोडला परिणामासाठी await करणे आवश्यक आहे.
जर तुम्हाला हे कसे काम करते यात स्वारस्य असेल तर तुम्ही त्याबद्दल येथे वाचू शकता (नवीन टॅबमध्ये उघडते), परंतु व्यावहारिकदृष्ट्या तुम्हाला फक्त एवढेच माहीत असणे आवश्यक आहे की जर तुम्ही जास्त वेळ घेणारे ऑपरेशन सुरू केले तर तुम्ही परिणामांची await करता, आणि असे करणाऱ्या कोणत्याही फंक्शनला async म्हणून घोषित करणे आवश्यक आहे.
व्यवहार जारी करणे
const setGreeting = async (greeting: string): Promise<any> => {
ग्रीटिंग बदलणारा व्यवहार जारी करण्यासाठी तुम्ही हे फंक्शन कॉल करता. हे एक दीर्घ ऑपरेशन असल्याने, फंक्शन async म्हणून घोषित केले आहे. अंतर्गत अंमलबजावणीमुळे, कोणत्याही async फंक्शनला Promise ऑब्जेक्ट परत करणे आवश्यक असते. या प्रकरणात, Promise<any> चा अर्थ असा आहे की आपण Promise मध्ये नेमके काय परत केले जाईल हे निर्दिष्ट करत नाही.
const txHash = await greeter.write.setGreeting([greeting])
कॉन्ट्रॅक्ट इन्स्टन्सच्या write फील्डमध्ये ब्लॉकचेन स्थितीवर लिहिणारी सर्व फंक्शन्स (ज्यांसाठी व्यवहार पाठवणे आवश्यक आहे) असतात, जसे की setGreeting (नवीन टॅबमध्ये उघडते). पॅरामीटर्स, जर काही असतील तर, यादी म्हणून प्रदान केले जातात, आणि फंक्शन व्यवहाराचा हॅश परत करते.
console.log(`Working on a fix, see https://eth-holesky.blockscout.com/tx/${txHash}`)
return txHash
}
व्यवहाराचा हॅश नोंदवा (तो पाहण्यासाठी ब्लॉक एक्सप्लोररच्या URL चा भाग म्हणून) आणि तो परत करा.
घटनांना प्रतिसाद देणे
greeter.watchEvent.SetGreeting({
watchEvent फंक्शन (नवीन टॅबमध्ये उघडते) तुम्हाला एखादी घटना उत्सर्जित झाल्यावर कोणते फंक्शन चालवायचे हे निर्दिष्ट करू देते. जर तुम्हाला फक्त एका प्रकारच्या घटनेची काळजी असेल (या प्रकरणात, SetGreeting), तर तुम्ही स्वतःला त्या घटना प्रकारापुरते मर्यादित ठेवण्यासाठी या सिंटॅक्सचा वापर करू शकता.
onLogs: logs => {
जेव्हा नोंद (log) एंट्रीज असतात तेव्हा onLogs फंक्शन कॉल केले जाते. इथेरियममध्ये "नोंद" आणि "घटना" सामान्यतः अदलाबदल करण्यायोग्य असतात.
console.log(
`Address ${logs[0].args.sender} changed the greeting to ${logs[0].args.greeting}`
)
अनेक घटना असू शकतात, परंतु सोपेपणासाठी आपण फक्त पहिल्या घटनेची काळजी घेतो. logs[0].args हे घटनेचे आर्ग्युमेंट्स आहेत, या प्रकरणात sender आणि greeting.
if (logs[0].args.sender != account.address)
setGreeting(`${account.address} insists on it being Hello!`)
}
})
जर प्रेषक हा सर्व्हर नसेल, तर ग्रीटिंग बदलण्यासाठी setGreeting वापरा.
package.json
ही फाईल (नवीन टॅबमध्ये उघडते) Node.js (नवीन टॅबमध्ये उघडते) कॉन्फिगरेशन नियंत्रित करते. हा लेख फक्त महत्त्वाच्या व्याख्या स्पष्ट करतो.
{
"main": "dist/index.js",
ही व्याख्या कोणती JavaScript फाईल चालवायची हे निर्दिष्ट करते.
"scripts": {
"start": "tsc && node dist/app.js",
},
स्क्रिप्ट्स या विविध ॲप्लिकेशन क्रिया आहेत. या प्रकरणात, आपल्याकडे फक्त start आहे, जे सर्व्हर संकलित (compile) करते आणि नंतर चालवते. tsc कमांड typescript पॅकेजचा भाग आहे आणि TypeScript ला JavaScript मध्ये संकलित करते. जर तुम्हाला ते मॅन्युअली चालवायचे असेल, तर ते node_modules/.bin मध्ये स्थित आहे. दुसरी कमांड सर्व्हर चालवते.
"type": "module",
JavaScript नोड ॲप्लिकेशन्सचे अनेक प्रकार आहेत. module प्रकार आपल्याला टॉप लेव्हल कोडमध्ये await ठेवू देतो, जे तुम्ही संथ (आणि त्यामुळे असिंक्रोनस) ऑपरेशन्स करता तेव्हा महत्त्वाचे असते.
"devDependencies": {
"@types/node": "^20.14.2",
"typescript": "^5.4.5"
},
ही अशी पॅकेजेस आहेत जी फक्त डेव्हलपमेंटसाठी आवश्यक आहेत. येथे आपल्याला typescript ची आवश्यकता आहे आणि आपण ते Node.js सोबत वापरत असल्यामुळे, आपल्याला नोड व्हेरिएबल्स आणि ऑब्जेक्ट्सचे प्रकार देखील मिळत आहेत, जसे की process. ^<version> नोटेशनचा (नवीन टॅबमध्ये उघडते) अर्थ ती आवृत्ती किंवा त्याहून उच्च आवृत्ती ज्यामध्ये ब्रेकिंग बदल नाहीत असा होतो. आवृत्ती क्रमांकांच्या अर्थाबद्दल अधिक माहितीसाठी येथे (नवीन टॅबमध्ये उघडते) पहा.
"dependencies": {
"dotenv": "^16.4.5",
"viem": "2.14.1"
}
}
ही अशी पॅकेजेस आहेत जी रनटाइमच्या वेळी, dist/app.js चालवताना आवश्यक असतात.
निष्कर्ष
आपण येथे तयार केलेला केंद्रीकृत सर्व्हर त्याचे काम करतो, जे वापरकर्त्यासाठी एजंट म्हणून कार्य करणे हे आहे. इतर कोणीही ज्याला विकेंद्रित ॲप्लिकेशन (dapp) कार्य करत राहावे असे वाटते आणि जो गॅस खर्च करण्यास तयार आहे, तो स्वतःच्या पत्त्यासह सर्व्हरचा नवीन इन्स्टन्स चालवू शकतो.
तथापि, हे तेव्हाच काम करते जेव्हा केंद्रीकृत सर्व्हरच्या क्रिया सहजपणे सत्यापित केल्या जाऊ शकतात. जर केंद्रीकृत सर्व्हरकडे कोणतीही गुप्त स्थिती माहिती असेल, किंवा तो कठीण आकडेमोड करत असेल, तर ती एक केंद्रीकृत संस्था आहे ज्यावर तुम्हाला ॲप्लिकेशन वापरण्यासाठी विश्वास ठेवणे आवश्यक आहे, जे नेमके ब्लॉकचेन टाळण्याचा प्रयत्न करतात. भविष्यातील लेखात मी या समस्येवर मात करण्यासाठी झिरो-नॉलेज प्रुफ्स कसे वापरावे हे दाखवण्याची योजना आखत आहे.