去中心化網絡的測試庫

老雅痞 發佈 2022-10-28T21:49:07.114956+00:00

去中心化網絡應用的世界是一個令人興奮的地方,近年來已經爆炸性增長,IPFS和以太坊等技術為點對點的網絡提供了可能性,創造出生活在傳統的客戶/伺服器模式之外的應用,用戶可以直接互動和控制自己的數據。

去中心化網絡應用的世界是一個令人興奮的地方,近年來已經爆炸性增長,IPFS和以太坊等技術為點對點的網絡提供了可能性,創造出生活在傳統的客戶/伺服器模式之外的應用,用戶可以直接互動和控制自己的數據。

同時,它仍然不成熟,對於軟體開發人員來說,它缺乏傳統的基於HTTP的網絡應用程式世界的許多能力和生態系統。在這個領域工作的開發者的工具和庫要少得多。

在過去的一年裡,我一直在努力改善這個問題(作為歐盟地平線的下一代網際網路計劃資助的項目的一部分),為IPFS和以太坊建立網絡攔截庫:MockIPFS和Mockthereum。這些庫既是一個立即有用的自動化測試庫,以支持現代集成測試和CI工作流程,也是為使用任一(或兩個)技術的網絡應用程式建立更通用的網絡代理工具的基礎。

如果這聽起來很酷,而且你只是想直接進入並親自嘗試這些,你可以從GitHub.com/httptoolkit/mockipfs/和github.com/httptoolkit/mockthereum/開始。

另一方面,如果你想聽聽這在實踐中能做什麼,並了解一下它在引擎蓋下是如何工作的,請繼續閱讀。

構建網絡應用的新方法

去中心化的網絡應用程式通常使用許多不同的技術,在堆棧的不同層次上,如:

  • IPFS:用於去中心化的靜態內容託管和數據存儲
  • 以太坊:用於去中心化的全局狀態、對該狀態的計算和金融交易
  • Filecoin/Storj :用於付費的去中心化長期內容存儲
  • WebRTC:用於點對點的原始數據傳輸和視頻/音頻連接
  • Service workers:一個允許完全離線的網絡應用的JavaScript API
  • Handshake(HNS)/以太坊名稱系統(ENS):將域名映射到網絡應用上
  • GunDB:用於網絡的去中心化資料庫,具有點對點同步功能
  • HTTP:用於與現有的 「傳統」網絡互動,以及與允許訪問許多這些協議的節點通信。

通過結合這些技術,有可能創建一個從分布式網絡提供服務的網絡應用程式,而不是一個可能離線或被封鎖的單一伺服器,它可以存儲數據,與他人通信,並普遍提供你期望從傳統SaaS網絡應用程式獲得的所有功能。

現在,這種架構的一個例子看起來像:

  • 將一個基於JS的單頁網絡應用程式發布到IPFS,使用服務工作者使其完全離線和本地運行
  • 使用HNS/ENS將域名映射到發布的內容哈希上
  • 允許用戶通過WebRTC進行點對點的通信,直接發送消息或在上面使用GunDB來同步結構化數據存儲
  • 將用戶的持久性內容發布到IPFS(可能是加密的),他們可以在自己的IPFS節點中固定,或通過Filecoin/Storj付費鏡像
  • 修改全局狀態或通過以太坊支持付費交易。

鑑於這樣的設置,擁有兼容瀏覽器的用戶(默認為Brave,或安裝了IPFS伴侶和Metamask擴展的Chrome/Firefox等)可以加載網絡應用,在他們的機器上使用它,並從其他人那裡發送和接收數據,所有這些都沒有一個中央伺服器參與,所有數據都存儲在本地,或在他們自己控制的服務中。

即使原來的出版商不存在了,他們所有的基礎設施都關閉了,如果圍繞這個模型設計得好,用戶將能夠永遠使用這個應用程式。

這至少是理論上的。在實踐中,有相當多的粗糙邊緣,所以這是很複雜和具有挑戰性的,但這是一個有趣的空間,有許多新技術不斷出現和發展。即使在今天,上面的清單也遠遠沒有完成,這些技術放在一起,暗示了網絡上去中心化技術的一個有趣的未來。

不過,HTTP如何與此相聯繫是值得注意的。雖然這些協議中的每一個都是獨立於HTTP的,但對於網絡應用中的瀏覽器連接,它們中的許多都使用HTTP作為最後一英里的傳輸。例如,對於IPFS,你通常會在你的機器上運行一個IPFS節點,直接與IPFS網絡進行通信,然後配置你的瀏覽器使用該節點進行所有的IPFS,然後所有的IPFS互動將通過從你的網絡應用中向該節點發出HTTP請求而發生。同樣,對於以太坊來說,在絕大多數情況下,網絡上的以太坊互動涉及到對託管的以太坊API的HTTP請求(這與中心化服務不一樣,因為任何工作節點都可以同樣工作,但必須使用一些託管節點)。

進入MockIPFS和Mockthereum

如果你建立一個這樣的網絡應用,你很快就會發現,測試它是一個嚴重的挑戰。幾乎沒有可用的工具或庫,所以你被迫要麼完全手動模擬出API、庫或原始HTTP請求(非難事,而且很難做到準確),要麼運行一個真正的IPFS/以太坊節點進行測試(慢、重、有限,而且有持久的狀態,有用,但不是你想要的自動化測試用例)。

MockIPFS和Mockthereum採取了不同的方法:在HTTP層面上的無狀態和完全可配置的模擬,對客戶端庫和託管節點之間使用的HTTP交互協議有內置的解釋和模擬。

這意味著你可以:

  • 在一行代碼中模擬兩種協議的最常見的互動結果。
  • 直接監控、記錄或斷言客戶端和網絡之間的所有以太坊/IPFS互動。
  • 模擬連接問題和超時等場景。
  • 在幾毫秒內創建、重置和銷毀模擬節點。
  • 在同一台機器上同時運行多個完全隔離的模擬節點,以最小的開銷,輕鬆地並行運行測試。

用MockIPFS測試一個使用IPFS的dweb應用

一個去中心化的網絡應用有很多方式想與IPFS交互,但最常見的是你想從一個CID中讀取一些IPFS數據,所以讓我們用這個作為例子。

要在網絡上做到這一點,你通常要寫這樣的代碼:

import * as IPFS from "ipfs-http-client";
import itAll from 'it-all';
import {
    concat as uint8ArrayConcat,
    toString as uint8ToString
} from 'uint8arrays';

const IPFS_CONTENT_PATH = '/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu';

async Function runMyApp(ipfsNodeConfig) {
    const ipfsClient = IPFS.create(ipfsNodeConfig);

    // ...
    // Somewhere in your code, read some content from IPFS:
    const content = await itAll(ipfs.cat(IPFS_CONTENT_PATH));
    const contentText = uint8ToString(uint8ArrayConcat(content));
    // ...
}

runMyApp({ /* Your IPFS node config */ });

這使用了ipfs-http-client,這是一個廣泛使用的官方庫,用於在網絡上使用IPFS,向本地IPFS節點發出HTTP請求,以獲取IPFS的內容ID(在這個例子中為Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu)。

使用MockIPFS來測試這段代碼,並模擬出返回的結果,看起來是這樣的:

// Import MockIPFS and create a fake node:
import * as MockIPFS from 'mockipfs';
const mockNode = MockIPFS.getLocal();

describe("Your tests", () => {
    // Start & stop your mock node to reset state between tests
    beforeEach(() => mockNode.start());
    afterEach(() => mockNode.stop());

    it("can mock & query IPFS interactions", async () => {
        // Define a rule to mock out this content:
        const ipfsPath = "/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu";
        const mockedContent = await mockNode.forCat(ipfsPath).thenReturn("Mock content");

        // Run the code that you want to test, configuring the app to use your mock node:
        await runMyApp(mockNode.ipfsOptions); // <-- IPFS cat() here will read 'Mock content'

        // Afterwards, assert that we saw the requests we expected:
        const catRequests = await mockNode.getQueriedContent();
        expect(catRequests).to.deep.equal([
            { path: ipfsPath }
        ]);
    });
});

在這種情況下,MockIPFS處理請求,解析API調用以匹配所使用的特定CID,然後返回正確編程和格式的內容,就像一個真正的IPFS節點,完全集成測試你的應用程式的整個客戶端代碼,但沒有真正的IPFS節點的開銷、複雜性或不可預測性。

像這樣模擬ipfs.cat是最簡單的情況,但MockIPFS可以走得更遠:

  • 通過mockNode.forPinAdd(cid).... 等調用,測試內容引腳和取消引腳,例如對無效/重複的引腳拋出錯誤。
  • 用mockNode.forNameResolve(name).thenTimeout()為IPNS查詢注入超時。
  • 用mockNode.forAdd().thenAcceptPublishAs(hash)模擬內容發布結果。

要想開始,請看README以獲得更多的細節和完整的API文檔,或者看一下測試套件以獲得涵蓋IPFS API每個主要領域的完整工作實例。

使用Mockthereum測試以太坊的dweb應用

在以太坊上構建網絡應用時,一個常見的互動是調用合約--即查詢區塊鏈上的數據,而不實際創建交易。

使用流行的以太坊網絡客戶端Web3.js,這樣做的代碼可能看起來像:

import Web3 from 'web3';

// Parameters for some real Web3 contract:
const CONTRACT_ADDRESS = "0x...";
const JSON_CONTRACT_ABI = { /* ... */ };

async function runMyApp(ethNodeAddress) {
    const web3 = new Web3(ethNodeAddress);

    // ...
    // Somewhere in your code, call a method on the Ethereum contract:
    const contract = new web3.eth.Contract(JSON_CONTRACT_ABI, CONTRACT_ADDRESS);
    const contractResult = await contract.methods.getText("test").call();
    // ...
}

runMyApp(/* Your Ethereum node API address */);

和上面的IPFS一樣,我們可以很容易地定義一個模擬節點,它可以攔截這個請求,返回任何值或者模擬任何你想要的其他行為:

// Import Mockthere and create a fake node:
import * as Mockthereum from 'mockthereum';
const mockNode = Mockthereum.getLocal();

describe("Your tests", () => {
    // Start & stop your mock node to reset state between tests
    beforeEach(() => mockNode.start());
    afterEach(() => mockNode.stop());

    it("can mock & query Ethereum interactions", async () => {
        // Define a rule to mock out the specific contract method that's called:
        const mockedFunction = await mockNode.forCall(CONTRACT_ADDRESS) // Match any contract address
            // Optionally, match specific functions and parameters:
            .forFunction('function getText(string key) returns (string)')
            .withparams(["test"])
            // Mock contract results:
            .thenReturn('Mock result');

        // Run the code that you want to test, configuring the app to use your mock node:
        await runMyApp(mockNode.url); // <-- Contract call here will read 'Mock result'

        // Afterwards, assert that we saw the contrat calls we expected:
        const mockedCalls = await mockedFunction.getRequests();
        expect(mockedCalls.length).to.equal(1);

        expect(mockedCalls[0]).to.deep.include({
            // Examine full interaction data, included decoded parameters etc:
            to: CONTRACT_ADDRESS,
            params: ["test"]
        });
    });
});

要想開始了解許多其他可以模擬的以太坊行為,請看README,或者看一下測試套件中涵蓋廣泛的典型以太坊交互的完整工作實例。

測試之外

在上面的快速例子中,我們已經看到了MockIPFS和Mockthereum如何處理特定的普通交互的簡單演示,通過配置客戶端的模擬節點地址而不是真實節點,這樣模擬節點就可以獨立於更廣泛的網絡處理所有流量。

當這樣使用時,所有不匹配的請求將收到默認響應,例如,所有IPFS添加請求將顯示為成功(同時沒有真正發布任何東西),所有以太坊錢包餘額將為零。

不過這兩個庫都可以超越這一點。每個庫都可以配置為將不匹配的請求轉發到其他地方,這樣部分或全部流量就會通過模擬節點傳遞到真正的IPFS/以太坊節點。這使得它有可能為調試而記錄流量,或者只模擬交互的一個子集,而所有其他的請求都表現得很正常。

要配置這一點,在創建模擬節點時,在getLocal調用中傳遞一個unmatchedRequests選項,像這樣:

const ipfsMockNode = MockIPFS.getLocal({
  unmatchedRequests: { proxyTo: "http://localhost:5001" }
});
const ethMockNode = Mockthereum.getLocal({
    unmatchedRequests: { proxyTo: "http://localhost:30303" }
});

通過這種配置,你可以在瀏覽器中把這些節點作為你的正常節點地址(通過在IPFS companion/Metamask/等中配置地址),用於高級代理用例。默認情況下,它們的行為就像它們代理的真實節點一樣,但你可以額外添加接收到的交互的日誌,以便在你瀏覽網頁時監控客戶端以太坊/IPFS的交互,或者你可以通過添加規則來匹配這些請求,模擬出甚至禁用某些類型的交互。

為自己入門

在保持本文簡短的同時,很難將這些工具的所有功能都擠進去。但如果這已經激起了你的興趣,可以去GitHub上看看這些庫本身,看看深入的入門指南和解釋,以及涵蓋其全部功能的詳細API文檔:MockIPFS, Mockthereum.

關鍵字: