第9章:Merkle DAG
9.1 Merkle树原理
Merkle树(哈希树)是一种二叉树结构,其中每个叶节点是数据块的哈希值,每个非叶节点是其子节点哈希值的哈希。这种结构提供了高效的数据完整性验证。
Merkle树结构:
Root Hash
/ \
Hash A Hash B
/ \ / \
Hash1 Hash2 Hash3 Hash4
| | | |
Data1 Data2 Data3 Data4
验证过程:
class MerkleTree {
constructor(leaves) {
this.leaves = leaves.map(data => this.hash(data));
this.tree = this.buildTree(this.leaves);
}
buildTree(leaves) {
if (leaves.length === 1) return leaves;
const nextLevel = [];
for (let i = 0; i < leaves.length; i += 2) {
const left = leaves[i];
const right = leaves[i + 1] || left; // 奇数情况处理
nextLevel.push(this.hash(left + right));
}
return this.buildTree(nextLevel);
}
// 生成证明路径
getProof(leafIndex) {
const proof = [];
let currentIndex = leafIndex;
let currentLevel = this.leaves;
while (currentLevel.length > 1) {
const siblingIndex = currentIndex % 2 === 0 ? currentIndex + 1 : currentIndex - 1;
if (siblingIndex < currentLevel.length) {
proof.push({
hash: currentLevel[siblingIndex],
direction: currentIndex % 2 === 0 ? 'right' : 'left'
});
}
currentIndex = Math.floor(currentIndex / 2);
currentLevel = this.getParentLevel(currentLevel);
}
return proof;
}
// 验证证明
static verifyProof(leafHash, proof, rootHash) {
let computedHash = leafHash;
for (const proofElement of proof) {
if (proofElement.direction === 'right') {
computedHash = hash(computedHash + proofElement.hash);
} else {
computedHash = hash(proofElement.hash + computedHash);
}
}
return computedHash === rootHash;
}
}
9.2 DAG(有向无环图)结构
IPFS使用Merkle DAG(有向无环图)作为核心数据结构,与简单的Merkle树相比,DAG提供了更灵活的链接机制。
DAG特性:
- 有向性:链接有方向,从父节点指向子节点
- 无环性:不存在循环路径
- 内容寻址:每个节点通过哈希值标识
- 不可变性:节点内容改变会改变其哈希值
DAG结构示例:
Website Root
/ \
Index.html CSS/
/ \
Images/ main.css
/ | \
logo.png header.jpg bg.png
DAG操作:
class MerkleDAG {
constructor() {
this.nodes = new Map();
}
// 创建节点
createNode(data, links = []) {
const node = {
data: data,
links: links.map(link => ({
name: link.name,
cid: link.cid,
size: link.size
}))
};
// 计算节点CID
const cid = this.computeCID(node);
this.nodes.set(cid.toString(), node);
return { cid, node };
}
// 获取节点
getNode(cid) {
return this.nodes.get(cid.toString());
}
// 遍历DAG
async *traverse(cid, visited = new Set()) {
if (visited.has(cid.toString())) {
return; // 避免循环(虽然DAG理论上无环)
}
visited.add(cid.toString());
const node = this.getNode(cid);
if (!node) {
throw new Error(`Node not found: ${cid}`);
}
yield { cid, node };
// 递归遍历子节点
for (const link of node.links) {
yield* this.traverse(link.cid, visited);
}
}
// 查找路径
async resolvePath(rootCid, path) {
const parts = path.split('/').filter(p => p);
let currentCid = rootCid;
for (const part of parts) {
const node = this.getNode(currentCid);
if (!node) {
throw new Error(`Node not found: ${currentCid}`);
}
const link = node.links.find(l => l.name === part);
if (!link) {
throw new Error(`Link not found: ${part}`);
}
currentCid = link.cid;
}
return currentCid;
}
}
9.3 内容验证和完整性
Merkle DAG提供了强大的内容验证机制:
完整性验证:
class ContentVerifier {
// 验证单个节点
verifyNode(node, cid) {
const computedCID = this.computeCID(node);
return computedCID.equals(cid);
}
// 验证子图完整性
async verifySubgraph(rootCid, verifier) {
for await (const { cid, node } of this.dag.traverse(rootCid)) {
if (!verifier(node, cid)) {
return {
valid: false,
error: `Invalid node: ${cid}`,
node: node
};
}
}
return { valid: true };
}
// 验证文件完整性
async verifyFile(fileCid, expectedSize = null) {
let totalSize = 0;
const chunks = [];
for await (const { cid, node } of this.dag.traverse(fileCid)) {
if (node.data) {
totalSize += node.data.length;
chunks.push(node.data);
}
}
// 验证总大小
if (expectedSize && totalSize !== expectedSize) {
return {
valid: false,
error: `Size mismatch: expected ${expectedSize}, got ${totalSize}`
};
}
// 重新组合并验证哈希
const combinedData = Buffer.concat(chunks);
const computedCID = await this.computeFileCID(combinedData);
return {
valid: computedCID.equals(fileCid),
size: totalSize,
data: combinedData
};
}
}
9.4 版本控制和分支管理
Merkle DAG天然支持版本控制:
版本历史:
class VersionControl {
constructor(dag) {
this.dag = dag;
this.versions = new Map();
}
// 创建新版本
createVersion(baseCid, changes, versionName) {
// 1. 获取基础版本
const baseNode = this.dag.getNode(baseCid);
// 2. 应用变更
const newNode = this.applyChanges(baseNode, changes);
// 3. 创建新节点
const { cid: newCid } = this.dag.createNode(newNode.data, newNode.links);
// 4. 记录版本信息
this.versions.set(versionName, {
cid: newCid,
parent: baseCid,
timestamp: Date.now(),
changes: changes
});
return newCid;
}
// 创建分支
createBranch(baseCid, branchName) {
const branch = {
name: branchName,
head: baseCid,
created: Date.now(),
versions: [baseCid]
};
this.branches.set(branchName, branch);
return branch;
}
// 合并分支
async mergeBranches(sourceBranch, targetBranch, mergeStrategy = 'recursive') {
const sourceHead = this.branches.get(sourceBranch).head;
const targetHead = this.branches.get(targetBranch).head;
// 找到共同祖先
const commonAncestor = await this.findCommonAncestor(sourceHead, targetHead);
if (!commonAncestor) {
throw new Error('No common ancestor found');
}
// 根据策略执行合并
switch (mergeStrategy) {
case 'recursive':
return this.recursiveMerge(commonAncestor, sourceHead, targetHead);
case 'octopus':
return this.octopusMerge([sourceHead, targetHead]);
default:
throw new Error(`Unknown merge strategy: ${mergeStrategy}`);
}
}
// 获取版本差异
async getDiff(cid1, cid2) {
const nodes1 = await this.collectNodes(cid1);
const nodes2 = await this.collectNodes(cid2);
const diff = {
added: [],
removed: [],
modified: []
};
// 比较两个版本的节点
for (const [cid, node] of nodes2) {
if (!nodes1.has(cid)) {
diff.added.push({ cid, node });
} else if (JSON.stringify(nodes1.get(cid)) !== JSON.stringify(node)) {
diff.modified.push({
before: nodes1.get(cid),
after: node,
cid: cid
});
}
}
for (const [cid, node] of nodes1) {
if (!nodes2.has(cid)) {
diff.removed.push({ cid, node });
}
}
return diff;
}
}
9.5 实际案例分析
让我们通过一个实际的文件系统案例来理解Merkle DAG的应用:
文件系统实现:
class IPFSFileSystem {
constructor(dag) {
this.dag = dag;
}
// 添加文件
async addFile(filePath, content) {
// 1. 创建文件节点
const fileNode = {
type: 'file',
data: content,
metadata: {
name: filePath.split('/').pop(),
size: content.length,
created: Date.now(),
modified: Date.now()
}
};
const { cid: fileCid } = this.dag.createNode(fileNode, []);
// 2. 更新目录结构
await this.updateDirectory(filePath, fileCid);
return fileCid;
}
// 创建目录
async createDirectory(dirPath) {
const dirNode = {
type: 'directory',
data: null,
metadata: {
name: dirPath.split('/').pop(),
created: Date.now(),
modified: Date.now()
}
};
const { cid: dirCid } = this.dag.createNode(dirNode, []);
// 如果是子目录,更新父目录
if (dirPath.includes('/')) {
await this.updateDirectory(dirPath, dirCid);
}
return dirCid;
}
// 读取文件
async readFile(fileCid) {
const fileNode = this.dag.getNode(fileCid);
if (!fileNode || fileNode.type !== 'file') {
throw new Error('Invalid file node');
}
return {
content: fileNode.data,
metadata: fileNode.metadata
};
}
// 列出目录内容
async listDirectory(dirCid) {
const dirNode = this.dag.getNode(dirCid);
if (!dirNode || dirNode.type !== 'directory') {
throw new Error('Invalid directory node');
}
const contents = [];
for (const link of dirNode.links) {
const childNode = this.dag.getNode(link.cid);
contents.push({
name: link.name,
type: childNode.type,
size: childNode.metadata?.size || 0,
cid: link.cid
});
}
return contents;
}
// 验证文件系统完整性
async verifyFileSystem(rootCid) {
const errors = [];
for await (const { cid, node } of this.dag.traverse(rootCid)) {
// 检查节点类型
if (!['file', 'directory'].includes(node.type)) {
errors.push({
type: 'invalid_type',
cid: cid,
message: `Invalid node type: ${node.type}`
});
}
// 检查文件大小
if (node.type === 'file' && node.metadata) {
const actualSize = node.data ? node.data.length : 0;
if (actualSize !== node.metadata.size) {
errors.push({
type: 'size_mismatch',
cid: cid,
message: `Size mismatch: expected ${node.metadata.size}, got ${actualSize}`
});
}
}
// 检查目录链接
if (node.type === 'directory') {
for (const link of node.links) {
const childNode = this.dag.getNode(link.cid);
if (!childNode) {
errors.push({
type: 'missing_child',
cid: cid,
childCid: link.cid,
message: `Missing child node: ${link.cid}`
});
}
}
}
}
return {
valid: errors.length === 0,
errors: errors,
totalNodes: (await this.dag.traverse(rootCid)).length
};
}
}
// 使用示例
async function demonstrateFileSystem() {
const dag = new MerkleDAG();
const fs = new IPFSFileSystem(dag);
// 创建文件
const file1Cid = await fs.addFile('/documents/report.txt',
Buffer.from('This is a quarterly report'));
const file2Cid = await fs.addFile('/documents/summary.txt',
Buffer.from('Executive summary'));
// 创建目录
const dirCid = await fs.createDirectory('/documents');
// 验证文件系统
const verification = await fs.verifyFileSystem(dirCid);
console.log('文件系统验证结果:', verification);
return { file1Cid, file2Cid, dirCid };
}
这个案例展示了如何使用Merkle DAG构建一个完整的文件系统,包括文件存储、目录管理、完整性验证等功能。通过Merkle DAG的特性,我们获得了数据完整性、版本控制、去重等天然优势。