You are on page 1of 36

ĐẠI HỌC QUỐC GIA TP.

HỒ CHÍ MINH

TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN


KHOA KHOA HỌC & KỸ THUẬT THÔNG TIN

BÁO CÁO MÔN HỌC

ĐỒ ÁN

IT207.N11.CNCL

GIẢNG VIÊN HƯỚNG DẪN: VÕ NGỌC TÂN

SINH VIÊN THỰC HIỆN: ĐÀO VĨNH KỲ - 19521730

TRẦN HÙNG - 19521584

TP. HỒ CHÍ MINH, 12/2020


i
Chương 6. Indexing and Storage................................................................................................4

1.1. Indexing Data............................................................................................................................4

1.1.1. Tracking ERC20 Token Holders.............................................................................5

1.1.2. Revisiting the ERC20 Interface...............................................................................5

1.1.3. Querying Transfer Events.........................................................................................6

1.1.4. Chia sẻ dữ liệu củ a chú ng tô i................................................................................11

1.1.5. Xử lý tổ chứ c lạ i chuỗ i..............................................................................................13

1.1.6. Sử dụ ng đă ng ký.........................................................................................................13

1.1.7. Phá t hiện mộ t sự tá i tổ chứ c.................................................................................14

1.1.8. Hoà n nguyên cá c thay đổ i......................................................................................16

1.1.9. Kiểm thử đơn vị..........................................................................................................18

1.1.10. Chọ n mộ t Node:..........................................................................................................18

1.1.11. Kiểm tra trình lậ p chỉ mụ c.....................................................................................19

1.1.12. Sử dụ ng Ả nh chụ p nhanh.......................................................................................22

1.1.13. Lưu ý về tậ p trung hó a............................................................................................23

1.1.14. Dịch vụ phi tậ p trung...............................................................................................23

1.1.15. Ứ ng dụ ng tậ p trung...................................................................................................24

1.2. Storage........................................................................................................................................ 24

1.2.1. Lưu trữ ngoài (Off-chain Storage).........................................................................25

1.2.2. Tiện ích mở rộng siêu dữ liệu ERC721(ERC721 Metadata Extension).....25

1.2.3. Saving Token Metadata.............................................................................................27

1.2.4. Interplanetary Storage................................................................................................30

2
1.2.5. Sử dụng IpFS trong ứng dụng.................................................................................32

1.2.6 Lưu trữ ứng dụng trên IPFS.....................................................................................34

1.3. Kết luận....................................................................................................................................... 35

3
Chương 6. Indexing and Storage

Trong hai chương cuối, chúng ta đã học cách đọc và ghi vào mạng Ethereum. Đọc có thể
được thực hiện như các cuộc gọi thông thường đến các hợp đồng hoặc bằng cách truy vấn
các sự kiện đã ghi, trong khi việc ghi luôn được thực hiện trong ngữ cảnh của một giao
dịch. Tuy nhiên, các thao tác này chỉ phù hợp với các trường hợp sử dụng cơ bản. Nếu
chúng tôi muốn hiển thị dữ liệu tổng hợp từ blockchain, việc truy vấn các sự kiện từ phía
máy khách sẽ nhanh chóng bị thiếu hụt. Tương tự như vậy, nếu chúng ta muốn lưu trữ
một lượng lớn thông tin trong một hợp đồng, chi phí khí đốt khiến nó không khả thi về
mặt kinh tế. Trong chương này, chúng ta sẽ làm việc với các thành phần ngoài chuỗi để
giải quyết cả hai vấn đề. Trước tiên, chúng ta sẽ trải qua quá trình lập chỉ mục dữ liệu
blockchain trong một máy chủ để truy vấn nó từ một ứng dụng khách và sau đó đi vào
các giải pháp lưu trữ ngoài chuỗi. Chúng tôi cũng sẽ xem xét các kỹ thuật để xử lý việc tổ
chức lại và thử nghiệm trong quá trình thực hiện, cũng như thảo luận về giá trị của các
giải pháp tập trung so với phi tập trung.

1.1. Indexing Data

Chúng ta sẽ bắt đầu với vấn đề lập chỉ mục dữ liệu blockchain. Trong ngữ cảnh này, bằng
cách lập chỉ mục, chúng tôi đề cập đến hành động thu thập các phần thông tin nhất định
từ mạng (chẳng hạn như số dư mã thông báo, giao dịch đã gửi hoặc hợp đồng được tạo)
và lưu trữ chúng trong kho dữ liệu có thể truy vấn, chẳng hạn như cơ sở dữ liệu quan hệ
hoặc công cụ phân tích như ElasticSearch, để thực hiện các truy vấn phức tạp. Trong suốt
chương này, thay vì cố gắng lập chỉ mục toàn bộ chuỗi, chúng tôi sẽ chọn một tập dữ liệu
cụ thể và xây dựng một giải pháp phù hợp với nó. , vì API sự kiện Ethereum không phù
hợp để làm như vậy. Ví dụ: không thể dễ dàng có được số lượng địa chỉ duy nhất chứa
nhiều hơn một số dư nhất định của nội dung ERC20 - một truy vấn nhỏ khi chạy trên cơ
sở dữ liệu quan hệ.

Lập chỉ mục cũng có thể được sử dụng để cải thiện hiệu suất khi truy vấn số lượng

4
lớn sự kiện, bằng cách hoạt động như một bộ nhớ cache truy vấn. Một cơ sở dữ liệu
chuyên dụng có thể trả lời các truy vấn sự kiện nhanh hơn nhiều so với một nút Ethereum
thông thường.

1.1.1. Tracking ERC20 Token Holders

Chúng tôi sẽ theo dõi những người nắm giữ mã thông báo của một đồng ERC20 cụ thể.
Hãy nhớ rằng các mã thông báo không thể thay thế ERC20 có thể hoạt động như một
đồng xu, trong đó mỗi địa chỉ có một số dư nhất định. Tuy nhiên, hợp đồng không đưa ra
phương pháp nào để thực sự liệt kê chúng. Ngay cả khi điều đó xảy ra, một số mã thông
báo có cơ sở người dùng sẽ vượt quá khả năng của một ứng dụng chỉ phía máy khách
truy vấn nút Ethereum. Ví dụ: tại thời điểm viết bài này, mã thông báo OmiseGO (OMG)
có hơn 650.000 chủ sở hữu duy nhất.2

1.1.2. Revisiting the ERC20 Interface

Chúng tôi sẽ xem xét giao diện hợp đồng ERC20 để xác định các khối xây dựng mà
chúng tôi sẽ sử dụng. Bỏ qua các chức năng liên quan đến phụ cấp, tiêu chuẩn ERC20
bao gồm các phương pháp và sự kiện này.

Như đã đề cập, không giống như tiêu chuẩn ERC721 mở rộng, ERC20 không cung
cấp phương pháp liệt kê tất cả chủ sở hữu mã thông báo. Cách duy nhất để xây dựng một
5
danh sách như vậy là đi qua mọi sự kiện Chuyển và thu thập tất cả các địa chỉ người
nhận.

1.1.3. Querying Transfer Events

Truy vấn sự kiện chuyển Chúng tôi sẽ bắt đầu bằng cách truy vấn tất cả các sự kiện
chuyển nhượng từ một hợp đồng nhất định. Chúng tôi sẽ xây dựng một lớp
ERC20Indexer, dựa vào nhà cung cấp kết nối web3, địa chỉ mã thông báo ERC20 và số
khối để bắt đầu truy vấn từ đó (Liệt kê 6-1). Tham số cuối cùng này chỉ được thêm vào vì
lý do hiệu suất: không có ý nghĩa gì khi truy vấn bất kỳ khối nào trước khi hợp đồng mã
thông báo được triển khai. Liệt kê 6-1.Constructor cho lớp ERC20Indexer mà chúng tôi
sẽ làm việc trên. Chúng tôi một lần nữa phụ thuộc vào openzeppelin-solidity@2.2.0 để có
được ABI hợp đồng ERC20. Chúng tôi sẽ lưu trữ danh sách tất cả địa chỉ chủ sở hữu mã
thông báo trên bộ chủ sở hữu và trường LastBlock sẽ theo dõi khối cuối cùng mà chúng
tôi đã xử lý

6
Bây giờ chúng ta có thể thêm một phương thức đầu tiên vào lớp này để lấy danh
sách các sự kiện chuyển giao từ một hợp đồng nhất định (Liệt kê 6-2). Vì danh sách đó có
khả năng lớn hơn những gì chúng tôi có thể đáp ứng trong một yêu cầu duy nhất (OMG
có hơn 2 triệu lần chuyển đến thời điểm hiện tại), chúng tôi sẽ cần chia nó thành nhiều
yêu cầu, vì vậy chúng tôi sẽ bắt đầu với một phương pháp xử lý tất cả các sự kiện trong
một phạm vi của các khối.

Liệt kê 6-2. Truy vấn tất cả các sự kiện chuyển từ một loạt các khối theo lô. Ở đây
BATCH_SIZE sẽ phụ thuộc vào khối lượng giao dịch trên mỗi khối của hợp đồng

Phương thức này sẽ nhận tất cả các sự kiện chuyển trong một phạm vi khối từ một
hợp đồng và gửi chúng đến một hàm ReduceEvent (Liệt kê 6-3). Chức năng giảm này sẽ
nhận một sự kiện và sử dụng nó để cập nhật danh sách chủ sở hữu mã thông báo hiện tại.

7
Liệt kê 6-3. Truy xuất người nhận chuyển mã thông báo để thêm người đó vào danh sách
chủ sở hữu mã thông báo. Việc chuyển đến địa chỉ số 0 thường là các mã thông báo bị
cháy, vì vậy chúng tôi sẽ đưa nó ra khỏi danh sách của mình

Tuy nhiên, phương pháp này có thể mang lại một số kết quả dương tính giả. Nếu
một địa chỉ từng giữ một số số dư nhưng sau đó đã chuyển tất cả, địa chỉ đó sẽ được thêm
vào danh sách chủ sở hữu của chúng tôi nhưng không bao giờ bị xóa. Điều này có nghĩa
là chúng ta cần kiểm tra xem số dư địa chỉ có khác 0 để liệt kê nó hay không. Vì chúng
tôi đang ở đó, chúng tôi sẽ đi thêm một dặm và theo dõi số dư hiện tại của mỗi chủ sở
hữu. Chúng tôi có hai tùy chọn để thực hiện việc này, mỗi tùy chọn đều có ưu và nhược
điểm riêng:

Chúng tôi có thể dựa vào phương pháp ERC20 balanceOf để kiểm tra số dư của
từng địa chỉ mà chúng tôi thêm vào danh sách của mình. Chúng tôi có thể thực hiện việc
này ngay khi chúng tôi tìm thấy một địa chỉ mới để thêm vào tập hợp của mình và có một
người giữ với số dư của nó đã sẵn sàng. Tuy nhiên, điều này có nghĩa là chúng tôi đang
đưa ra yêu cầu bổ sung cho từng chủ sở hữu mã thông báo và chúng tôi cũng cần chạy lại
truy vấn này bất cứ khi nào chúng tôi thấy một khối mới với các lần chuyển mới.

Chúng tôi có thể sử dụng thực tế rằng số dư của bất kỳ địa chỉ nào trong hợp đồng
ERC20 có thể được xác định bằng cách chỉ cần xem xét các sự kiện chuyển nhượng. Vì
chúng tôi đã truy vấn chúng, chúng tôi có thể theo dõi tất cả các chuyển động đến và đi từ
mỗi địa chỉ và cập nhật chúng cho phù hợp. Điều này sẽ yêu cầu logic hơn một chút về

8
phía chúng tôi, nhưng không cần bất kỳ yêu cầu bổ sung nào đối với mạng Ethereum.
Nhược điểm của nó là chúng ta không thể dựa vào số dư của một địa chỉ cho đến khi
chúng ta quét xong cho đến khi khối mới nhất trong chuỗi

Để ưu tiên giảm số lượng yêu cầu, chúng tôi sẽ đi với chiến lược thứ hai (Liệt kê
6-4). Điều này có nghĩa là chức năng của chúng tôi để giảm bớt một sự kiện cũng sẽ cần
tính đến giá trị của mỗi lần chuyển.

Liệt kê 6-4. Giảm giá một sự kiện để cập nhật số dư của mỗi chủ sở hữu. Ở đây số dư là
một đối tượng thay thế tập hợp các chủ sở hữu cũ. Lưu ý rằng chúng tôi đang loại trừ địa
chỉ số 0 với tư cách là người gửi và người nhận, vì chuyển từ địa chỉ này đại diện cho các
sự kiện đúc tiền và chuyển đến địa chỉ đó đại diện cho bỏng

Được trang bị một lớp có thể tạo danh sách những người nắm giữ và số dư của
chúng cho một phạm vi khối nhất định, bây giờ chúng ta cần phải cập nhật danh sách này
khi các khối mới được khai thác (Liệt kê 6-5). Chúng tôi sẽ sử dụng tính năng thăm dò để
nhận các khối mới, mặc dù đăng ký cũng là một giải pháp thay thế khả thi.

9
Liệt kê 6-5. Thanh toán cho các khối mới và truy xuất bất kỳ sự kiện chuyển giao mới
nào. Hàm processNewBlocks sẽ truy vấn khối mới nhất và gọi vào processBlocks với
phạm vi khối mới, trong khi hàm start khởi động một vòng lặp vô hạn liên tục thăm dò và
sau đó ngủ trong 1 giây

Một chi tiết quan trọng trong việc triển khai của chúng tôi là chúng tôi sẽ không
xử lý đến khối mới nhất mà chỉ xử lý cho đến một số khối nhất định trước đó. Điều này
đảm bảo rằng bất kỳ sự kiện chuyển giao nào mà chúng tôi đã xử lý đều được xác nhận
và sẽ không bị lùi lại như một phần của việc tổ chức lại. Sau đó, chúng tôi sẽ xem xét các
chiến lược để truy vấn đến khối gần đây nhất và xử lý các bản ghi lại khi chúng tôi phát
hiện ra chúng.

10
1.1.4. Chia sẻ dữ liệu của chúng tôi

Bước cuối cùng là thực sự cung cấp quyền truy cập vào dữ liệu chúng tôi đã thu thập
(Liệt kê 6-6). Chúng tôi sẽ thiết lập một máy chủ express đơn giản để hiển thị tập hợp số
dư ở định dạng JSON theo yêu cầu HTTP GET. 188 Liệt kê 6-6 Máy chủ express đơn
giản hiển thị số dư từ trình chỉ mục trong một điểm cuối HTTP. Trình trợ giúp
mapValues lodash được sử dụng để định dạng các giá trị BigNumber trước khi tuần tự
hóa chúng trong JSON. Đảm bảo nhận được mã thông báo Infura để đặt biến
API_TOKEN

11
Liệt kê 6-7. Truy vấn điểm cuối express để lấy số dư mã thông báo. Chúng tôi đang sử
dụng tiện ích jq3 chỉ để in đẹp đầu ra JSON

1.1.5. Xử lý tổ chức lại chuỗi

Cho đến thời điểm này, chúng tôi đã tránh được vấn đề sắp xếp lại chuỗi bằng cách chỉ
xử lý các chuyển giao có thể được coi là đã hoàn thành, nói cách khác, các chuyển giao
đã xảy ra đủ khối trước đó cơ hội các khối đó bị xóa khỏi chuỗi là không đáng kể . Giờ
đây, chúng tôi sẽ loại bỏ hạn chế này và xem cách chúng tôi có thể xử lý các sự kiện mới
nhất một cách an toàn bằng cách phản ứng đúng cách với một nhà tổ chức lại.

1.1.6. Sử dụng đăng ký

Cách dễ nhất để phát hiện và phản ứng với một bản reorg là thông qua đăng ký. Việc
đăng ký một sự kiện, chẳng hạn như trên ERC20 Transfer, sẽ không chỉ đẩy các sự kiện
mới vào quy trình của chúng tôi trong thời gian thực mà còn thông báo về bất kỳ sự kiện
nào bị xóa do tổ chức lại. Điều này có nghĩa là chúng ta có thể viết phần đối chiếu của
hàm ReduceEvent để hoàn tác một sự kiện và chạy nó bất cứ khi nào nút Ethereum đẩy
một loại bỏ sự kiện (Liệt kê 6-8).

Liệt kê 6-8. Ghi lại một sự kiện chuyển khoản trong danh sách số dư của chúng tôi.
Logic ở đây là ngược lại trong phương thức ReduceEvent

12
Liệt kê 6-9. Sử dụng đăng ký để theo dõi các sự kiện mới và theo dõi các đăng ký đã bị
loại bỏ do tổ chức lại. Trình xử lý dữ liệu kích hoạt bất cứ khi nào có sự kiện mới và sự
kiện đã thay đổi khi sự kiện bị xóa khỏi chuỗi do tổ chức lại

1.1.7. Phát hiện một sự tái tổ chức

Một sự sắp xếp lại xảy ra khi một chain fork tập hợp nhiều sức mạnh băm tích lũy hơn so
với phần đầu hiện tại và fork đó trở thành chuỗi chính thức. Điều này có thể xảy ra nếu
các bộ công cụ khai thác khác nhau hoạt động trên các nhánh khác nhau. Điều này có
nghĩa là trong quá trình tổ chức lại, một hoặc nhiều khối (bắt đầu ngược từ phần đầu hiện
tại) sẽ được thay thế bằng các khối khác. Các khối mới này có thể chứa hoặc không chứa

13
các giao dịch giống như các khối trước đó và cũng có thể được sắp xếp theo thứ tự khác
nhau, 4 mang lại kết quả khác nhau. Nhớ lại từ Chương 3 rằng mỗi khối được xác định
bằng hàm băm của nó. Hàm băm này được tính toán từ dữ liệu của khối và hàm băm của
khối trước đó. Đây là những gì tạo nên một blockchain về bản chất của nó: thực tế là mỗi
khối được gắn với khối trước đó. Và điều này có nghĩa là không thể thay đổi một khối cũ
nếu không buộc phải thay đổi mã định danh của tất cả các khối tiếp theo. Khi băm của
một khối ở một độ cao nhất định thay đổi, điều đó có nghĩa là khối đó và có thể là các
khối khác trước khi nó cũng thay đổi. Sau đó, chúng tôi chỉ có thể theo dõi khối mới nhất
mà chúng tôi đã xử lý và nếu hàm băm của nó thay đổi tại bất kỳ thời điểm nào, chúng tôi
sau đó quét ngược lại các hàm băm đã thay đổi khác, cho đến khi chúng tôi phát hiện ra
một tổ tiên chung (Liệt kê 6-10). Hãy sửa đổi tập lệnh của chúng tôi để thêm một kiểm
tra các bản ghi lại bằng cách sử dụng chiến lược này, điều này sẽ yêu cầu chúng tôi theo
dõi các hàm băm khối mà chúng tôi đã xử lý.

Liệt kê 6-10. Cập nhật processNewBlocks hàm kiểm tra các bản ghi lại sau mỗi lần lặp.
Hàm undoBlocks (Liệt kê 6-12) sẽ hoàn tác tất cả các lần chuyển liên quan đến các khối
đã xóa, trả về khối gần đây nhất không bị ảnh hưởng bởi việc sắp xếp lại

14
1.1.8. Hoàn nguyên các thay đổi

Khi việc tổ chức lại được phát hiện, chúng tôi cần hoàn nguyên mọi chuyển giao mà
chúng tôi đã xử lý từ các khối đã xóa. Để làm được điều đó, trước tiên chúng ta cần theo
dõi những lần chuyển mà chúng ta đã xử lý trên mỗi khối (Liệt kê 6-11). Chúng ta sẽ
thêm một trường mới vào lớp Indexer của mình: một ngăn xếp chứa một mục trên mỗi
khối mà chúng ta đã thấy khi xử lý sự kiện chuyển nhượng. Mỗi mục sẽ chứa số khối, mã

15
băm và danh sách chuyển nó bao gồm. Chúng tôi sẽ thêm các mục mới vào nó bất cứ khi
nào chúng tôi giảm bớt một sự kiện mới

Liệt kê 6-11: Theo dõi các sự kiện chuyển giao trên mỗi khối. Hàm này được gọi từ
ReduceEvent. Lưu ý rằng hàm này phải được gọi theo thứ tự khi các sự kiện mới đang
được xử lý để đảm bảo danh sách các khối vẫn được sắp xếp với khối gần đây nhất ở cuối

Liệt kê 6-12: Lùi lại danh sách các sự kiện đã xử lý và hoàn tác tất cả các lần chuyển.
Hàm này phải trả về khối gần đây nhất chưa bị xóa trong quá trình tổ chức lại, để tập lệnh
có thể xử lý lại chuỗi từ đó. Hàm undoTransfer tương tự với hàm được trình bày trong

16
phần phụ đăng ký trước đó trong chương này

1.1.9. Kiểm thử đơn vị

Cho đến nay, chúng tôi đã bỏ qua một khía cạnh quan trọng của phát triển phần mềm:
kiểm tra. Mặc dù thử nghiệm trong Ethereum về cơ bản không khác biệt đáng kể so với
các ứng dụng khác, chúng tôi sẽ sử dụng ví dụ về trình lập chỉ mục của mình để giới
thiệu một số kỹ thuật hữu ích cụ thể cho thử nghiệm blockchain.

1.1.10. Chọn một Node:

Quyết định đầu tiên nằm ở việc chọn nút nào để sử dụng cho các thử nghiệm của chúng
tôi. Nhớ lại từ các chương trước rằng chúng ta có thể làm việc với ganache, một trình mô
phỏng blockchain hoặc trên một nút thực tế, chẳng hạn như Geth hoặc Parity, đang chạy
trên chế độ phát triển. Cái trước nhẹ hơn và cung cấp các phương pháp bổ sung để thao

17
túng trạng thái blockchain, rất hữu ích trong các bài kiểm tra đơn vị. Mặt khác, việc sử
dụng một nút thực tế sẽ đại diện hơn cho thiết lập sản xuất thực tế cho ứng dụng của bạn.
Thỏa hiệp tốt là sử dụng ganache với con dấu tức thì cho các bài kiểm tra đơn vị, trong
khi nút phát triển Geth hoặc Parity có thể được sử dụng cho end-to -end các bài kiểm tra
tích hợp, chạy với thời gian khối cố định. Điều này cho phép kiểm soát chi tiết hơn trong
các bài kiểm tra đơn vị và một môi trường đại diện hơn về tích hợp.

1.1.11. Kiểm tra trình lập chỉ mục

Bây giờ chúng tôi sẽ viết một số thử nghiệm đơn vị cho trình chỉ mục của chúng tôi.
Chúng tôi sẽ sử dụng ganache làm back end, mocha5 làm trình chạy thử nghiệm và chai6
với plugin bignumber7 để viết xác nhận. Bước đầu tiên là triển khai mã thông báo
ERC20 để trình chỉ mục của chúng tôi giám sát (Liệt kê 6-13). Chúng tôi sẽ mở rộng
triển khai ERC20 mặc định từ OpenZeppelin bằng phương pháp đúc tiền công khai, vì
vậy chúng tôi có thể dễ dàng đúc mã thông báo cho bất kỳ địa chỉ nào chúng tôi muốn
kiểm tra.

Liệtkê 6-13. ERC20 với phương pháp đào tiền công khai, cho phép bất kỳ ai tạo mã
thông báo mới. Không sử dụng trong sản xuất!

18
Liệt kê 6-14. Mã trình soạn sẵn cho một bộ thử nghiệm cho Trình chỉ mục. Nó khởi tạo
một phiên bản web3 mới và triển khai một phiên bản của hợp đồng mã thông báo ERC20.
Lưu ý rằng một số câu lệnh yêu cầu đã bị xóa cho ngắn gọn

Liệt kê 6-15 Kiểm tra để kiểm tra xem việc chuyển từ đúc tiền có được xử lý chính xác
hay không. Chúng tôi khởi tạo phiên bản Trình lập chỉ mục mới cho hợp đồng mã thông
báo mới được triển khai, đúc một số mã thông báo cho một địa chỉ và thực thi trình lập
chỉ mục để kiểm tra kết quả

19
Liệt kê 6-16. Bắt đầu một cá thể ganache và chạy bộ thử nghiệm tương ứng. Mỗi lệnh
phải được chạy trên một thiết bị đầu cuối khác nhau

Liệt kê 6-17. Phương pháp trợ giúp để hướng dẫn ganache khai thác một số khối nhất
định. Mã trong mineBlocks kích hoạt một số yêu cầu số đã chọn song song và trả về khi
tất cả chúng đều thành công

20
1.1.12. Sử dụng Ảnh chụp nhanh

Giờ đây, chúng tôi có thể viết thêm các bài kiểm tra thực hiện các tình huống
khác của trình chỉ mục của chúng tôi. Tuy nhiên, trong bất kỳ bộ thử nghiệm tốt nào, tất
cả các thử nghiệm phải độc lập với nhau, điều này không xảy ra ở đây vì chúng tôi đang
triển khai một phiên bản duy nhất của hợp đồng ERC20 của mình. Điều này có nghĩa là
bất kỳ quá trình đúc hoặc chuyển giao nào xảy ra trong một bài kiểm tra sẽ được chuyển
sang các bước sau đây. cơ hội này để giới thiệu một khái niệm mới dành riêng cho
ganache: ảnh chụp nhanh (Liệt kê 6-18). Ảnh chụp nhanh cho phép chúng tôi lưu trạng
thái hiện tại của blockchain được mô phỏng, chạy một tập hợp các hoạt động và sau đó
quay trở lại trạng thái đã lưu.

Liệt kê 6-18 Các hàm trợ giúp để chụp một ảnh chụp nhanh mới, trả về id ảnh chụp
nhanh và để hoàn nguyên về một ảnh chụp nhanh cụ thể được cung cấp id của nó

Liệt kê 6-18 Các hàm trợ giúp để chụp một ảnh chụp nhanh mới, trả về id ảnh chụp
nhanh và để hoàn nguyên về một ảnh chụp nhanh cụ thể được cung cấp id của nó

21
1.1.13. Lưu ý về tập trung hóa

Lần đầu tiên trong cuốn sách này, chúng tôi đã giới thiệu một thành phần phía máy chủ
cho các ứng dụng của chúng tôi. Thay vì chỉ xây dựng một ứng dụng chỉ dành cho phía
máy khách kết nối trực tiếp với blockchain, giờ đây chúng tôi có một sự phụ thuộc tập
trung mới cần thiết để ứng dụng của chúng tôi chạy. Có thể lập luận rằng ứng dụng của
chúng tôi do đó không còn đủ điều kiện là một ứng dụng phi tập trung nữa, vì nó tin
tưởng một cách mù quáng vào dữ liệu được trả về bởi một máy chủ do một nhóm duy
nhất điều hành. dữ liệu do máy chủ cung cấp. Tiếp theo với ví dụ về số dư ERC20, một
khách hàng có thể yêu cầu 10 người nắm giữ mã thông báo hàng đầu từ máy chủ lập chỉ
mục và sau đó xác minh với blockchain rằng số dư của họ là chính xác. Họ vẫn có thể
không phải là top 10 - nhưng ít nhất khách hàng có sự đảm bảo rằng số dư không bị giả
mạo. Tuy nhiên, đây không phải là giải pháp cho vấn đề, vì không phải tất cả dữ liệu đều
có thể được xác minh dựa trên blockchain mà không cần truy vấn lại tất cả các sự kiện.
Hơn nữa, ứng dụng của chúng tôi hiện phụ thuộc vào một thành phần có thể bị hỏng bất
kỳ lúc nào, khiến nó không thể sử dụng được

1.1.14. Dịch vụ phi tập trung

Một cách tiếp cận cho vấn đề này là tìm kiếm các giải pháp phi tập trung ngoài chuỗi. Ví
dụ: tại thời điểm viết bài này, giao diện GraphQL với dữ liệu blockchain có tên EthQL10
đang được xem xét. Điều này có thể được thêm vào như một phần của giao diện tiêu
chuẩn cho tất cả các nút Ethereum, cho phép truy vấn các sự kiện từ bất kỳ máy khách
nào dễ dàng hơn. Một ví dụ khác, thegraph11 là một dự án cung cấp các lược đồ
GraphQL tùy chỉnh cho các giao thức khác nhau. Họ dựa vào mạng lưới các nút phi tập
trung được khuyến khích sử dụng mã thông báo để giữ cho các lược đồ đó luôn cập nhật
và trả lời bất kỳ truy vấn nào từ người dùng. Các giải pháp tính toán hoặc lập chỉ mục phi
tập trung vẫn đang được thiết kế. Và ngay cả khi đã sẵn sàng, một giải pháp phân quyền
chung không phải lúc nào cũng có thể đáp ứng cho các nhu cầu cụ thể của ứng dụng của
bạn. Với suy nghĩ này, chúng ta sẽ thảo luận về cách tiếp cận thứ hai.
22
1.1.15. Ứng dụng tập trung

Một giải pháp rõ ràng không phải là giải pháp cho vấn đề là chỉ chấp nhận rằng các ứng
dụng có thể được tập trung. Đây có thể là một tuyên bố gây tranh cãi, tập trung mạnh mẽ
vào các ứng dụng phi tập trung trong suốt cuốn sách, nhưng nó không cần thiết. Bằng
cách dựa vào chuỗi như là nguồn chân lý cuối cùng và xây dựng các giao thức mở chạy
trên nó, bất kỳ nhà phát triển nào cũng có thể tự do xây dựng một ứng dụng để tương tác
với các giao thức đó. Điều này mang lại cho người dùng sự linh hoạt khi di chuyển giữa
các ứng dụng khác nhau hoạt động như cổng vào dữ liệu của họ trong một lớp giao thức
phi tập trung chung. Phi tập trung khi đó được hiểu là quyền tự do có thể đóng gói và rời
đi bất kỳ lúc nào trong khi vẫn bảo toàn dữ liệu và các hiệu ứng mạng phát sinh từ các
lớp phi tập trung được chia sẻ.

Cơ sở lý luận này mang lại cho chúng tôi với tư cách là nhà phát triển quyền tự do xây
dựng các giải pháp mạnh mẽ như chúng tôi muốn ở cấp ứng dụng bằng cách tận dụng bất
kỳ số lượng thành phần tập trung nào để truy vấn, lưu trữ, tính toán hoặc bất kỳ dịch vụ
nào khác mà chúng tôi có thể cần. Như bạn có thể tưởng tượng, tập trung là một vấn đề
gây tranh cãi trong cộng đồng phát triển Ethereum. Không có câu trả lời đúng hay sai cho
chủ đề này, và cuộc thảo luận có thể sẽ tiếp tục phát triển theo thời gian. Bất kể cách tiếp
cận bạn thực hiện là gì, hãy chắc chắn hiểu những ưu và nhược điểm của nó và cân nhắc
chúng với các yêu cầu của giải pháp bạn đang tìm kiếm để xây dựng.

1.2. Storage

1 vấn đề khác trong Ethereum: lưu trữ. Lưu trữ dữ liệu trên Ethereum blockchain rất tốn
kém, 625 gas per byte (làm tròn 32 byte/slots), cộng với cơ sở 68 per non-zero byte chỉ
để gửi dữ liệu đó vào chuỗi khối. Với định mức 1GWei, điều này có nghĩa là lưu trữ một
PNG 100kb sẽ tốn khoảng 0,07 ETH. Mặt khác, lưu trữ dữ liệu trong logs để truy cập bên
ngoài chuỗi sẽ ít hơn, tốn 8 đơn vị gas/byte, cộng với cơ sở 68 để gửi dữ liệu (bằng 1/10

23
chi phí đối với hình ảnh mẫu), nhưng vẫn tốn kém khi bắt đầu mở rộng quy mô. Điều này
có nghĩa là cần xem xét các giải pháp thay thế để lưu trữ dữ liệu ứng dụng lớn.

1.2.1. Lưu trữ ngoài (Off-chain Storage)

Tương tự như cách ta đã sử dụng để lập chỉ mục, ta có thể thiết lập một máy chủ riêng tập
trung cung cấp khả năng lưu trữ. Thay vì lưu trữ dữ liệu trên blockchain, chúng ta có thể
lưu trữ URL và từ đó chúng ta có thể truy xuất dữ liệu đó bên ngoài blockchain. Tất
nhiên, cách làm này chỉ hữu ích cho các dữ liệu không cần thiết đối với hợp đồng thực
hiện hợp lí, các mục đích ngoài blockchain - chẳng hạn như hiển thị hình ảnh được liên
kết với nội dung cũng có thể làm cách này. Cùng với URL, ta cũng nên lưu trữ hàm băm
của dữ liệu được lưu trữ. Điều này cho phép máy khách truy xuất thông tin để xác minh
rằng máy chủ lưu trữ đã không can thiệp vào thông tin đó.Có lúc không thể truy xuất dữ
liệu được do máy chủ lưu trữ gặp sự cố, hàm băm đảm bảo rằng bất kỳ ứng dụng nào
cũng có thể kiểm tra xem dữ liệu có chính xác hay không. Nói cách khác, chúng ta có thể
bỏ đi tính khả dụng của dữ liệu bằng cách di chuyển nó ra khỏi blockchain, nhưng không
phải là tính toàn vẹn của nó.

Ta sẽ chọn ứng dụng ERC721 từ chương trước để làm ví dụ minh họa trường hợp này để
triển khai và lưu trữ siêu dữ liệu được liên kết với mỗi mã thông báo (chẳng hạn như tên
và mô tả) trong một trang web off-chain.

1.2.2. Tiện ích mở rộng siêu dữ liệu ERC721(ERC721 Metadata Extension)

Trước khi bắt đầu triển khai, ta sẽ xem xét một trong những phần mở rộng của tiêu chuẩn
ERC721: phần mở rộng siêu dữ liệu (Ví dụ 6-20). Tiện ích mở rộng này chỉ định rằng
mọi token đều có URI token được liên kết với siêu dữ liệu của nó. URI này có thể là một
URL HTTP chứa một tài liệu JSON với thông tin về mỗi token. Siêu dữ liệu này có thể
bao gồm tên chuẩn, một số văn bản mô tả, thẻ, thông tin tác giả, hình ảnh hoặc bất kỳ
trường nào khác cho miền của tập hợp.

Ví dụ 6-20. Đặc điểm của phương thức tokenURI được yêu cầu bởi tiện ích mở rộng siêu
dữ liệu ERC721

24
function tokenURI(uint256 tokenId)

external view returns (string memory);

Chúng tôi sẽ mở rộng ERC721 contract của mình (Ví dụ 6-21) bao gồm contract
ERC721Metadatabase được cung cấp bởi gói openzeppelin-solidity@2.2.0, theo dõi một
URI per token.

Ví dụ 6-21. ERC721 contract được cập nhật token được chấp nhận liên kết khi minting a
token. Hãy nhớ rằng hợp đồng này yêu cầu một lượng ETH tỷ lệ với ID của token để
mang lại lợi nhuận.

25
Hãy sửa đổi ứng dụng để lưu trữ siêu dữ liệu trong máy chủ và thêm URL chứa dữ liệu
vào token contract cùng với nội dung hàm băm.

1.2.3. Saving Token Metadata

Ta sẽ sửa đổi phương thức mint chính của mình trong thành phần ERC721 để nó không
chỉ chấp nhận một ID mà còn chấp nhận các trường tiêu đề và chuỗi mô tả (Ví dụ 6-22).
Chúng tôi sẽ lưu dữ liệu này ngoài chuỗi, lấy URL và chuyển nó đến lệnh gọi contract
sau.

26
Ví dụ 6-22. Phương pháp mint cập nhật để xử lý siêu dữ liệu token. Lưu ý rằng điều này
cũng yêu cầu sửa đổi thành phần Mint bằng cách thêm đầu vào tiêu đề và mô tả, vì vậy
người dùng có thể cung cấp các giá trị này.

Hàm lưu sẽ tính toán hàm băm của dữ liệu, sử dụng nó làm định danh và lưu trữ trong
máy chủ lưu trữ.

(Ví dụ 6-23). Lưu ý rằng bằng cách sử dụng hàm băm làm định danh, bất kỳ ứng dụng
khách nào cũng có thể xác thực rằng dữ liệu được truy xuất không bị giả mạo.

27
Trong ví dụ này, máy chủ nhận yêu cầu POST là một quy trình NodeJS sẽ chấp nhận dữ
liệu JSON tùy ý tại một đường dẫn URL, lưu cục bộ trong một tệp sau đó phân phát nó
theo yêu cầu GET. Trong một ứng dụng thực tế, bạn có thể lưu vào các services.

Giờ đây, ta có thể tải siêu dữ liệu của token trong lần tải đầu tiên (Ví dụ 6-24). Ta sẽ
thêm lệnh add vào một hàm loadTokensData mới khi thành phần chính đã tải và sau khi
các token hiện có đã được truy xuất.

Ví dụ 6-24. Lặp qua tất cả token hiện tại và tải siêu dữ liệu của chúng bằng cách truy vấn
contract để truy xuất URL nơi nó được lưu trữ và sau đó là URL để truy xuất siêu dữ liệu
thực tế.

28
Lưu ý rằng tính toàn vẹn của siêu dữ liệu được xác minh bằng cách tính toán hàm băm
của nó trước khi chấp nhận nó. Bạn có thể kiểm tra nó bằng cách sửa đổi siêu dữ liệu đã
lưu trong hệ thống cục bộ của mình (tìm trong thư mục máy chủ / dữ liệu của dự án) và
kiểm tra xem no metadata nào được hiển thị cho token đã sửa đổi.

Điều này cho phép ta liên kết dữ liệu bổ sung với mỗi token không thể thay thế khi chúng
tôi mint nó, thực sự có thể được tận dụng bởi bất kỳ ứng dụng nào hiển thị thông tin
token. Về vấn đề đó, bạn có thể coi metadata token tương đương với siêu dữ liệu
opengraph13 cho một trang HTML thông thường.

1.2.4. Interplanetary Storage

Là giải pháp thay thế cho các giải pháp lưu trữ tập trung, ta có thể lưu trữ dữ liệu của
mình trong InterPlanetary File System (IPFS). IPFS là “một hệ thống phân tán để lưu trữ
và truy cập các tệp, trang web, ứng dụng và dữ liệu.” Nói cách khác, nó hoạt động như
một hệ thống lưu trữ phi tập trung.

IPFS là gì?

29
IPFS hoạt động như một hệ thống tập tin phân tán ngang hàng. Bất kỳ máy tính nào cũng
có thể tham gia mạng, từ máy chủ IPFS chuyên dụng đến người dùng thông thường trong
một gia đình. Bất cứ khi nào người dùng yêu cầu tệp từ mạng, tệp đó sẽ được tải xuống từ
máy tính gần nhất có tệp và được cung cấp cho người dùng khác tải xuống từ vị trí mới
này. Tính khả dụng của nội dung phụ thuộc vào việc có đủ người dùng lưu trữ các tệp
liên quan.

Dữ liệu trong IPFS không được xác định theo vị trí giống như các file trong hệ thống mà
xác định theo nội dung của nó. Khi yêu cầu một tệp từ mạng IPFS, bạn giải quyết nó
bằng mã định danh của nó, không có gì khác hơn là một nội dung của hàm băm. Điều này
đảm bảo tính toàn vẹn của tất cả nội dung, vì khách hàng sẽ xác thực tất cả nội dung nhận
được dựa trên số nhận dạng của nó. Nó cũng cho phép khách hàng yêu cầu một tập tin mà
không cần biết nó được lưu trữ ở đâu trong mạng.

Điều này có nghĩa là nội dung trong IPFS là không thay đổi. Bất kỳ thay đổi nào đối với
nó đều yêu cầu lưu trữ một bản sao mới hoàn toàn dưới một mã định danh mới. Tệp trước
đó sẽ được mạng giữ lại miễn là có người giữ một bản sao của nó.

Tất cả các thuộc tính này làm cho IPFS trở thành một kết hợp tốt cho các ứng dụng
blockchain. Xác minh băm nội dung mà ta xây dựng thủ công trong phần trước đã được
cung cấp bởi giao thức của chính nó. Và bằng cách lập chỉ mục định danh nội dung trong
hợp đồng thông minh (smart contract )thay vì vị trí của nó, ta có thể tách dữ liệu
blockchain khỏi bất kỳ centralized content provider.

Ghi chú

IpFS dựa vào việc người dùng sẵn sàng lưu và chia sẻ nội dung đã có sẵn, điều này có thể
khiến nó giống như một lựa chọn tệ để xây dựng các ứng dụng quan trọng. Tuy nhiên, dữ
liệu sẵn có cho ứng dụng của bạn có thể được cung cấp bằng cách dựa vào dịch vụ ghim
IpFS.Có rất nhiều service hoạt động để lưu trữ dữ liệu này, nhưng chúng tham gia vào

30
mạng IpFS bằng cách cung cấp nội dung của bạn cho tất cả người dùng - nghĩa là có tính
phí.

1.2.5. Sử dụng IpFS trong ứng dụng

Để bật hỗ trợ IPFS trong ứng dụng , trước tiên ta cần kết nối với IPFS node. Điều này
tương tự như việc kết nối với một Ethereum node để truy cập vào mạng Ethereum, với sự
khác biệt là chúng ta không cần private key hoặc currency để ghi bất kỳ dữ liệu nào vào
IPFS.

Ta có thể lưu trữ public node IPFS của riêng mình như một phần của ứng dụng hoặc dựa
vào nhà cung cấp bên thứ ba. Ví dụ, Infura cung cấp không chỉ các node công khai
Ethereum mà còn cung cấp các node IPFS, có nghĩa là chúng ta có thể sử dụng cổng
IPFS của chúng trực tiếp.

Tuy nhiên, cũng có thể người dùng đang chạy node IPFS của riêng họ trong máy tính của
họ. IPFS cung cấp một tiện ích mở rộng trình duyệt, companion, kết nối với một node cục
bộ và làm cho trình duyệt hỗ trợ IPFS. Điều này bao gồm việc thêm hỗ trợ cho các liên
kết ipfs và đưa một đối tượng ipfs vào đối tượng cửa sổ toàn cầu trong tất cả các trang
web - giống như Metamask thêm một đối tượng nhà cung cấp ethereum vào tất cả các
trang web.

Note

Lưu ý rằng cũng có tùy chọn chạy một node IpFS trong trang web của riêng bạn. thư viện
js-ipfs cung cấp triển khai tương thích với trình duyệt của toàn bộ giao thức IpFS, vì vậy
bạn có thể khởi động daemon IpFS khi người dùng truy cập ứng dụng của bạn. Tuy
nhiên, điều này có thể làm cho ứng dụng của bạn nặng hơn nhiều và quy trình IpFS trong
ứng dụng không ổn định như quy trình chuyên dụng. Do đó, phương pháp được đề xuất
để tương tác với mạng là sử dụng IpFS HTTP API để kết nối với một node riêng biệt.

31
Mở kết nối IPFS trong ứng dụng rất giống với việc mở kết nối Ethereum: trước tiên, kiểm
tra xem có sẵn đối tượng kết nối toàn cầu (global connection) hay không và nếu không
hãy quay trở lại public node (Ví dụ 6-25). Sử dụng thư viện javascript ipfs-http-
client@30.1 để truy cập mạng, là thư viện IPFS tương đương với web3.js.

Ví dụ 6-25. Tạo một phiên bản máy khách ipfs mới. Trước tiên, kiểm tra xem đối tượng
toàn cục được đưa vào bởi tiện ích mở rộng companion, có khả dụng hay không. Nếu
không, quay lại kết nối với cổng Infura IPFS

Sử dụng ứng dụng khách IPFS mới này, giờ đây ta có thể dễ dàng lưu metadeta token của
mình vào mạng IPFS thay vì vào một máy chủ tập trung.

Tìm lại kết quả từ mạng IPFS với URL cũng đơn giản. Lưu ý rằng không cần xác minh
tính toàn vẹn của nó nữa, vì giao thức sẽ tự động xử lý điều đó.

32
1.2.6 Lưu trữ ứng dụng trên IPFS

IPFS được sử dụng không chỉ để lưu trữ dữ liệu ứng dụng của chính nó mà còn dùng để
phân quyền tối đa. Tất cả code của ứng dụng phía client có thể được tải lên IPFS và được
lấy từ đó. Nhưng làm thế nào người dùng của chúng tôi có thể truy cập nó từ một trình
duyệt thông thường? Hoặc giải quyết nó mà không cần phải chỉ định hàm băm?

Vấn đề đầu tiên có thể được giải quyết thông qua các cổng IPFS (xem Ví dụ 6-26). Cổng
IPFS là một trang web thông thường cung cấp nội dung từ mạng IPFS. Nó cho phép bạn
truy cập bất kỳ mục IPFS nào tại đường dẫn / ipfs / CID, trong đó CID là nội dung hàm
băm xác định từng đối tượng trên mạng.

Ví dụ 6-26.Bạn có thể truy cập phiên bản cũ hơn của trang web ipfs.io tại các địa chỉ sau
trực tiếp trên trình duyệt của mình thông qua bất kỳ cổng public nào được liệt kê. Vì ID
của nội dung là hàm băm của nó, bạn có thể chắc chắn rằng tất cả các cổng sẽ phân tán
chính xác cùng một đối tượng

https://gateway.ipfs.io/ipfs/
QmeYYwD4y4DgVVdAzhT7wW5vrvmbKPQj8wcV2pAzjbj886/

https://ipfs.infura.io/ipfs/
QmeYYwD4y4DgVVdAzhT7wW5vrvmbKPQj8wcV2pAzjbj886/

https://cloudflare-ipfs.com/ipfs/
QmeYYwD4y4DgVVdAzhT7wW5vrvmbKPQj8wcV2pAzjbj886/

33
Vấn đề thứ hai, tên thân thiện với người dùng cho các trang IPFS, có thể được giải quyết
bằng cách sử dụng DNSLink. DNSLink là một quá trình ánh xạ tên DNS với nội dung
IPFS bằng cách sử dụng các bản ghi DNS TXT.

Giả sử chúng tôi muốn ánh xạ trang web của mình trong IPFS tới miền example.com.
Bằng cách thêm bản ghi TXT vào _dnslink.example.com với giá trị dnslink = / ipfs /
CID, mọi cổng IPFS sẽ tự động ánh xạ mọi yêu cầu đến /ipfs/example.com với nội dung
được chỉ định.

Không chỉ vậy, ta còn có thể chỉ định bản ghi CNAME DNS cho tên miền trỏ đến một
cổng. Điều này cho phép ta tự động phục vụ trang của mình tại example.com trực tiếp từ
cổng do IPFS chỉ định.Tóm lại, toàn bộ quy trình để truy cập trang web của chúng tôi sẽ

• Người dùng yêu cầu example.com

• Truy vấn DNS trả lời bằng CNAME tới gateway.ipfs.io.

• Người dùng gửi yêu cầu tới IP của gateway.ipfs.io bằng cách sử dụng example.com
dưới dạng header của Host .

• Cổng thực hiện truy vấn TXT DNS cho cả example.com và _dnslink.example.com và
nhận IPFS CID làm phản hồi.

• Cổng phân phối nội dung từ IPFS đến người dùng cuối một cách minh bạch.

Mặt khác mức độ điều hướng có thể được giới thiệu bằng cách dựa vào IPNS,
InterPlanetary Name System]. Hệ thống này cho phép bạn có các liên kết có thể thay đổi
tham chiếu đến nội dung IPFS, các liên kết IPNS cũng được băm thay vì ID IPFS và cập
nhật trang web lên phiên bản mới. Điều này giúp ta không phải sửa đổi bản ghi DNS
TXT của mình khi triển khai trang web phiên bản mới.

1.3. Kết luận

Ta đã phát sinh 2 vấn đề khi xây dựng các ứng dụng Ethereum: cách thực hiện các truy
vấn phức tạp trên chuỗi dữ liệu và cách lưu trữ lượng lớn dữ liệu. Cả hai vấn đề này đều

34
yêu cầu phải nhìn ra từ bên ngoài Ethereum và dựa vào các services khác - tập trung hoặc
phi tập trung. Ta đã xem xét cách viết các bài kiểm tra đơn vị tương tác với mạng
Ethereum và một số chiến lược để xử lý việc tổ chức lại chuỗi.

Bên cạnh các vấn đề hoặc chiến lược cụ thể được nêu chi tiết trong chương này, có lẽ
điều quan trọng nhất là xác định nhu cầu phân quyền ứng dụng của bạn. Mặc dù chúng ta
đã quen với các yêu cầu phi chức năng quen thuộc như hiệu suất, bảo mật hoặc khả năng
sử dụng, các ứng dụng blockchain cũng cần phải tính đến sự phân quyền. Phi tập trung là
lý do cốt lõi tại sao một blockchain được sử dụng ngay từ đầu, vì vậy bạn nên đặc biệt
chú ý đến nó.

Giống như các yêu cầu phi chức năng khác, phân quyền không phải là hệ nhị phân. Ứng
dụng của ta có thể có các mức độ phân quyền khác nhau, tùy thuộc vào thành phần nào
của ngăn xếp được tập trung, mức độ tin tưởng mà ta đặt vào các bên thứ ba thương mại
thay vì mạng ngang hàng hoặc mức độ kiểm soát của người dùng đối với dữ liệu của
riêng họ.

Ví dụ: một ứng dụng tài chính có thể hoàn toàn tập trung ngoại trừ giao thức cơ bản quản
lý tài sản của người dùng, cho phép hiệu suất cao và trải nghiệm người dùng tốt, đồng
thời đảm bảo người dùng rằng họ có thể quản lý tài sản của mình tại bất kỳ thời điểm
nào. Mặt khác, một ứng dụng tập trung vào việc vượt qua kiểm duyệt cần phải là ứng
dụng phi tập trung để không có nguy cơ bị nhà cung cấp dịch vụ đóng cửa.

Các ứng dụng khác nhau sẽ có các yêu cầu khác nhau. Điều quan trọng là ta phải xác
định giải pháp của mình, để ta biết mình có quyền truy cập vào giải pháp nào và xây
dựng cấu trúc ứng dụng của mình cho phù hợp.

35

You might also like