BTC
$96,000
5.73%
ETH
$3,521.91
3.97%
HTX
$0.{5}2273
5.23%
SOL
$198.17
3.05%
BNB
$710
3.05%
lang
简体中文
繁體中文
English
Tiếng Việt
한국어
日本語
ภาษาไทย
Türkçe
Trang chủ
AI AI
Tin nhanh
Bài viết
Sự kiện
BlockBeats Pro
Thêm
Thông tin tài chính
Chuyên đề
Hệ sinh thái chuỗi khối
Mục nhập
Podcast
Data
OPRR

Tìm hiểu sâu: Điều gì xảy ra khi 1 DAI được gửi?

Đọc bài viết này mất 194 phút
Đây không phải là một loại tiền tệ internet "ma thuật". Bạn có thể thấy mọi đoạn mã đang chuyển động và thậm chí có thể chạm vào nó.
Nguồn gốc: NOTONLYOWNER
Tác giả gốc: tincho
Bản gốc biên soạn: Nhóm dịch thuật cộng đồng Denglian


Bạn có 1 DAI, hãy sử dụng ví (chẳng hạn như Metamask) để gửi 1 DAI đến "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" (là Vitalik.eth) và nhấp vào Gửi.


Sau một thời gian, ví sẽ hiển thị rằng giao dịch đã được xác nhận. Đột nhiên, Vitalik.eth hiện có tài sản là 1 DAI. Điều gì đang xảy ra ở hậu trường?


Hãy phát lại. và phát lại ở chế độ chuyển động chậm.


Bạn đã sẵn sàng chưa?


Xây dựng giao dịch


Ví là phần mềm hỗ trợ gửi giao dịch đến mạng Ethereum.


Giao dịch chỉ là một cách để thông báo cho mạng Ethereum rằng bạn, với tư cách là người dùng, muốn thực hiện một hành động. Trong trường hợp này, điều này sẽ gửi 1 DAI cho Vitalik. Và những ví như Metamask giúp thiết lập các giao dịch như vậy một cách tương đối đơn giản.


Trước tiên chúng ta hãy xem giao dịch mà ví sẽ tạo, giao dịch này có thể được biểu diễn dưới dạng một đối tượng với các trường và giá trị tương ứng.


Giao dịch của chúng ta lúc đầu sẽ như thế này:



Trường mô tả địa chỉ đích. Trong trường hợp này, “0x6b175474e89094c44da98b954eedeac495271d0f” là địa chỉ của hợp đồng thông minh DAI.


Đợi đã, cái gì cơ?


Chúng ta có nên gửi 1 DAI cho Vitalik không? Không phải là địa chỉ của Vitalik sao?


Ồ, không. Để gửi DAI, bạn phải thực hiện một giao dịch thực thi một đoạn mã được lưu trữ trong chuỗi khối (tên ưa thích cho cơ sở dữ liệu Ethereum) để cập nhật số dư được ghi lại của DAI. Logic để thực hiện cập nhật này và bộ lưu trữ liên quan được lưu giữ trong một chương trình máy tính công cộng bất biến trong cơ sở dữ liệu Ethereum - hợp đồng thông minh DAI.


Vì vậy, bạn muốn thiết lập một giao dịch có nội dung trong hợp đồng "Này anh bạn, hãy cập nhật số dư nội bộ của bạn, lấy 1 DAI khỏi số dư của tôi và thêm 1 DAI vào Số dư của Vitalik” . Trong biệt ngữ Ethereum, cụm từ “hey bạn ơi” có nghĩa là đặt địa chỉ của DAI trong trường “đến” của giao dịch.


Tuy nhiên, trường "đến" là không đủ. Từ thông tin được cung cấp trong giao diện người dùng ví ưa thích của bạn, ví sẽ yêu cầu bạn điền vào một số trường khác để thiết lập giao dịch được định dạng phù hợp:



Vì vậy, khi bạn gửi 1 DAI cho Vitalik, bạn không sử dụng địa chỉ của Vitalik cũng như không điền vào trường số tiền. Trên 1. Đó là cuộc sống khó khăn như thế nào (và chúng ta chỉ đang khởi động thôi). Trường số tiền thực sự được bao gồm trong giao dịch và thể hiện số lượng ETH (đơn vị tiền tệ gốc của Ethereum) bạn đang gửi trong giao dịch. Vì bạn không muốn gửi ETH ngay bây giờ nên ví sẽ đặt chính xác trường này thành 0.


Đối với "chainId", đây là trường chỉ định chuỗi mà giao dịch được thực hiện trên đó. Đối với Ethereum Mainnet, nó là 1. Tuy nhiên, vì tôi sẽ chạy thử nghiệm này trên một nhánh cục bộ của mạng chính nên tôi sẽ sử dụng ID chuỗi của nó: 31337, Các chuỗi khác có số nhận dạng khác.


Còn trường "nonce" thì sao? Đó là con số cần tăng lên mỗi khi bạn gửi giao dịch lên mạng. Đó là một cơ chế bảo vệ để tránh các vấn đề phát lại. Ví thường đặt số này cho bạn. Để làm điều này, họ truy vấn mạng, hỏi nonce mới nhất được tài khoản của bạn sử dụng là gì và sau đó đặt nonce của giao dịch hiện tại cho phù hợp. Trong ví dụ trên, nó được đặt thành 0, mặc dù trên thực tế nó sẽ phụ thuộc vào số lượng giao dịch được thực hiện trên tài khoản của bạn.


Tôi vừa nói rằng ví "truy vấn mạng". Ý tôi là ví thực hiện lệnh gọi chỉ đọc tới nút Ethereum và nút trả lời với dữ liệu được yêu cầu. Có nhiều cách khác nhau để đọc dữ liệu từ nút Ethereum, tùy thuộc vào vị trí của nút và loại API mà nó hiển thị.


Chúng ta hãy tưởng tượng rằng chiếc ví có quyền truy cập mạng trực tiếp vào nút Ethereum. Thông thường hơn, ví tương tác với các nhà cung cấp bên thứ ba như Infura, Alchemy, QuickNode và nhiều nhà cung cấp khác. Các yêu cầu tương tác với các nút tuân theo một giao thức đặc biệt để thực hiện các cuộc gọi từ xa. Giao thức này được gọi là JSON-RPC.


Một yêu cầu ví cố gắng để có được một tài khoản không cần thiết sẽ trông như thế này:



Trong đó "0x6fC27A75d76d8563840691DDE7a947d7f3F179ba" sẽ là tài khoản của người khởi tạo. Như bạn có thể thấy từ phản hồi, nonce của nó là 0.


Ví sử dụng các yêu cầu mạng (trong trường hợp này là thông qua HTTP) để lấy dữ liệu, yêu cầu điểm cuối JSON-RPC do nút hiển thị. Tôi chỉ bao gồm một dữ liệu ở trên, nhưng ví thực sự có thể truy vấn bất kỳ dữ liệu nào họ cần để thiết lập giao dịch. Đừng ngạc nhiên nếu trong cuộc sống thực, bạn nhận thấy có nhiều yêu cầu mạng hơn để truy vấn những thứ khác. Ví dụ: đây là ảnh chụp nhanh về lưu lượng Metamask mà nút kiểm tra cục bộ nhận được trong vài phút:



Trường dữ liệu giao dịch


DAI là một hợp đồng thông minh. Logic chính của nó được triển khai tại địa chỉ “0x6b175474e89094c44da98b954eedeac495271d0f” trên mạng chính Ethereum.


Cụ thể hơn, DAI là Token có thể thay thế được, tuân thủ tiêu chuẩn ERC20 -- một loại hợp đồng đặc biệt. Điều này có nghĩa là DAI triển khai ít nhất các giao diện được nêu chi tiết trong Đặc tả ERC20. Theo thuật ngữ web2 (hơi xa vời), DAI là một dịch vụ mạng nguồn mở bất biến chạy trên Ethereum. Vì nó tuân theo đặc tả ERC20 nên có thể biết trước (không nhất thiết phải xem mã nguồn) các giao diện chính xác được hiển thị để tương tác với nó.


Một lưu ý nhỏ: không phải tất cả các Token ERC20 đều như thế này. Việc triển khai một giao diện nhất định (có lợi cho tương tác và tích hợp) không thể đảm bảo hành vi cụ thể. Tuy nhiên, đối với bài tập này, chúng ta có thể giả định một cách an toàn rằng DAI hoạt động giống như một token ERC20 khá chuẩn.


Trong hợp đồng thông minh DAI, có nhiều chức năng (mã nguồn có thể tìm thấy tại tại đây), nhiều trong số đó đến trực tiếp từ đặc tả ERC20. Đặc biệt lưu ý là bộ chuyển bên ngoài  Chức năng.



Chức năng này cho phép bất kỳ ai nắm giữ DAI Token Người đó chuyển một phần của nó sang một tài khoản Ethereum khác. Chữ ký của nó là "chuyển (địa chỉ, uint256)". Tham số đầu tiên là địa chỉ tài khoản của người nhận và tham số thứ hai là số nguyên không dấu biểu thị số lượng Token được chuyển.


Hiện tại, chúng tôi không quan tâm đến chi tiết cụ thể về hoạt động của chức năng này. Tin tôi đi, bạn sẽ biết rằng chức năng này sẽ trừ số tiền đã chuyển từ số dư của người gửi và sau đó tăng số tiền của người nhận tương ứng.


Điều này rất quan trọng vì khi thiết lập giao dịch để tương tác với hợp đồng thông minh, người ta cần biết chức năng nào của hợp đồng sẽ được thực hiện. và những tham số nào cần truyền. Giống như trong web2, bạn muốn gửi yêu cầu POST tới API web. Rất có thể bạn sẽ cần chỉ định URL chính xác và các thông số của nó trong yêu cầu. Điều này cũng giống như vậy. Chúng tôi muốn chuyển 1 DAI, vì vậy chúng tôi phải biết cách chỉ định trong giao dịch rằng nó sẽ thực hiện chức năng "chuyển" trên hợp đồng thông minh DAI.


May mắn thay, điều này rất đơn giản và trực quan.


Haha, tôi đùa đấy. KHÔNG.


Đây là những gì bạn phải đưa vào giao dịch để gửi 1 DAI đến Vitalik (hãy nhớ địa chỉ "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"):



Hãy để tôi giải thích.


Để đơn giản hóa việc tích hợp và có cách tiêu chuẩn hóa để tương tác với các hợp đồng thông minh, hệ sinh thái Ethereum áp dụng (một số dạng) "Đặc tả ABI hợp đồng" ( ABI là viết tắt của Giao diện nhị phân ứng dụng). Trong các tình huống sử dụng thông thường, tôi nhấn mạnh rằng trong các tình huống sử dụng thông thường, để thực thi các chức năng hợp đồng thông minh, trước tiên bạn phải tuân theo Đặc tả ABI hợp đồng mã hóa cuộc gọi. Các trường hợp sử dụng nâng cao hơn có thể không tuân theo thông số kỹ thuật này, nhưng chúng tôi chắc chắn sẽ không đi sâu vào lỗ hổng này. Tôi chỉ muốn nói rằng các hợp đồng thông minh thông thường được lập trình với Solidity, như DAI, thường tuân theo hợp đồng Đặc điểm kỹ thuật ABI.


Bạn có thể thấy ở trên là byte kết quả được mã hóa ABI khi sử dụng hàm "transfer(address,uint256)" của DAI để chuyển 1 DAI đến địa chỉ "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".


Có nhiều công cụ có thể mã hóa giao dịch ABI (chẳng hạn như: https://chaintool.tech/calldata) và hầu hết các ví đều triển khai mã hóa ABI theo cách nào đó để tương tác với hợp đồng. Vì lợi ích của ví dụ này, chúng tôi có thể sử dụng công cụ dòng lệnh của noreferrer" target="_blank">cast  để xác minh rằng chuỗi byte ở trên là chính xác, nó có thể mã hóa ABI cuộc gọi với cụ thể tham số:



Điều gì đang làm phiền bạn ? Có câu hỏi nào không?


Ồ, xin lỗi, vâng. 100000000000000 đó. Thành thật mà nói, tôi thực sự muốn đưa ra cho bạn một lập luận mạnh mẽ hơn ở đây. Nhiều Token ERC20 được biểu thị bằng 18 chữ số thập phân. Ví dụ: ĐẠI.


Chúng ta chỉ có thể sử dụng số nguyên không dấu trong hợp đồng. Do đó, 1 DAI thực sự được lưu trữ dưới dạng 1 * 10^18 - tức là 100000000000000.


Bây giờ chúng ta có một chuỗi byte được mã hóa ABI đẹp mắt có trong trường "dữ liệu" của giao dịch. Bây giờ nó trông như thế này:



Khi chúng tôi bước vào giai đoạn thực hiện giao dịch thực tế, chúng tôi sẽ xem lại nội dung của trường "dữ liệu" này.


Gas


Bước tiếp theo là quyết định số tiền phải trả cho giao dịch. Bởi vì hãy nhớ rằng, tất cả các giao dịch đều phải trả phí cho mạng lưới các nút dành thời gian và nguồn lực để thực hiện và xác minh chúng.


Phí thực hiện giao dịch được thanh toán bằng ETH. Và số ETH cuối cùng sẽ phụ thuộc vào số tiền mà giao dịch của bạn tiêu thụ net Gas (nghĩa là chi phí tính toán cao đến mức nào), bạn sẵn sàng trả bao nhiêu cho mỗi đơn vị gas và số tiền tối thiểu mà mạng sẵn sàng chấp nhận.


Nhìn chung, từ góc độ người dùng, bạn càng trả nhiều tiền thì giao dịch càng nhanh. Vì vậy, nếu bạn muốn trả 1 DAI cho Vitalik ở khối tiếp theo, bạn có thể cần đặt mức phí cao hơn mức bạn sẵn sàng đợi vài phút (hoặc lâu hơn) cho đến khi xăng rẻ hơn.


Các ví khác nhau có thể thực hiện các phương pháp khác nhau để quyết định số lượng gas phải trả. Tôi không biết bất kỳ cơ chế nào được mọi người sử dụng. Các chiến lược để xác định mức phí chính xác có thể liên quan đến việc truy vấn thông tin liên quan đến khí từ các nút (chẳng hạn như phí cơ bản tối thiểu được mạng chấp nhận).


Ví dụ: trong yêu cầu bên dưới, bạn có thể thấy plug-in trình duyệt Metamask gửi yêu cầu đến nút kiểm tra cục bộ để lấy dữ liệu phí gas khi thiết lập một giao dịch:



Phản hồi yêu cầu được đơn giản hóa trông giống như:



「eth_feeHistory」 Điểm cuối được hiển thị bởi một số nút, cho phép truy vấn dữ liệu phí giao dịch. Nếu tò mò, bạn có thể đọc đây hoặc tại đây Chơi với nó, hoặc xem Thông số kỹ thuật đây.

Các ví phổ biến cũng sử dụng các dịch vụ ngoài chuỗi phức tạp hơn để ước tính chi phí giao dịch gas và đề xuất các giá trị hợp lý cho người dùng. Dưới đây là ví dụ về ví yêu cầu điểm cuối công khai của dịch vụ mạng và nhận nhiều dữ liệu hữu ích liên quan đến Gas:



Xem đoạn phản hồi:



Tuyệt vời phải không?


Dù sao, tôi hy vọng bạn đã quen với việc đặt giá gas không hề đơn giản, đó là bước cơ bản để thiết lập một giao dịch thành công. Ngay cả khi tất cả những gì bạn muốn làm là gửi 1 DAI. Đây là hướng dẫn giới thiệu thú vị để tìm hiểu sâu hơn một số cơ chế này để đặt mức phí chính xác hơn cho các giao dịch của bạn.


Với một số thông tin cơ bản ban đầu, bây giờ chúng ta hãy quay lại giao dịch thực tế. Có 3 trường liên quan đến Gas cần được đặt:



Ví sẽ sử dụng một số cơ chế được đề cập để điền vào hai trường đầu tiên cho bạn. Điều thú vị là, mỗi khi giao diện người dùng ví cho phép bạn chọn giữa phiên bản giao dịch "chậm", "thông thường" hoặc "nhanh", nó thực sự đang cố gắng quyết định giá trị nào hoạt động tốt nhất cho các thông số chính xác đó. Bây giờ bạn có thể hiểu rõ hơn nội dung phản hồi có định dạng JSON nhận được từ ví trên.


Để xác định giá trị của trường thứ ba, GasLimit, có một cơ chế thuận tiện mà ví có thể sử dụng để mô phỏng giao dịch trước khi thực sự thực hiện giao dịch đó. Điều này cho phép họ ước tính chính xác lượng gas mà một giao dịch sẽ tiêu thụ và do đó đặt GasLimit hợp lý.


Tại sao không đặt GasLimit thật lớn? Tất nhiên là để bảo vệ tiền của bạn. Hợp đồng thông minh có thể có logic tùy ý và bạn là người trả tiền cho việc thực hiện nó. Bằng cách chọn GasLimit hợp lý khi bắt đầu giao dịch, bạn có thể tự bảo vệ mình và tránh tình huống xấu hổ khi sử dụng hết số tiền ETH trong tài khoản của mình để trả phí gas.


Việc ước tính Gas có thể được thực hiện thông qua điểm cuối "eth_estimateGas" của nút. Ví có thể sử dụng cơ chế này để mô phỏng giao dịch của bạn và xác định GasLimit chính xác cho lần chuyển DAI của bạn trước khi gửi 1 DAI. Phản hồi yêu cầu từ ví có thể trông như thế này:



Trong phản hồi, bạn có thể thấy rằng việc chuyển tiền sẽ cần khoảng 34706 đơn vị Gas.


Hãy đưa thông tin này vào phần trọng tải của giao dịch:



Hãy nhớ rằng, "maxPriorityFeePerGas" và "maxFeePerGas" cuối cùng sẽ phụ thuộc vào điều kiện mạng khi giao dịch được gửi. Ở trên tôi đặt một số giá trị tùy ý chỉ vì ví dụ này. Đối với giá trị được đặt cho GasLimit, tôi chỉ tăng ước tính lên một chút để gửi xác suất thực hiện giao dịch.


Danh sách truy cập và loại giao dịch


Hãy nhận xét ngắn gọn về hai trường còn lại được đặt trong giao dịch của bạn.


Đầu tiên là trường "accessList". Các kịch bản sử dụng nâng cao hoặc các kịch bản biên có thể yêu cầu giao dịch chỉ định trước địa chỉ tài khoản sẽ được truy cập và khe lưu trữ của hợp đồng, do đó giảm chi phí giao dịch.


Tuy nhiên, việc xây dựng trước một danh sách như vậy có thể không đơn giản và mức tiết kiệm gas hiện tại có thể không đáng kể. Đặc biệt đối với các giao dịch đơn giản như gửi 1 DAI. Vì vậy, chúng ta có thể trực tiếp đặt nó vào một danh sách trống. Mặc dù hãy nhớ rằng nó tồn tại vì một lý do và nó nằm trong tương lai Có thể có ý nghĩa hơn.


Thứ hai, Loại giao dịch. Nó được chỉ định trong trường "loại". Loại là một chỉ báo về những gì bên trong giao dịch. Giao dịch của chúng tôi sẽ là giao dịch loại 2 -- vì nó tuân theo tại đây Định dạng được chỉ định.



Ký giao dịch


Làm thế nào để các nút biết rằng đó là tài khoản của bạn chứ không phải tài khoản của người khác đang gửi giao dịch?


Chúng ta đã đạt đến bước quan trọng trong việc thiết lập một giao dịch hợp lệ: ký kết.


Khi ví đã thu thập đủ thông tin để thiết lập giao dịch và bạn nhấn gửi, nó sẽ ký điện tử vào giao dịch của bạn. Làm thế nào để ký? Sử dụng khóa riêng của tài khoản của bạn (ví của bạn có quyền truy cập) và thuật toán mã hóa liên quan đến các đường cong elip có tên là ECDSA.


Đối với những người tò mò, thứ thực sự được ký là hàm băm "keccak256" được nối giữa loại giao dịch và nội dung được mã hóa RLP.



Mặc dù bạn không nên có rất nhiều kiến thức về mật mã để hiểu điều này. Nói một cách đơn giản, quá trình này sẽ xác nhận thỏa thuận. Nó làm cho nó chống giả mạo bằng cách đóng dấu nó bằng một con dấu thông minh mà chỉ có khóa riêng của bạn mới có thể tạo ra. Từ giờ trở đi, bất kỳ ai có quyền truy cập vào giao dịch đã ký (ví dụ: nút Ethereum) đều có thể xác minh bằng mật mã rằng chính tài khoản của bạn đã tạo ra giao dịch.


Nói rõ hơn: việc ký tên không phải là mã hóa. Giao dịch của bạn luôn rõ ràng. Một khi chúng được công khai, bất kỳ ai cũng có thể rút ra ý nghĩa từ nội dung của chúng.


Trong quá trình ký kết một giao dịch, không có gì đáng ngạc nhiên khi chữ ký được tạo ra. Trong thực tế, đó là một loạt các giá trị kỳ lạ không thể đọc được mà bạn thường thấy được gọi là "v", "r" và "s".

Nếu bạn muốn hiểu sâu hơn về ý nghĩa thực sự của những điều này và tầm quan trọng của chúng đối với việc khôi phục địa chỉ tài khoản của bạn thì Internet chính là bạn của bạn.


Bạn có thể xem @ethereumjs/tx gói. Ngoài ra, bạn có thể sử dụng một số tiện ích từ ethers Một ví dụ cực kỳ đơn giản về việc ký giao dịch để gửi 1 DAI có thể trông như thế này:



Đối tượng thu được sẽ trông giống như :



Tuần tự hóa


Bước tiếp theo là tuần tự hóa giao dịch đã ký. Điều này có nghĩa là mã hóa đối tượng đẹp ở trên thành một chuỗi byte nhị phân để nó có thể được gửi đến mạng Ethereum và được nút nhận sử dụng.


Phương thức mã hóa được Ethereum chọn được gọi là RLP. Giao dịch được mã hóa như sau:



trong đó byte ban đầu là loại giao dịch.


Dựa trên đoạn mã trước đó, bạn thực sự có thể thấy giao dịch được tuần tự hóa được thêm vào như thế này:



Đây là gửi 1 đến Vitalik trong nhánh phân nhánh cục bộ của tôi trên mạng chính Ethereum Tải trọng thực tế của DAI.


Gửi giao dịch


Sau khi được thiết lập, ký và xê-ri hóa, giao dịch phải được gửi đến An Nút Ethereum.


Các nút cung cấp điểm cuối JSON-RPC thuận tiện nơi các nút có thể nhận yêu cầu giao dịch.


Sử dụng "eth_sendRawTransaction" để gửi giao dịch. Đây là lưu lượng truy cập mạng được ví sử dụng khi gửi giao dịch:



Yêu cầu-phản hồi tóm tắt có dạng:



Kết quả trong phản hồi chứa hàm băm của giao dịch: "bf77c4a9590389b0189494aeb2b2d68dc5926a5e20430fb5bc3c610b59db3fb5". Chuỗi ký tự thập lục phân dài 32 byte này là mã định danh duy nhất của giao dịch được gửi.


Tiếp nhận nút


Làm thế nào chúng ta có thể biết khi nào một nút Ethereum nhận được một giao dịch được ký tuần tự? Điều gì sẽ xảy ra?


Một số người có thể hỏi trên Twitter, một số có thể đọc một số bài viết trên Medium. Những người khác thậm chí có thể đọc tài liệu hoặc xem video


Chỉ có một nơi duy nhất để tìm ra sự thật: trong mã nguồn. Hãy sử dụng go-ethereum v1.10.18 ( hay còn gọi là Geth), việc triển khai một nút Ethereum phổ biến ("ứng dụng khách thực thi" sau khi Ethereum chuyển sang Proof-of-Stake). Từ giờ trở đi, tôi sẽ kèm theo các liên kết đến mã nguồn của Geth để bạn có thể theo dõi.


Sau khi nhận được phản hồi về "eth_sendRawTransaction" điểm cuối, nút cần phân tích cú pháp giao dịch được tuần tự hóa có trong yêu cầu thân hình. . Vì vậy, nó bắt đầu Giải tuần tự hóa các giao dịch. Từ giờ trở đi, các nút sẽ có thể truy cập các trường giao dịch dễ dàng hơn.


Tại thời điểm này, các nút đã bắt đầu xác thực các giao dịch. Đầu tiên , đảm bảo rằng phí giao dịch (tức là Giá * GasLimit) không vượt quá mức tối đa mà nút sẵn sàng chấp nhận (rõ ràng là Theo mặc định, đây là Ether). Ngoài ra Sau đó, hãy đảm bảo giao dịch được bảo vệ chống phát lại (theo EIP155 --bạn có nhớ trường "ID chuỗi" mà chúng tôi đã đặt trong giao dịch không?), hoặc nút sẵn sàng chấp nhận các giao dịch không được bảo vệ.


Các bước tiếp theo bao gồm GửiGiao dịchtớiNhóm giao dịch (còn được gọi là mempool). Nói một cách đơn giản, nhóm này đại diện cho tập hợp các giao dịch mà một nút nhận biết được tại một thời điểm cụ thể. Theo như chỉ có các nút biết thì chúng vẫn chưa được tích hợp vào chuỗi khối.


Trước khi giao dịch thực sự được đưa vào nhóm, nút kiểm tra để xem liệu nó đã biết về nó chưa. Và chữ ký ECDSA của nó là hợp lệ. Nếu không, hãy hủy giao dịch.


Sau đóMempool nặng bắt đầu. Như bạn có thể nhận thấy, có rất nhiều logic tầm thường để đảm bảo rằng nhóm giao dịch "vui vẻ và lành mạnh".


Có khá nhiều điều quan trọng ở đâyxác minh. Ví dụ: GasLimit Bên dưới khối GasLimit hoặc quy mô giao dịch Không nhiều hơnMức tối đa được phép hoặc nonce là dự kiến hoặc người gửi có Đủ tiền để trang trải chi phí tiềm năng (tức là value + GasLimit*price ), vân vân.


Mặc dù chúng tôi có thể tiếp tục phát triển nhưng chúng tôi không ở đây để trở thành chuyên gia về mempool. Ngay cả khi muốn thực hiện điều này, chúng tôi cần cân nhắc rằng mỗi nhà khai thác nút có thể thực hiện một cách tiếp cận khác nhau để quản lý mempool miễn là họ tuân theo các quy tắc đồng thuận của mạng. Điều này có nghĩa là thực hiện xác nhận đặc biệt hoặc tuân theo các quy tắc ưu tiên giao dịch tùy chỉnh. Để chỉ gửi 1 DAI, chúng ta có thể coi mempool như một tập hợp các giao dịch đang háo hức chờ được chọn và đưa vào một khối.


Sau khi thêm thành công giao dịch vào nhóm (và thực hiện bản ghi), nút Trả về hàm băm giao dịch. Đây chính xác là những gì chúng tôi thấy được trả về trong phản hồi yêu cầu JSON-RPC trước đó.


Kiểm tra mempool



Nếu bạn vượt qua Metamask Hoặc bất kỳ ví tương tự nào kết nối với một nút truyền thống theo mặc định để gửi giao dịch, đến một lúc nào đó nó sẽ "đổ bộ" vào mempool của nút công khai. Bạn có thể đảm bảo điều này bằng cách tự mình kiểm tra mempool.


Có một điểm cuối thuận tiện mà một số nút được hiển thị được gọi là "eth_newPendingTransactionFilter". Đó có thể là frontrunning  ;bot Một người bạn tốt. Việc truy vấn định kỳ điểm cuối này cho phép chúng tôi quan sát, trước khi một giao dịch được đưa vào chuỗi, 1 DAI hiện đã đi vào bộ nhớ của nút kiểm tra cục bộ.


Trong mã Javascript, điều này có thể được thực hiện bằng cách:



Để xem lệnh gọi "eth_newPendingTransactionFilter" thực tế, chúng ta có thể trực tiếp kiểm tra lưu lượng mạng:



Từ bây giờ, tập lệnh sẽ (tự động) thăm dò các thay đổi trong mempool. Đây là lần gọi đầu tiên trong số nhiều lệnh gọi định kỳ tiếp theo, kiểm tra các thay đổi:



Sau khi nhận được giao dịch, nút cuối cùng phản hồi bằng hàm băm của nó:



Tóm tắt yêu cầu - phản hồi có dạng:



Trước đó tôi đã nói "nút truyền thống" nhưng không giải thích nhiều. Ý tôi là, có một số các nút chuyên biệt hơn có đặc điểm nhóm bộ nhớ riêng . Chúng cho phép người dùng "ẩn" các giao dịch khỏi công chúng trước khi chúng được đưa vào một khối.


Bất kể trường hợp cụ thể nào, cơ chế này thường liên quan đến việc thiết lập một kênh riêng giữa người khởi tạo giao dịch và người xây dựng khối. Dịch vụ bảo vệ Flashbots là một ví dụ rõ ràng. Kết quả thực tế là ngay cả khi bạn giám sát các mempool bằng phương pháp trên, bạn không thể truy cập các giao dịch đi vào trình tạo khối thông qua các kênh riêng tư.


Giả sử rằng giao dịch gửi 1 DAI được gửi tới mạng thông qua kênh thông thường và không sử dụng dịch vụ này.


Tuyên truyền


Để một giao dịch được đưa vào một khối, nó cần phải đạt được điểm bằng cách nào đó nơi nó có thể được thiết lập và nút đề xuất giao dịch. Trong bằng chứng công việc trong Ethereum, những điều này các nút được gọi là thợ mỏ. Trong Bằng chứng cổ phần Ethereum , được gọi là người xác minh. Mặc dù thực tế thường phức tạp hơn. Lưu ý rằng có thể có nhiều cách để xây dựng khối thuê ngoài cho một Dịch vụ chuyên nghiệp.


Là một người dùng bình thường, bạn không cần biết những nhà sản xuất khối này là ai hoặc họ ở đâu. Thay vào đó, bạn có thể chỉ cần gửi một giao dịch hợp lệ đến bất kỳ nút thông thường nào trong mạng, đưa nó vào nhóm giao dịch và để giao thức ngang hàng thực hiện công việc của chúng.


một số như thế này Giao thức p2pkết nối các nút Ethereum với nhau. Trong số những thứ khác, chúng cho phép thường xuyên Giao dịch trao đổi.


Ngay từ đầu, tất cả các nút đều được kết nối với các nút ngang hàng của chúng (theo mặc định, Tối đa 50 nút ngang hàng (Peers)) cùng nhau Nghe và phát giao dịch.


Khi một giao dịch đến mempool, nó sẽ được xử lý Đã gửi tới tất cả các đồng nghiệp được kết nối chưa biết về giao dịch.


Để đạt hiệu quả, chỉ có một tập hợp con ngẫu nhiên các nút được kết nối (căn bậc hai) là Gửi giao dịch hoàn tất. Phần còn lại là Chỉ gửi hàm băm giao dịch. Các nút này có thể yêu cầu trả lại giao dịch hoàn chỉnh nếu cần.


Một giao dịch không thể tồn tại mãi trong mempool của nút. Nếu nó không bị loại bỏ trước tiên vì một số lý do khác (ví dụ: nhóm đã đầy, giá giao dịch thấp hoặc được thay thế bằng một giao dịch mới có số lần/giá cao hơn), thì nó có thể bị loại bỏ sau một khoảng thời gian nhất định (mặc định là 3 giờ ) tự động Xóa.


Các giao dịch hợp lệ trong mempool được coi là đã sẵn sàng để người xây dựng khối nhận và xử lý là Theo dõi trong giao dịch đang chờ xử lý. Cấu trúc dữ liệu nàycó thể Truy vấn trình tạo khốiđể có được các giao dịch có thể xử lý được phép đưa vào chuỗi.


Chuẩn bị công việc và bao gồm giao dịch


Các giao dịch sẽ đến tay người khai thác sau khi duyệt qua mempools Node ( ít nhất là tại thời điểm viết bài này). Loại nút này đặc biệt nặng nề đối với những người thực hiện đa nhiệm. Đối với những người quen thuộc với Golang, điều này có nghĩa là có khá nhiều quy trình và kênh trong logic liên quan đến khai thác. Đối với những người không quen thuộc với Golang, điều này có nghĩa là hoạt động bình thường của người khai thác không thể được giải thích một cách tuyến tính như tôi mong muốn.


Mục tiêu của phần này gồm có hai phần. Trước tiên, hãy hiểu cách thức và thời điểm các giao dịch của chúng tôi được người khai thác lấy ra khỏi bộ nhớ. Thứ hai, tìm hiểu thời điểm bắt đầu thực hiện giao dịch.

Khi thành phần khai thác của nút được khởi tạo, ít nhất hai điều liên quan sẽ xảy ra. Trước tiên, hãybắt đầu Nghe khi giao dịch mới đến mempool. Thứ hai, Một số các vòng lặp cơ bảnđược kích hoạt.


Trong biệt ngữ Geth, hành động xây dựng một khối bằng một giao dịch và niêm phong nó được gọi là "cam kết công việc". Vì vậy, chúng tôi muốn hiểu hoàn cảnh mà điều này đã xảy ra.


Điểm mấu chốt làvòng lặp "công việc mới". Đây là một quy trình độc lập hoạt động khi một nút nhận được các loại thông báo khác nhau sẽ kích hoạt việc gửi tác phẩm. Trình kích hoạt này cần được gửi Một yêu cầu công việc tới một trình xử lý đang hoạt động khác trên nút đó (chạy vòng lặp "chính"). Khi nhận đượcKhi công việc đó được yêu cầu, Công việc gửi bắt đầu.


Nút đã bắt đầu thực hiện một số hoạt độngChuẩn bị ban đầu. Chủ yếu bao gồm việc tạo tiêu đề khối. Điều này bao gồm việc tìm kiếm Khối gốc, đảm bảo Dấu thời gian của khối đang được xây dựng là chính xác, Đặt số khối, GasLimit, địa chỉ coinbasePhí cơ bản và các nhiệm vụ khác.


Sau đó, công cụ đồng thuận được gọi để thực hiện tiêu đề khối "Chuẩn bị đồng thuận". Điều nàytính toán đúng độ khó của khối(phụ thuộcvào phiên bản mạng hiện tại). Nếu bạn đã nghe nói về “quả bom độ khó” của Ethereum thì bạn sẽ biết điều đó.


Lưu ý của người dịch: Không có bom độ khó nào sau TheMerge.


Tiếp theo, bối cảnh con dấu khối đã tạo. Ngoài các hành động khác, điều này bao gồm Lấy trạng thái đã biết cuối cùng. Đây là trạng thái mà giao dịch đầu tiên trong khối được tạo sẽ được thực thi. Đây có thể là giao dịch của chúng tôi gửi 1 DAI.


Sau khi khối sẵn sàng, nó sẽ bắt đầuThực hiện giao dịch.


Chúng ta đã đạt đến điểm này: cho đến nay, giao dịch đang chờ xử lý của chúng ta vẫn nằm thoải mái trong mempool của nút, cùng với các giao dịch khác Đã nhận.


Theo mặc định, Các giao dịch được sắp xếp theo giá và số lần trong một khối. Đối với trường hợp của chúng tôi, vị trí của giao dịch trong khối thực sự không liên quan.


Bắt đầu ngay bây giờ theo trình tựThực hiện các giao dịch này. Một giao dịchđã được thực hiện Sau khi thực hiện, mỗi giao dịch sẽ được xây dựng dựa trên trạng thái kết quả của giao dịch trước đó.


Thực thi


Một giao dịch Ethereum có thể được coi là một quá trình chuyển đổi trạng thái.


Trạng thái 0: Bạn có 100 DAI và Vitalik cũng có 100.


Giao dịch: Bạn gửi 1 DAI cho Vitalik.


Trạng thái 1: Bạn có 99 DAI và Vitalik có 101.


Do đó, việc thực hiện một giao dịch đòi hỏi phải áp dụng một loạt hoạt động vào trạng thái hiện tại của chuỗi khối. Kết quả là tạo ra một trạng thái mới (khác). Đây sẽ được coi là trạng thái hiện tại mới cho đến khi có giao dịch khác xuất hiện.


Trên thực tế, điều này thú vị hơn (và phức tạp hơn). hãy xem nào.


Chuẩn bị (Phần 1)


Trong biệt ngữ Geth, thợ mỏ trong khối Gửi giao dịch. Hành động gửi giao dịch được thực hiện trong ">Môi trường. Môi trường này chứa một cụ thể Trạng thái (bỏ qua mọi thứ khác).

Vì vậy, tóm lại, việc gửi giao dịch về cơ bản là: (1)Hãy nhớ trạng thái hiện tại, (2)Sửa đổi nó bằng cách áp dụng giao dịch, (3) Tùy thuộc vào sự thành công của giao dịch, hãy chấp nhận trạng thái mới hoặc quay trở lại trạng thái ban đầu.


Điều thú vị đã xảy ra ở (2): Giao dịch ứng dụng.


Điều đầu tiên cần lưu ý là Giao dịch được chuyển thành tin nhắn "Tin nhắn". Nếu bạn đã quen với Solidity, nơi bạn thường viết những thứ như "msg.data" hoặc "msg.sender", thì cuối cùng việc đọc "tin nhắn" trong mã Geth là dấu hiệu cho thấy bạn được chào đón đến vùng đất thân thiện.


Khi kiểm tra tin nhắn, bạn sẽ nhanh chóng nhận thấy ít nhất một điểm khác biệt so với các giao dịch. Một tin nhắn có Trường "từ"! Trường này là địa chỉ Ethereum của người ký, được xác định bởi Chữ ký công khaiCó nguồn gốc (bạn có nhớ các trường "v", "r" và "s" kỳ lạ không?).


Bây giờ, môi trường thực thiđang được chuẩn bị thêm. Đầu tiên, với Một môi trường liên quan đến khối được tạo, bao gồm số khối, dấu thời gian, địa chỉ coinbase và khối GasLimit. Sau đó...


Con thú bước vào và đó là Máy ảo Ethereum.


Máy ảo Ethereum (EVM), chịu trách nhiệm thực hiện các giao dịch Dựa trên ngăn xếp 256 -bitcông cụ tính toán, chúng ta có thể mong đợi điều gì từ nó?


EVM là một cỗ máy. Là một cỗ máy, nó có một bộ hướng dẫn (còn gọi là opcode) mà nó có thể thực thi. Bộ hướng dẫn đã thay đổi qua nhiều năm. Do đó, phải có một đoạn mã cho EVM biết nên sử dụng mã nào hôm nay. Khi EVM instance Khi định cấu hình trình thông dịch, nó Chọn bộ mã hành động chính xác, tùy thuộc vào phiên bản đang được sử dụng.


Cuối cùng, trong Thực thi thực tếTrước đây Hai bước cuối cùng. Bối cảnh giao dịch của EVM được tạo (bạn đã bao giờ sử dụng "tx.origin" hoặc "tx.gasprice" trong hợp đồng thông minh Solidity của mình chưa?) và EVM được cấp quyền truy cập vào trạng thái hiện tại.


Chuẩn bị (Phần 2)


Bây giờ đến lượt EVM thực hiện Chuyển đổi trạng thái. Với một thông báo, môi trường và trạng thái ban đầu, nó sử dụng một bộ hướng dẫn giới hạn để chuyển sang trạng thái mới. Trong số này, Vitalik có thêm 1 DAI.


Trước khi áp dụng chuyển đổi trạng thái, EVM phải đảm bảo rằng nó tuân thủ Quy tắc đồng thuận cụ thể. Hãy xem cách thực hiện điều này.


Quá trình xác minh bắt đầu từ những gì Geth đã nói "kiểm tra trước", bao gồm:


1. Xác minh tính xác thực của thông tin. itPhải khớp với địa chỉ "từ" của thư. Ngoài ra, nó Không được là số nonce lớn nhất có thể (bằng cách kiểm tra xem nonce +1 có gây tràn hay không).

2. Đảm bảo rằng tài khoản tương ứng với địa chỉ "từ" của tin nhắn không có mã. Nghĩa là, nguồn gốc giao dịch là Tài khoản thuộc sở hữu bên ngoài (EOA). Do đó, tuân thủ thông số kỹ thuật EIP 3607.

3. Xác minh rằng các trường "maxFeePerGas" ("gasFeeCap" trong Geth) và "maxPriorityFeePerGas" ("gasTipCap" trong Geth) được đặt trong giao dịch nằm trong phạm vi dự kiến. Ngoài ra, phí ưu tiênKhông lớn hơnchi phí tối đa. và maxFeePerGaslớn hơnPhí cơ bản của khối hiện tại.

4. Mua Gas và kiểm tra xem tài khoản có thể thanh toán cho toàn bộ Gas mà nó dự định tiêu thụ hay không. Và còn có mục tiêu Đủ Gas để xử lý giao dịch. Cuối cùng hãy để tài khoảnthanh toán trả trước Phí gas (đừng lo lắng, sẽ có cơ chế hoàn trả trong tương lai).


Tiếp theo, EVMTính toán "khí nội tại" được tiêu thụ bởi các giao dịch. Có một số yếu tố cần xem xét khi tính toán khí nội tại. Đầu tiên, liệu giao dịch có phải là Tạo hợp đồng hay không. Chúng tôi không có điều đó ở đây, vì vậy Khí ban đầu 21000 chiếc. Sau đó, Số byte khác 0 cũng được tính đến. Được tính phí cho mỗi byte khác 016 đơn vị (tuân theo thông số kỹ thuật này ) . Chỉ tính phí mỗi byte 0 giá 4 đơn vị. Cuối cùng, nếu chúng tôi cung cấp Danh sách truy cập, một số gas nữa sẽ được tính trước.


Chúng tôi đặt trường "giá trị" của giao dịch thành 0. Nếu chúng tôi chỉ định một giá trị dương thì bây giờ giá trị đó sẽ là EVM checkThời điểm tài khoản của người gửi thực sự có đủ số dư để thực hiện chuyển ETH. Ngoài ra, nếu chúng tôi thiết lập danh sách truy cập, Bây giờ chúng sẽ được khởi tạo ở trạng thái.


Giao dịch đang được thực hiện không tạo ra hợp đồng. EVM biết điều đóvì " đến" không phải là 0. Do đó, nó sẽ khởi động tài khoản nonceTăng1 và Thực hiện cuộc gọi.


Cuộc gọi sẽ lấy địa chỉ của thông tin từ đến, truyền dữ liệu, không có giá trị và bất kỳ lượng gas nào còn lại sau khi tiêu thụ lượng gas nội tại.


Gọi


Hợp đồng thông minh DAI được lưu trữ tại địa chỉ "0x6b175474e89094c44da98b954eedeac495271d0f". Đây là địa chỉ chúng tôi đặt trong trường "đến" của giao dịch. Lệnh gọi ban đầu này dành cho EVM để thực thi bất kỳ mã nào được lưu trữ ở đó, từng mã một.


Mã opcode là các lệnh EVM, được biểu thị bằng số thập lục phân từ 00 đến FF. Mặc dù họ thường được gọi bằng tên của họ. Ví dụ: "00" là "STOP" và "FF" là "SELFDESTRUCT". Bạn có thể tìm thấy danh sách opcode thuận tiện tại evm.codes.


Vậy chính xác thì opcode của DAI là gì? Rất vui vì bạn đã hỏi:



Don đừng hoảng sợ. Còn quá sớm để tìm ra tất cả.


Hãy bắt đầu từ từ và đặt Phân tích lệnh gọi ban đầu. Đó là Tài liệu ngắn gọn cung cấp bản tóm tắt hay:



Đầu tiên, logicKiểm tra xem đã đạt đến giới hạn độ sâu cuộc gọi hay chưa. Giới hạn này được đặt thành 1024, nghĩa là chỉ có thể có tối đa 1024 lệnh gọi lồng nhau trong một giao dịch. tại đây Một bài viết thú vị để đọc về một số lý do và sự tinh tế đằng sau hành vi này của EVM. Chúng ta sẽ xem cách tăng/giảm độ sâu cuộc gọi sau.


Lưu ý phụ liên quan: giới hạn độ sâu cuộc gọi không phải giới hạn kích thước ngăn xếp của EVM -- kích thước ngăn xếp cũng vậy (trùng hợp?) là 1024 phần tử.


Bước tiếp theo là Đảm bảo rằng nếu giá trị dương được chỉ định trong cuộc gọi thì người gửi có đủ số dư để thực hiện chuyển khoản (Thực thi sau bước ). Chúng ta có thể bỏ qua điều này vì giá trị chúng ta đang gọi bằng 0. Ngoài ra, a Ảnh chụp nhanh trạng thái hiện tại được chụp. Điều này cho phép Dễ dàng khôi phục mọi thay đổi trạng thái.


Chúng tôi biết rằng địa chỉ DAI đề cập đến một tài khoản có mã. Do đó, nó Phải tồn tạitrong không gian trạng thái của Ethereum.


Tuy nhiên, chúng ta hãy tưởng tượng một chút rằng đây không phải là giao dịch gửi 1 DAI. Giả sử đó là một giao dịch rác vô giá trị nhắm mục tiêu đến một địa chỉ mới. Tài khoản tương ứng sẽ cần phải là Thêm vào trạng thái. Tuy nhiên, nếu tài khoản trống rỗng thì sao? Dường như không có lý do gì để theo dõi nó ngoài việc lãng phí dung lượng ổ đĩa của nút. EIP 158 thực hiện một số thay đổi đối với giao thức Ethereum để giúp tránh điều này khỏi xảy ra. Đó là lý do tại sao bạn thấy Điều kiện "nếu" này.


Một điều khác mà chúng tôi biết là DAIKhông phải hợp đồng được biên dịch trước. Hợp đồng biên soạn trước là gì? Sau đây là nội dung được cung cấp bởi Sách vàng Ethereum:



Tóm lại, Ở trạng thái Ethereum, có (cho đến nay) 9 hợp đồng đặc biệt khác nhau. Các tài khoản này (từ 0x0000000000000000000000000000001 đến 0x000000000000000000000009) ra khỏi hộp chứa mã cần thiết để thực hiện các thao tác được đề cập trong tờ giấy màu vàng. Tất nhiên, bạn có thể Tự kiểm tra việc triển khai trong mã của Geth.


Để thêm chút màu sắc cho câu chuyện hợp đồng được biên dịch trước, hãy lưu ý rằng trong mạng chính Ethereum, tất cả các tài khoản này đều có số dư ít nhất 1wei. Đây là Cố ý (ít nhất là cho đến khi người dùng bắt đầu gửi nhầm ether). Hãy nhìn xem, Đây là một giao dịch trong 5 năm qua tới "0x0000000000 000000000000000000000009" được biên dịch trước Tài khoản đã gửi 1wei.


Không sao cả. Sau khi nhận ra rằng địa chỉ đích của cuộc gọi không tương ứng với hợp đồng được biên dịch trước, nodeĐọc mã tài khoản từ trạng thái. Sau đó Đảm bảo nó không trống. Cuối cùng, lệnh EVMsử dụng trình thông dịch của nó để chạy mã này với đầu vào đã cho (nội dung của trường "dữ liệu" của giao dịch).


Thông dịch viên (Phần 1)


Bây giờ là lúc EVM thực sự thực thi mã DAI. Để hoàn thành nhiệm vụ này, EVM có sẵn một số yếu tố. Nó có một Ngăn xếp có thể chứa Tối đa 1024 phần tử (mặc dù chỉ có 16 phần tử đầu tiên có thể truy cập trực tiếp thông qua các mã hoạt động có sẵn); nó có không gian bộ nhớ đọc/ghi; nó có Bộ đếm chương trình; Nó có một không gian bộ nhớ chỉ đọc đặc biệt được gọi là calldataLưu dữ liệu đầu vào của cuộc gọi. Ngoài ra còn có một cái gì đó khác để viết về.


Như thường lệ, có một số bước thiết lập và xác nhận cần thiết trước khi bắt tay vào công việc thú vị. Đầu tiên, Tăng độ sâu cuộc gọi1. Thứ hai, nếu cần, Chế độ chỉ đọc được đặt. Cuộc gọi của chúng tôi không ở chế độ chỉ đọc (xem "false" được truyền ở đây). Nếu không, một số hoạt động EVM sẽ không được phép. Điều này bao gồm các hướng dẫn EVM thay đổi trạng thái 「SSTOR E」 「TẠO E」 「TẠO2」 「SELFDESTRUCT」 「GỌI」 có giá trị dương và "LOG" .


Trình thông dịch hiện là Vòng lặp thực thi. Nó bao gồm theo thứ tự Thực thi trong mã DAIMã hoạt động được chỉ định bởi bộ đếm chương trình và tập lệnh EVM hiện tại. Hiện tại chúng tôi đang sử dụng Bộ hướng dẫn ở London--Đây là khi trình thông dịch được khởi tạo lần đầu tiên Được định cấu hình trong bảng nhảy.


Vòng lặp cũng chịu trách nhiệm cho Duy trì ngăn xếp hoạt động tốt (tránh tràn trên và dưới). và dành Chi phí gas cố định và khi thích hợp Chi phí khí động. Các chi phí động nàyBao gồm, ví dụ: mở rộng bộ nhớ EVM (đọc thêm về cách tính chi phí mở rộng bộ nhớtại đây). Lưu ý rằng Gas được tiêu thụ trước (--không phải sau) khi thực thi opcode.


Bạn có thể tìm thấy hành vi thực tế của từng lệnh có thể có tại Việc triển khai được tìm thấy trong tệp Geth này. Chỉ cần nhìn lướt qua, bạn có thể bắt đầu xem các hướng dẫn này hoạt động như thế nào với ngăn xếp, bộ nhớ, dữ liệu cuộc gọi và trạng thái.


Tại thời điểm này, chúng ta cần truy cập trực tiếp vào các mã hoạt động của DAI và theo dõi việc thực hiện chúng đối với các giao dịch của chúng ta. Tuy nhiên, tôi không nghĩ đây là cách tốt nhất để giải quyết vấn đề này. Tôi thà rời khỏi EVM và Geth trước rồi mới chuyển sang lãnh thổ Solidity. Điều này sẽ cung cấp cho chúng ta cái nhìn tổng quan có giá trị hơn về hành vi cấp cao của các hoạt động chuyển giao ERC20.


Thực thi vững chắc


Hợp đồng thông minh DAI được sử dụngĐộ tin cậyđược mã hóa. Nó là một ngôn ngữ cấp cao hướng đối tượng, khi được biên dịch sẽ tạo ra mã byte EVM, có khả năng triển khai các hợp đồng thông minh trên các chuỗi tương thích EVM (trong trường hợp của chúng tôi là Ethereum).


Có thể tìm thấy mã nguồn của DAI Xác minh trong block explorer hoặc trên GitHub. Để dễ tham khảo, tôi sẽ chỉ vào cái đầu tiên.


Trước khi bắt đầu, chúng ta hãy luôn nhớ rằng EVM không biết gì về Solidity. Nó không biết gì về các biến, chức năng, cách bố trí hợp đồng, mã hóa ABI, v.v. Chuỗi khối Ethereum lưu trữ mã byte EVM đơn giản, không phải mã Solidity cầu kỳ.


Bạn có thể hỏi tại sao khi bạn truy cập bất kỳ trình khám phá khối nào, họ sẽ hiển thị cho bạn mã Solidity trên địa chỉ Ethereum. Ừm, nó chỉ là vỏ bọc thôi. Trong hầu hết các trình khám phá khối, người ta có thể tải lên mã nguồn Solidity và trình duyệt chịu trách nhiệm biên dịch mã nguồn đó với các cài đặt trình biên dịch cụ thể. Nếu đầu ra do trình biên dịch tạo ra khớp với những gì được lưu trữ tại địa chỉ được chỉ định trên blockchain thì mã nguồn của hợp đồng được cho là "đã được xác minh". Từ thời điểm đó trở đi, bất kỳ ai điều hướng đến địa chỉ đó sẽ thấy mã Solidity cho địa chỉ đó, thay vì chỉ mã byte EVM được lưu trữ tại địa chỉ đó.


Một hệ quả không hề nhỏ của điều trên là, ở một mức độ nào đó, chúng tôi tin tưởng rằng các trình khám phá khối sẽ hiển thị cho chúng tôi mã hợp pháp (điều này không nhất thiết phải đúng, thậm chí tai nạn). Nhưng có thể có các lựa chọn thay thế--trừ khi mỗi lần bạn muốn đọc hợp đồng, bạn phải Xác minh mã nguồn dựa vào nút của riêng bạn.


Dù sao thì bây giờ hãy quay lại mã Solidity cho DAI.


Trên hợp đồng thông minh của DAI (sử dụng Solidity v0.5.12), hãy tập trung vào Việc thực thi hàm: "chuyển".



Khi "chuyển" chạy Khi đó, nó sẽ gọi một hàm khác có tên "transferFrom", sau đó trả về bất kỳ cờ boolean nào mà cờ sau trả về. Tham số thứ nhất và thứ hai của "transfer" (ở đây gọi là "dst" và "wad") được chuyển trực tiếp đến "transferFrom". Ngoài ra, hàm này còn đọc địa chỉ của người gửi (trong "msg.sender" dưới dạng Các biến toàn cục về độ rắn).


Trong ví dụ của chúng tôi, đây sẽ là các giá trị được chuyển đến "transferFrom":



Hãy cùng xem"transferFrom" thì:



Đầu tiên, Số dư được kiểm tra so với số tiền được chuyển.



Đơn giản: bạn Bạn không thể chuyển nhiều DAI hơn số dư của bạn. Nếu tôi không có 1 DAI, quá trình thực thi sẽ dừng tại thời điểm này và trả về thông báo lỗi. Lưu ý rằng số dư của từng địa chỉ được theo dõi trong bộ lưu trữ của hợp đồng thông minh. Trong cấu trúc dữ liệu của Bản đồ có tên "balanceOf" . Nếu bạn có ít nhất 1 DAI, tôi có thể đảm bảo với bạn rằng địa chỉ tài khoản của bạn được ghi ở đâu đó ngoài kia.


Thứ hai, Mã thông báo phụ cấp đã được xác minh.



Việc này hiện không liên quan gì đến chúng tôi. Bởi vì chúng tôi không thực hiện chuyển khoản thay mặt cho một tài khoản khác. Mặc dù điều quan trọng cần lưu ý là đây là cơ chế mà tất cả các mã thông báo ERC20 nên triển khai - DAI không phải là một ngoại lệ. Về cơ bản, bạn có thể ủy quyền cho các tài khoản khác chuyển Token DAI từ tài khoản của mình.


Thứ ba, hãy thực hiện mục tiêu Hoán đổi số dư.



Khi gửi 1 DAI At lần này, số dư của người gửi giảm 1000000000000000 và số dư của người nhận tăng thêm 1000000000000000. Các thao tác này được thực hiện khi đọc và ghi trên cấu trúc dữ liệu "balanceOf". Điều đáng chú ý là hai hàm đặc biệt "cộng" và "phụ" được sử dụng để thực hiện các phép tính.


Tại sao không chỉ đơn giản sử dụng toán tử "+" và "-"?


Hãy nhớ: hợp đồng này được biên soạn bằng Solidity 0.5.12. Vào thời điểm đó, các trình biên dịch không bao gồm tính năng kiểm tra tràn/tràn như ngày nay. Vì vậy, các nhà phát triển phải nhớ (hoặc được nhắc nhở) để tự mình triển khai chúng khi thích hợp. Do đó, "thêm" và "phụ" được sử dụng trong hợp đồng DAI. Chúng chỉ là các hàm nội bộ tùy chỉnh thực hiện phép cộng và phép trừ, với các kiểm tra ràng buộc để tránh các vấn đề số học.



Hàm thêm kết hợp x và Thêm y và dừng thực thi nếu kết quả nhỏ hơn x (do đó ngăn ngừa tràn số nguyên).

Hàm phụ trừ y khỏi x và dừng thực thi nếu kết quả của phép toán lớn hơn x (do đó ngăn chặn tình trạng tràn số nguyên).


Thứ tư, Kích hoạt sự kiện chuyển giao (như được chỉ định trong Đặc tả ERC20 được đề xuất).



Sự kiện là một thao tác ghi lại. Dữ liệu phát ra trong sự kiện có thể được lấy từ các dịch vụ ngoài chuỗi đọc chuỗi khối, nhưng sẽ không bao giờ được các hợp đồng khác lấy được.


Trong hoạt động chuyển giao của chúng ta, sự kiện được phát ra dường như ghi lại ba phần tử. Địa chỉ của người khởi tạo "0x6fC27A75d76d8563840691DDE7a947d7f3F179ba", địa chỉ của người nhận "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" và số tiền gửi "1000000000000000".


Hai cái đầu tiên tương ứng với các tham số được đánh dấu là "được lập chỉ mục" trong phần khai báo sự kiện. Các tham số chỉ mục tạo điều kiện thuận lợi cho việc truy xuất dữ liệu, cho phép lọc bất kỳ giá trị bản ghi tương ứng nào. Trừ khi sự kiện được đánh dấu là "ẩn danh", mã nhận dạng của sự kiện cũng được đưa vào làm chủ đề.


Vì vậy, cụ thể hơn, sự kiện "Chuyển" mà chúng tôi đang xử lý thực sự ghi lại 3 chủ đề (mã định danh của sự kiện, địa chỉ của người khởi tạo và địa chỉ của người nhận) và 1 giá trị (số lượng DAI được chuyển). Chúng tôi sẽ đề cập đến nhiều chi tiết hơn về sự kiện này khi chúng tôi đề cập đến nội dung EVM cấp thấp.


Ở cuối hàm, giá trị boolean "true" là trở lại (dưới dạng được đặc tả ERC20 khuyến nghị).



Đây là tín hiệu , cho biết việc chuyển giao đã được thực hiện thành công. Cờ boolean này được chuyển tới hàm "chuyển" đã bắt đầu cuộc gọi (nó cũng chỉ trả về cuộc gọi đó).


Vậy là xong! Nếu bạn đã từng gửi DAI thì đây là logic bạn đã triển khai. Đây là số tiền bạn phải trả để có một mạng lưới nút phi tập trung toàn cầu thực hiện công việc cho bạn.


Đợi một chút. Có lẽ tôi đã đi quá xa một chút. Bởi vì như tôi đã nói với bạn trước đây, EVM không biết gì về Solidity. Nút không triển khai Solidity. Họ thực thi mã byte của EVM.


Đã đến lúc thỏa thuận thực sự.


Thực thi EVM


Trong phần này, nó khá kỹ thuật. Tôi cho rằng bạn đã quen thuộc với mã byte EVM. Nếu bạn chưa quen với nó, tôi thực sự khuyên bạn nên đọc cột này hoặc loạt bài này. Ở đó, bạn sẽ tìm thấy nhiều khái niệm trong phần này được giải thích riêng lẻ và sâu hơn.


Mã byte thô của DAI rất khó đọc -- chúng ta đã chứng kiến điều đó trong phần trước. Một cách tốt hơn để nghiên cứu nó là sử dụng phiên bản đã tháo rời. Bạn có thể tìm thấy các byte đã được phân tách của Dai tại đây mã ( để dễ tham khảo, tôi đã trích xuất nó thành ý chính này ).


Con trỏ bộ nhớ trống và giá trị cuộc gọi


Nếu bạn đã quen với trình biên dịch Solidity, trước Ba chỉ thị không nên gây ngạc nhiên. Nó chỉ đang khởi tạo con trỏ bộ nhớ trống.



Trình biên dịch Solidity dành các khe bộ nhớ từ "0x00" đến "0x80" cho nội dung bên trong. Vì vậy, "con trỏ bộ nhớ trống" là một con trỏ tới khe trống đầu tiên trong bộ nhớ EVM. Nó được lưu trữ ở "0x40" và trỏ đến "0x80" khi khởi tạo.


Hãy nhớ rằng tất cả các mã EVM đều có cách triển khai tương ứng trong Geth. Ví dụ: bạn có thể Thực sự xem cách triển khai "MSTORE" bật hai phần tử ngăn xếp và ghi một từ 32 byte vào bộ nhớ EVM:



Trong mã byte của DAI, các lệnh EVM sau đảm bảo rằng lệnh gọi không giữ bất kỳ giá trị nào . Nếu đúng như vậy, việc thực thi sẽ dừng ở lệnh "REVERT". Hãy chú ý sử dụng hướng dẫn "CALLVALUE" (Được triển khai tại đây để đọc Giá trị hiện được gọi.



Cuộc gọi của chúng tôi không có giá trị (trường "giá trị" của giao dịch được đặt thành 0) -- vì vậy chúng tôi có thể tiếp tục.


Xác minh calldata (Phần 1)


Tiếp theo: một bước kiểm tra khác được trình biên dịch giới thiệu. Lần này, nó tìm ra kích thước của dữ liệu cuộc gọi (thu được thông qua hướng dẫn "CALLDATASIZE"--được triển khai ở đây nếu nó dưới 4 byte (xem hướng dẫn "0x4" và "LT" bên dưới). Trong trường hợp này, nó sẽ nhảy tới vị trí " 0x142". Dừng lại thực hiện lệnh REVERT ở vị trí "0x146".



Điều này có nghĩa là trong hợp đồng thông minh DAI, Kích thước của calldata buộc phải tối thiểu là 4 byte. Điều này là do cơ chế mã hóa ABI được Solidity sử dụng xác định các hàm bằng cách sử dụng bốn byte đầu tiên của hàm băm keccak256 đã ký của chúng (thường được gọi là "bộ chọn hàm" - Xem Thông số kỹ thuật - Tài liệu về độ vững chắc).


Nếu calldata không có ít nhất 4 byte thì không thể xác định được hàm. Vì vậy, trình biên dịch đã đưa ra các hướng dẫn EVM cần thiết để sớm thất bại trong trường hợp này. Đó là những gì bạn đang chứng kiến ở trên.


Để gọi hàm "transfer(address,uint256)", bốn byte đầu tiên của calldata phải khớp với bộ chọn của hàm. Bốn byte này là:



4 byte đầu tiên của trường "data" của giao dịch mà chúng ta đã tạo trước đó hoàn toàn giống nhau:



Bây giờ độ dài của dữ liệu cuộc gọi đã được xác minh, đã đến lúc sử dụng nó. Xem bên dưới cách đặt 4 byte dữ liệu cuộc gọi đầu tiên lên trên ngăn xếp (hướng dẫn EVM chính cần lưu ý ở đây là "CALLDATALOAD", tại đây được triển khai).



Thực ra là "CALLDATALOAD" Thay đổi Phần 32 từ calldata được đẩy lên ngăn xếp. Cần phải cắt ngắn nó bằng "SHR" để giữ lại bốn phần đầu tiên Lễ hội nhân vật.


Bộ lập lịch chức năng


Đừng cố gắng hiểu từng dòng sau đây. Thay vào đó, hãy chỉ chú ý đến những chế độ nâng cao nổi bật. Mình sẽ thêm vài đường phân chia cho rõ ràng hơn:



Không phải ngẫu nhiên mà một số giá trị hex được đẩy vào ngăn xếp có chiều dài 4 byte. Đây thực sự là bộ chọn chức năng.


Bộ hướng dẫn trên là cấu trúc phổ biến của mã byte được tạo bởi trình biên dịch Solidity. Nó thường được gọi là "bộ lập lịch chức năng". Nó tương tự như quá trình if-else hoặc switch. Nó chỉ cố gắng khớp bốn byte dữ liệu cuộc gọi đầu tiên với tập hợp các bộ chọn đã biết cho hàm. Khi tìm thấy kết quả khớp, quá trình thực thi sẽ chuyển sang phần khác của mã byte. Ở đó, hướng dẫn cho chức năng cụ thể đó được đặt trong phần này.


Theo logic trên, EVM khớp bốn byte đầu tiên của calldata với bộ chọn của hàm "chuyển" ERC20: "0xa9059cbb". Và nhảy đến vị trí mã byte "0x6b4". Điều này yêu cầu EVM bắt đầu thực hiện chuyển DAI.


Xác minh calldata (Phần 2)


Sau khi khớp bộ chọn và jump , bây giờ là EVM sẽ bắt đầu chạy mã cụ thể liên quan đến hàm. Nhưng trước khi đi vào chi tiết, nó cần phải nhớ bằng cách nào đó vị trí, nơi tiếp tục thực thi sau khi tất cả logic liên quan đến hàm đã được thực thi.


Cách thực hiện việc này chỉ đơn giản là duy trì vị trí mã byte thích hợp trên ngăn xếp. Vui lòng xem giá trị "0x700" được đẩy bên dưới. Nó sẽ treo xung quanh ngăn xếp cho đến khi được lấy ra vào một thời điểm nào đó (sau này) và được sử dụng để quay lại để kết thúc quá trình thực thi.



Bây giờ hãy cụ thể hơn Tìm hiểu thêm về chức năng "chuyển".


Trình biên dịch nhúng một số logic để đảm bảo rằng kích thước của calldata là chính xác đối với hàm có hai tham số loại "địa chỉ" và "uint256". Đối với hàm "chuyển", ít nhất 68 byte (4 byte cho bộ chọn + 64 byte cho hai đối số được mã hóa ABI).



Nếu kích thước của dữ liệu cuộc gọi nhỏ hơn, hãy thực thi sẽ dừng ở "REVERT" tại vị trí "0x6c9". Vì dữ liệu cuộc gọi của giao dịch của chúng tôi đã được mã hóa bằng ABI chính xác và do đó có độ dài phù hợp nên việc thực thi sẽ chuyển đến vị trí "0x6ca".


Đọc tham số


Bước tiếp theo là để EVM đọc hai tham số được cung cấp trong calldata tham số. Đây là địa chỉ 20 byte "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" và số "1000000000000000" ("0x0de0b6b3a7640000" ở dạng thập lục phân). Cả hai đều được mã hóa ABI theo nhóm 32 byte. Do đó, cần phải có một số thao tác cơ bản để đọc các giá trị chính xác và đặt chúng lên trên cùng của ngăn xếp.



Để được nhiều hơn nữa trực quan, sau khi áp dụng tập lệnh trên theo trình tự (cho đến "0x6fb"), phần trên cùng của ngăn xếp trông như thế này:


p>


Đây là cách EVM nhanh chóng trích xuất hai tham số từ calldata và đặt chúng vào ngăn xếp để sử dụng trong tương lai.


Hai lệnh cuối cùng ở trên (vị trí mã byte "0x6fc" và "0x6ff") chỉ khiến việc thực thi chuyển sang vị trí "0x1df4". Hãy tiếp tục ở đó.


hàm "chuyển giao"


Trong một phân tích Solidity đơn giản, chúng ta thấy rằng "chuyển giao" (address,uint256)" là một trình bao bọc gọi hàm "transferFrom(address,address,uint256)" phức tạp hơn. Trình biên dịch dịch cuộc gọi nội bộ này thành các hướng dẫn EVM này:



Đầu tiên hãy chú ý đến lệnh đẩy giá trị "0x1e01". Điều này hướng dẫn EVM "ghi nhớ" vị trí chính xác mà nó sẽ quay trở lại để tiếp tục thực hiện sau cuộc gọi nội bộ sắp tới.


Sau đó, hãy chú ý đến việc sử dụng "CALLER" (vì trong Solidity, các cuộc gọi nội bộ đều sử dụng "msg.sender"). Và hai hướng dẫn "DUP5". Tất cả điều này đặt ba tham số cần thiết của "transferFrom" lên đầu ngăn xếp: địa chỉ của người gọi, địa chỉ của người nhận và số tiền cần chuyển. Hai tham số cuối cùng đã có sẵn ở đâu đó trong ngăn xếp, vì vậy "DUP5" được sử dụng. Bây giờ phần trên cùng của ngăn xếp có tất cả các tham số cần thiết:



Cuối cùng, sau lệnh "0x1dfd" và "0x1e00", quá trình thực thi sẽ chuyển sang vị trí "0xa25". Ở đó EVM sẽ bắt đầu thực hiện các hướng dẫn tương ứng với chức năng "transferFrom".


chức năng "transferFrom"


Điều đầu tiên cần kiểm tra là liệu người gửi có đủ DAI hay không Số dư - nếu không nó sẽ bị khôi phục. Số dư của người gửi được lưu trong bộ nhớ hợp đồng. Khi đó lệnh EVM cơ bản cần có là "SLOAD". Tuy nhiên, "SLOAD" cần biết cần đọc khe lưu trữ nào. Đối với ánh xạ (các loại cấu trúc dữ liệu Solidity giữ số dư tài khoản trong hợp đồng thông minh DAI), điều này không quá đơn giản để nói.


Ở đây tôi sẽ không đi sâu vào cách bố trí bên trong của các biến trạng thái Solidity trong kho lưu trữ hợp đồng. Bạn có thể đọc nó tại đây Đó là v0.5.15. Tất cả những gì tôi muốn nói là với địa chỉ chính "k" của bản đồ "balanceOf", giá trị "uint256" tương ứng của nó sẽ được lưu trong khe lưu trữ "keccak256(k . p)", trong đó "p" là khe vị trí của bản đồ, "." là một kết nối. Bạn có thể tự mình giải các bài toán.


Tham khảoDung lượng lưu trữ cho các biến trạng thái .


Để đơn giản, chúng tôi chỉ nêu bật một số thao tác cần thực hiện. EVM phải i) tính toán khe lưu trữ được ánh xạ, ii) đọc giá trị, iii) so sánh nó với số lượng được chuyển (giá trị đã có trên ngăn xếp). Do đó, chúng ta sẽ thấy các hướng dẫn như "SHA3" để băm, "SLOAD" để đọc bộ nhớ và "LT" để so sánh.



Nếu người gửi không có đủ DAI, quá trình thực thi sẽ tại "0xa6f" Tiếp tục tại "0xadb" và cuối cùng gặp "REVERT" tại "0xadb". Vì tôi không quên nạp 1 DAI vào số dư tài khoản người gửi của mình nên hãy tiếp tục đến vị trí “0xadc”.


Bộ hướng dẫn sau đây tương ứng với EVM xác minh xem người gọi có khớp với địa chỉ của người khởi tạo hay không (hãy nhớ "if (src != msg.sender .. ) trong hợp đồng.) { ...}" đoạn mã trong hợp đồng).



Vì nó không khớp , sau đó Tiếp tục thực hiện tại vị trí "0xdb2".


Đoạn mã sau có nhắc nhở bạn điều gì không? Kiểm tra các hướng dẫn đang được sử dụng. Tương tự như vậy, đừng đọc từng dòng một. Sử dụng trực giác của bạn để khám phá các mẫu nâng cao và các lệnh phù hợp nhất.



Cảm giác tương tự như Ánh xạ được đọc từ bộ nhớ, đó là vì nó là như vậy! Phía trên là EVM đọc số dư của người gửi từ ánh xạ "balanceOf".


Sau đó, quá trình thực thi sẽ nhảy tới vị trí "0x1e77", phần thân của hàm "sub".


Hàm "phụ" trừ hai số và khôi phục giá trị ban đầu khi số nguyên tràn xuống. Tôi không viết mã byte ở đây, mặc dù bạn có thể thấy nó trong _blank">Ở đây Tìm anh ấy. Kết quả của các phép tính số học được lưu vào ngăn xếp.


Quay lại lệnh tương ứng với phần thân của hàm "transferFrom", kết quả của phép trừ bây giờ sẽ được ghi vào không gian lưu trữ - cập nhật bản đồ "balanceOf". Hãy cố gắng chú ý đến các tính toán sau để có được khe lưu trữ thích hợp cho mục được ánh xạ, được thực hiện bằng lệnh "SSTORE". Lệnh này là lệnh ghi dữ liệu vào trạng thái một cách hiệu quả - nghĩa là cập nhật bộ nhớ của hợp đồng.



Một nhóm khá giống nhau Opcode được chạy để cập nhật số dư tài khoản của người nhận. Đầu tiên là "balanceOf" từ bộ lưu trữ Đọc trên bản đồ. Sau đó Sử dụng chức năng "thêm"Thêm số dư vào số tiền được chuyển. Cuối cùng, kết quả được ghi vào Groove lưu trữ thích hợp.


Bản ghi sự kiện (Nhật ký)


Trong mã của hợp đồng, "Chuyển giao" sự kiện được phát hành sau khi số dư đã được cập nhật. Do đó, phải có một bộ hướng dẫn trong mã byte được phân tích để xử lý các sự kiện đó với dữ liệu thích hợp.

Tuy nhiên, sự kiện này là một thứ khác thuộc về thế giới giả tưởng của Solidity. Trong thế giới EVM, các sự kiện tương ứng với các hoạt động ghi lại.


Việc ghi nhật ký được thực hiện thông qua bộ lệnh "LOG" có sẵn. Có một số biến thể, tùy thuộc vào số lượng chủ đề được ghi lại. Trong trường hợp của DAI, chúng tôi nhận thấy rằng sự kiện "Chuyển khoản" được phát hành có 3 chủ đề.


Không có gì đáng ngạc nhiên khi tìm thấy một tập lệnh chạy lệnh "LOG3".



Trong những hướng dẫn này, Tại ít nhất một giá trị nổi bật: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef". Đây là giá trị nhận dạng chính của sự kiện. Còn được gọi là chủ đề 0. Đó là một giá trị tĩnh được trình biên dịch tính toán tại thời điểm biên dịch (được nhúng trong mã byte thời gian chạy của hợp đồng). Như đã đề cập trước đó, hàm băm của chữ ký sự kiện:



Ngay trước khi đến lệnh "LOG3", ngăn xếp trông như thế này:



Vậy số tiền chuyển ở đâu? Trong bộ nhớ! 


EVM trước tiên được hướng dẫn lưu trữ số tiền trong bộ nhớ trước khi đạt "LOG3". Bằng cách này, nó có thể được sử dụng sau này bằng cách ghi lại hướng dẫn. Nếu nhìn vào vị trí "0xf21", bạn sẽ thấy lệnh "MSTORE" chịu trách nhiệm thực hiện việc này.


Vì vậy, khi đạt đến "LOG3", EVM có thể lấy giá trị thực được ghi từ bộ nhớ một cách an toàn, bắt đầu từ phần bù "0x80" và đọc "0x20" byte (hai phần tử ngăn xếp đầu tiên ở trên).


Một cách khác để hiểu nhật ký là xem Triển khai trong Geth. Ở đó bạn sẽ tìm thấy một hàm duy nhất chịu trách nhiệm xử lý tất cả các hướng dẫn ghi nhật ký. Bạn có thể thấy i) một mảng chủ đề trống được khởi tạo, ii) bù bộ nhớ và kích thước dữ liệu Đọc từ ngăn xếp, iii) chủ đề Đọc từ ngăn xếpchèn vào mảng, iv) Giá trị từ bộ nhớ được đọc từ, v ) nhật ký chứa địa chỉ, chủ đề và giá trị mà nó được phát ra được thêm vào.


Những nhật ký này sau đó được khôi phục như thế nào, chúng ta sẽ sớm tìm hiểu.


Giá trị trả về


Điều cuối cùng mà hàm "transferFrom" thực hiện là trả về giá trị Boolean " ĐÚNG VẬY". Đó là lý do tại sao lệnh đầu tiên sau "LOG3" chỉ đẩy giá trị "0x1" vào ngăn xếp.



Chuẩn bị hướng dẫn tiếp theo Hãy để ngăn xếp thoát khỏi hàm "transferFrom" và quay lại hàm "transfer" của trình bao bọc của nó. Hãy nhớ rằng vị trí của bước nhảy tiếp theo này đã được lưu trữ trên ngăn xếp - đó là lý do tại sao bạn không nhìn thấy nó trong opcode bên dưới.



Quay lại "chuyển" Trong hàm, tất cả những gì bạn phải làm là chuẩn bị ngăn xếp cho bước nhảy cuối cùng. đến điểm mà việc thực thi sẽ bị chấm dứt. Vị trí sắp nhảy cũng đã được lưu trong ngăn xếp trước đó (bạn có nhớ giá trị "0x700" đã được đẩy không?)



Tất cả những gì còn lại là chuẩn bị ngăn xếp cho lệnh cuối cùng: "RETURN". Lệnh này chịu trách nhiệm đọc một số dữ liệu từ bộ nhớ và chuyển lại cho người gọi ban đầu.


Đối với chuyển DAI, dữ liệu được trả về sẽ chỉ bao gồm cờ boolean "true" được hàm "transfer" trả về. Hãy nhớ rằng, giá trị này đã có trên ngăn xếp.


EVM bắt đầu lấy vị trí bộ nhớ trống đầu tiên còn trống. Điều này được thực hiện bằng cách đọc một con trỏ để giải phóng bộ nhớ:



Tiếp theo, giá trị phải được lưu trong bộ nhớ bằng cách sử dụng "MSTORE". Mặc dù không nói trực tiếp cho bạn nhưng những hướng dẫn sau đây chỉ là những hướng dẫn mà trình biên dịch cho là phù hợp nhất để chuẩn bị ngăn xếp cho thao tác "MSTORE".



Lệnh "RETURN" là từ Sao chép dữ liệu trả về trong bộ nhớ. Vì vậy, cần phải cho biết cần đọc bao nhiêu bộ nhớ và bắt đầu từ đâu. Lệnh sau chỉ yêu cầu EVM đọc từ bộ nhớ và trả về byte "0x20", bắt đầu từ con trỏ bộ nhớ trống.



Giá trị trả về "0x0000000000000000000000000000000000000000000000000000000001" (tương ứng với Giá trị Boolean "đúng").


Việc thực thi đã dừng lại.


Trình thông dịch (Phần 2)


Việc thực thi mã byte đã kết thúc. Phải dừng trình thông dịch Lặp lại. Trong Geth, nó thực hiện điều này:



Điều này có nghĩa là việc thực thi mã opcode "RETURN" bằng cách nào đó sẽ trả về lỗi. Kể cả việc thực hiện thành công như của chúng tôi. Trên thực tế, Nó có. Mặc dù nó hoạt động như một lá cờ - khi nó khớp với lá cờ được trả về khi thực thi thành công mã opcode "RETURN", lỗi thực sự là Xóa.


Hoàn tiền gas và thanh toán


Khi quá trình phiên dịch kết thúc, chúng tôi quay lại kích hoạt nó ban đầu trong cuộc gọi . Điều này chạy Hoàn tất thành công. Do đó, dữ liệu được trả về và mọi Gas còn lại chỉ đơn giản là trở lại.

Cuộc gọi cũng đã hoàn tất. Việc thực thi xảy ra sau khi chuyển đổi trạng thái gói.


Đầu tiên Cung cấp hoàn tiền Gas. Nó được thêm vào bất kỳ Gas còn lại nào trong giao dịch. Số tiền hoàn lại được giới hạn ở mức 1/5 lượng Gas đã sử dụng (do EIP 3529 ). Tất cả Gas hiện có sẵn (còn lại và được hoàn lại) là Thanh toán bằng ETH vào tài khoản của người khởi tạo, theo giá phí do người khởi tạo đặt ban đầu trong giao dịch. Tất cả Gas còn lại là Re -thêmvào Gas có sẵn trong khối để các giao dịch tiếp theo có thể tiêu thụ Gas này.


Sau đó Trả tiền tip cam kết ban đầu cho địa chỉ coinbase (địa chỉ công cụ khai thác trong PoW, địa chỉ trình xác thực trong PoS). Điều thú vị là tất cả gas sử dụng trong quá trình thực hiện đều được thanh toán. Mặc dù một số trong số họ sau đó đã được trả lại. Ngoài ra, vui lòng lưu ý tại đây *Cách tính tiền boa hợp lệ. Chỉ cần lưu ý rằng nó bị giới hạn bởi trường giao dịch "maxPriorityFeePerGas". Nhưng quan trọng hơn, hãy nhận ra rằng nó không bao gồm phí cơ bản! Không có gì sai với điều đó – Ethereum thích Xem Đốt ETH.


Cuối cùngKết quả thực thi được bao bọc trong một cấu trúc đẹp hơn. Bao gồm lượng khí đã sử dụng, mọi lỗi EVM có thể hủy bỏ quá trình thực thi (không có lỗi nào trong trường hợp của chúng tôi) và dữ liệu được trả về từ EVM.


Tạo biên nhận giao dịch


Cấu trúc thể hiện kết quả thực hiện hiện đã được thông qua Lên Trở lại  ;. Tại thời điểm này, Geth thực hiện Dọn dẹp nội bộ. Sau khi hoàn thành, nó sẽ Gas được sử dụng trong các giao dịch tích lũy (bao gồm cả tiền hoàn lại).


Quan trọng nhất bây giờ là lúc tạo biên lai giao dịch. Biên nhận là một đối tượng tóm tắt dữ liệu liên quan đến việc thực hiện giao dịch. Thông tin mà nó bao gồm là: Trạng thái thực thi (thành công/thất bại), Giá trị băm giao dịch, Đơn vị gas được sử dụng, Địa chỉ tạo hợp đồng (không có trong trường hợp của chúng tôi), Nhật ký đã phát ra, bộ lọc nở hoa cho các giao dịchKhác.


Chúng tôi sẽ sớm truy xuất toàn bộ nội dung biên lai cho giao dịch của mình.


Nếu bạn muốn tìm hiểu thêm về nhật ký giao dịch và vai trò của bộ lọc nở hoa, vui lòng xem bài viết của noxx.


Khối khai thác


Việc thực hiện các giao dịch tiếp theo tiếp tục diễn ra cho đến khi hết dung lượng của khối Tất cả .


Tại thời điểm này, nút sẽ gọi công cụ đồng thuận tới cuối cùng đã hoàn thành khối. Trong PoW, điều này yêu cầu Tích lũy phần thưởng khai thác (Phần thưởng đầy đủCác khối khác Phần thưởng một phần) và tương ứng Cập nhật trạng thái gốc cuối cùng của khối.


Tiếp theo, khối thực tếđược lắp ráp và sẽ Tất cả dữ liệu được đặt vào đúng vị trí. bao gồm Giao dịch tiêu đề hàm băm hoặc Băm biên nhận và thông tin khác.


Bây giờ mọi thứ đã sẵn sàng cho việc khai thác PoW thực sự. một" nhiệm vụ mới " được tạo và Đẩy tới đúng người nghe. được ủy quyền cho công cụ đồng thuận Nhiệm vụ khai quậtbắt đầu.


Tôi sẽ không giải thích chi tiết cách hoạt động khai thác PoW thực tế. Đã có rất nhiều bài viết về nó trên internet. Chỉ cần lưu ý rằng trong Geth, điều này liên quan đến mục tiêu Quy trình thử và sai đa luồng tới tìm một số, Đáp ứng điều kiện cần. Không cần phải nói, một khi Ethereum chuyển sang bằng chứng cổ phần, quy trình khai thác sẽ được xử lý rất khác.


Khối được khai thác là Đẩy sang kênh thích hợpđã nhận được trong vòng kết quả. trong đó biên nhận và Nhật ký sẽ được cập nhật tương ứngvà cung cấp dữ liệu khối mới nhất sau khi được khai thác hiệu quả.


Cuối cùng, khối này đã được Ghi vào chuỗi và đặt nó ở đầu chuỗi.


Khối phát sóng


Bước tiếp theo là Thông báo cho toàn bộ mạng, một vùng mới Các khối đã được khai thác. Đồng thời, khối này được "_blank">Lưu trữ vào bộ khối đang chờ xử lý bên trong. Hãy kiên nhẫn chờ xác nhận từ các nút khác.

Thông báo đã được hoàn tấtXuất bản một sự kiện cụ thể, Tiếp nhận vòng lặp phát sóng (vòng lặp) khai thác. Ở đó, khối hoàn toàn Truyền bá tới các đồng nghiệp trên mạng con và theo cách nhẹ nhàng hơn cung cấp cho người khác.


Cụ thể hơn, việc phát sóng yêu cầu Gửi các khối dữ liệu tới căn bậc hai của thiết bị ngang hàng được kết nối. Việc triển khai nội bộ là Đẩy dữ liệu vào hàng đợi kênh chặn cho đến khi nó vượt qua lớp p2pGửi. Tin nhắn p2p được xác định là NewBlockMsg. Các nút còn lại nhận được Thông báo nhẹ bao gồm cả hàm băm khối.


Xin lưu ý rằng điều này chỉ có tác dụng với PoW. Trong Bằng chứng cổ phần, Việc truyền bá khối sẽ diễn ra trên công cụ đồng thuận.


Xác minh các khối


Các nút ngang hàng liên tục Nghe tin nhắn. Mục tiêu Các tin nhắn có thể có trình xử lý liên quan và ngay khi nhận được tin nhắn tương ứng, gọi.


Do đó, khi nhận được thông báo "NewBlockMsg" cùng với dữ liệu khối, trình xử lý tương ứng của nó được thực thi. Trình xử lý Giải mã tin nhắn và chạy một số Xác minh sớm. Những xác minh này bao gồmKiểm tra độ tỉnh táo, về cơ bản là đảm bảo chúng được đệm và hạn chế. và chúgiao dịch để xác minh.


Sau đó Người ngang hàng đã gửi tin nhắn được đánh dấulà sở hữu khối. Điều này tránh việc các khối được truyền trở lại nó sau này.


Cuối cùng, gói là chuyển xuống cho Trình xử lý thứ hai trong đó khối sẽ là nhập hàng đợi vào bản sao chuỗi cục bộ. Việc tham gia nhóm được thực hiện thông qua Gửi yêu cầu nhập trực tiếp đến kênh tương ứng Đã hoàn tất. Khi yêu cầu được chọn, nó sẽ kích hoạt Hoạt động xếp hàng thực tế. Cuối cùng ĐẩyChặn dữ liệu vào hàng đợi.


Khối hiện nằm trong hàng đợi cục bộ, sẵn sàng để xử lý. Hàng đợi này là Đọc thường xuyên. Khi khối đến phía trước, nút sẽ nhặt nó lên và Thử nhập.


Trước khi thực sự chèn khối ứng cử viên, có ít nhất hai xác minh đáng được nhấn mạnh.


Đầu tiên, chuỗi địa phương phải có Bao gồm nút cha của khối được truyền bá.


Thứ hai, tiêu đề khốiPhải hợp lệ. Những xác nhận này là xác nhận thực sự. Có nghĩa là, những điều thực sự quan trọng đối với sự đồng thuận và nằm trong _blank">Sách Vàng của Ethereum sẽ được chỉ định. Do đó, chúng là Được cung cấp bởi cơ chế đồng thuận.


Ví dụ: công cụ kiểm tra liệu bằng chứng công việc của khối có hợp lệ hay không hoặc dấu thời gian của khối không phải trong quá khứ hoặc trong tương lai không xa hoặc bị chặn chiều cao đã có tăng đúng, v.v.


Sau khi xác minh rằng nó tuân thủ các quy tắc đồng thuận, toàn bộ khối sẽ tiếp tục Truyền bá tới một tập hợp con các đồng nghiệp. Sau đó Thực tế nhập chạy.


Có rất nhiều điều xảy ra trong quá trình nhập khẩu. Vì vậy, tôi sẽ cắt ngay cuộc rượt đuổi.


Trong Một vài xác minh bổ sung sau, Đã truy xuất trạng thái khối gốc. Đây là trạng thái mà giao dịch đầu tiên của khối mới sẽ được thực hiện. Sử dụng nó làm điểm tham chiếu, Toàn bộ khối được xử lý. Nếu bạn đã từng nghe nói rằng tất cả các nút Ethereum đều phải thực hiện và xác minh mọi giao dịch thì bây giờ bạn có thể chắc chắn. Sau đó, Đăng- trạng thái hoàn thành Đã xác minh (xem cách tại đây). Cuối cùng, The Các khối được ghivào chuỗi cục bộ.


Quá trình nhập thành công tiếp tụcXuất bản khối lên các nút ngang hàng trên các nút khác (không phải là một chương trình phát sóng đầy đủ).


Toàn bộ quá trình xác minh được sao chép tới tất cả các nút nhận khối. Một phần lớn sẽ chấp nhận nó vào chuỗi địa phương của họ và nhiều khối hơn sẽ đến và được chèn lên trên nó sau này.


Truy xuất các giao dịch


Sau khi khai thác một số khối trên khối chứa giao dịch, bạn có thể bắt đầu để giả định một cách an toàn rằng giao dịch thực sự đã được xác nhận.


Truy xuất các giao dịch từ chuỗi rất đơn giản. Tất cả những gì chúng ta cần là giá trị băm của nó. Thuận tiện là chúng tôi đã có nó ngay lần đầu tiên chúng tôi gửi giao dịch.


Bản thân dữ liệu giao dịch, cùng với mã băm và số khối, luôn có thể được truy xuất trên điểm cuối "eth_getTransactionByHash" của nút. Đúng như mong đợi, bây giờ nó trả về:



Có thể yêu cầu biên nhận giao dịch tại điểm cuối "eth_getTransactionReceipt". Tùy thuộc vào nút bạn chạy truy vấn này, bạn cũng có thể nhận được thông tin bổ sung ngoài dữ liệu biên nhận giao dịch dự kiến. Đây là biên lai giao dịch tôi nhận được từ nhánh cục bộ của mạng chính:



Bạn có thấy nó không? Nó ghi ""trạng thái": 1".


Điều này chỉ có nghĩa là một điều: thành công


Tác phẩm


Chắc chắn có thể có nhiều điều hơn nữa về câu chuyện này.


Theo một nghĩa nào đó, nó không bao giờ kết thúc. Luôn có thêm một cảnh báo nữa. Và một lưu ý phụ. Một đường dẫn thực hiện thay thế. Thực hiện một nút khác. Một hướng dẫn EVM khác mà tôi có thể đã bỏ qua. Một ví khác xử lý mọi thứ theo cách khác. Tất cả mọi thứ sẽ đưa chúng ta đến gần hơn với việc tìm ra “sự thật” về điều gì sẽ xảy ra khi bạn gửi 1 DAI.


May mắn thay, đây không phải là điều tôi dự định làm. Tôi hy vọng +10.000 từ cuối cùng không khiến bạn nghĩ như vậy. Cho phép tôi làm sáng tỏ ở đây.


Nhìn lại, bài viết này là sản phẩm phụ của sự kết hợp giữa tò mò và thất vọng.


Tò mò vì tôi đã làm công việc bảo mật hợp đồng thông minh Ethereum được hơn 4 năm rồi, nhưng tôi chưa dành nhiều thời gian để tìm hiểu nó một cách thủ công như trước đây đã thích Khám phá sự phức tạp của lớp cơ sở. Tôi thực sự muốn có được trải nghiệm trực tiếp và xem quá trình triển khai thực tế của Ethereum. Nhưng hợp đồng thông minh luôn bị mắc kẹt ở giữa. Bây giờ tôi cuối cùng đã tìm thấy khoảng thời gian yên bình hơn, có vẻ như đây là thời điểm thích hợp để quay trở lại cội nguồn của mình và bắt đầu cuộc phiêu lưu này.


Nhưng tò mò thôi là chưa đủ. Tôi cần một cái cớ. Một điểm kích hoạt. Tôi biết những gì tôi đang nghĩ sẽ khó khăn. Vì thế tôi cần một lý do đủ mạnh chứ không chỉ để bắt đầu làm việc. Và quan trọng hơn, mỗi khi tôi cảm thấy mệt mỏi khi cố gắng hiểu mã của Ethereum, tôi lại bắt đầu lại.


Tôi tìm thấy nó ở đâu đó mà tôi không tìm. Tôi phát hiện ra nó trong sự thất vọng của tôi.


Thật khó chịu khi chúng ta đã quen với sự thiếu minh bạch kinh khủng khi gửi tiền. Nếu bạn đã từng cần gửi tiền ở một quốc gia đang phát triển với sự kiểm soát vốn ngày càng nghiêm ngặt, chắc chắn bạn sẽ cảm nhận được cảm xúc của tôi. Vì vậy, tôi muốn nhắc nhở bản thân rằng chúng ta có thể làm tốt hơn. Tôi quyết định diễn tả sự thất vọng của mình thành lời.


Bài viết này cũng nhắc nhở tôi. Nếu bạn có thể vượt qua sự mơ hồ, giá cả, những con khỉ trong JPEG, Ponzis, Rugpulls và trộm cắp thì vẫn có giá trị ở đây. Đây không phải là một loại tiền tệ internet "ma thuật". Ở đây có toán học, mật mã và khoa học máy tính thực sự. Là nguồn mở, bạn có thể thấy mọi phần chuyển động. Bạn gần như có thể chạm vào chúng. Bất kể hôm nay là ngày nào, bất kể thời gian nào. Bất kể bạn là ai. Bất kể bạn đến từ đâu.



Liên kết gốc


Chào mừng bạn tham gia cộng đồng chính thức của BlockBeats:

Nhóm Telegram đăng ký: https://t.me/theblockbeats

Nhóm Telegram thảo luận: https://t.me/BlockBeats_App

Tài khoản Twitter chính thức: https://twitter.com/BlockBeatsAsia

举报 Báo lỗi/Báo cáo
Chọn thư viện
Thêm mới thư viện
Hủy
Hoàn thành
Thêm mới thư viện
Chỉ mình tôi có thể nhìn thấy
Công khai
Lưu
Báo lỗi/Báo cáo
Gửi