Alert Source Discuss
Standards Track: Networking

EIP-706: DEVp2p snappy 压缩

Authors Péter Szilágyi <peter@ethereum.org>
Created 2017-09-07

摘要

以太坊使用的基础网络协议 (DEVp2p) 目前没有采用任何形式的压缩。这导致整个网络中浪费了大量的带宽,使得初始同步以及正常运行都变得更慢,更迟缓。

本 EIP 提出对 DEVp2p 协议进行一个小的扩展,以在初始握手后对所有消息负载启用 Snappy 压缩。经过大量的基准测试,结果表明,对于初始同步,数据流量减少了 60-80%。您可以在下面找到确切的数字。

动机

目前在 Geth 中使用快速同步同步以太坊主网络(区块 4,248,000)会消耗 1.01GB 的上传带宽和 33.59GB 的下载带宽。在 Rinkeby 测试网络(区块 852,000)上,则是 55.89MB 的上传带宽和 2.51GB 的下载带宽。

然而,这些数据中的大部分(区块、交易)都是高度可压缩的。通过在消息负载级别启用压缩,我们可以将之前的数字减少到主网络上的 1.01GB 上传 / 13.46GB 下载,以及测试网络上的 46.21MB 上传 / 463.65MB 下载。

在 DEVp2p 级别(例如,相对于 eth)执行此操作的动机是,它可以无缝地为所有子协议(eth、les、bzz)启用压缩,从而减少这些协议在尝试单独优化数据流量时可能产生的任何复杂性。

规范

将公开的 DEVp2p 版本号从 4 提升到 5。如果在握手期间,远程方仅公开支持版本 4,则运行与现在完全相同的协议。

如果远程方公开的 DEVp2p 版本 >= 5,则在发送期间加密 DEVp2p 消息之前,注入一个 Snappy 压缩步骤:

  • 一条消息由 {Code, Size, Payload} 组成
  • 使用 Snappy 压缩原始负载,并将其存储在同一字段中。
  • 将消息大小更新为压缩后负载的长度。
  • 像以前一样加密和发送消息,忽略压缩。

与消息发送类似,当从远程节点接收到 DEVp2p v5 消息时,在解密 DEVp2p 消息之后,立即插入一个 Snappy 解压缩步骤:

  • 一条消息由 {Code, Size, Payload} 组成
  • 像以前一样解密消息负载,忽略压缩。
  • 使用 Snappy 解压缩负载,并将其存储在同一字段中。
  • 将消息大小更新为解压缩后负载的长度。

重要的注意事项:

  • 握手消息永远不会被压缩,因为它需要协商公共版本。
  • Snappy 框架使用,因为 DEVp2p 协议已经是面向消息的。

注意:Snappy 也支持未压缩的二进制字面量(高达 4GB),为已经压缩或加密的数据(Snappy 通常会自动检测到这种情况)的微调未来优化留有空间,这些数据不会获得压缩收益。

避免 DOS 攻击

目前,DEVp2p 消息长度限制为 24 位,最大大小为 16MB。随着 Snappy 压缩的引入,必须小心不要盲目地解压缩消息,因为它们可能会比 16MB 大得多。

然而,Snappy 能够计算输入消息的解压缩大小,而无需在内存中膨胀它(流以未压缩的长度开始,最大为 2^32 - 1,存储为小端 varint)。这可以用于丢弃任何解压缩后超过某个阈值的消息。该提案是使用与当前 DEVp2p 协议相同的限制(16MB)作为解压缩消息的阈值。 这保留了当前 DEVp2p 协议所做的相同保证,因此应用程序级别的协议不会有任何意外。

替代方案(已丢弃)

已经提出并丢弃的数据压缩的替代解决方案是:

扩展协议 xyz 以支持压缩消息,而不是在 DEVp2p 级别执行:

  • 优点:可以更好地优化何时压缩以及何时不压缩。
  • 缺点:将传输层编码混合到应用层逻辑中。
  • 缺点:使单个消息规范因压缩细节而更加复杂。
  • 缺点:需要在每个协议上进行跨客户端协调,从而使工作变得更加困难和重复(eth、les、shh、bzz)。

引入协议的无缝变体,例如用 xyz-compressed 扩展的 xyz

  • 优点:可以在没有跨客户端协调的情况下完成(hack)。
  • 缺点:网络上充斥着客户端特定的协议声明。
  • 缺点:无论如何,都需要在 EIP 中进行规范以实现跨互操作性。

已经讨论并丢弃的其他想法:

不要显式限制解压缩消息的大小,仅限制压缩消息的大小:

  • 优点:允许更大的消息通过 DEVp2p 传输。
  • 缺点:上层协议需要检查并丢弃大型消息。
  • 缺点:需要延迟解压缩以允许在没有 DOS 的情况下限制大小。

向后兼容性

该提案完全向后兼容。升级到提议的 DEVp2p 协议版本 5 的客户端仍然应该支持跳过仅声明 DEVp2p 协议版本 4 的连接的压缩步骤。

实现

您可以在 https://github.com/ethereum/go-ethereum/pull/15106 中找到此 EIP 的参考实现。

测试向量

任何给定输入都有多个有效的编码,并且在吞吐量和输出大小之间进行权衡时,Snappy 中有多个良好的内部压缩算法。因此,不同的实现可能会产生压缩形式的细微变化,但所有实现都应该彼此交叉兼容。

例如,以 Rinkeby 测试网络中区块 #272621 的十六进制编码 RLP 为例:block.rlp (~3MB)

您可以使用以下代码段验证编码的二进制文件是否可以解码为正确的纯文本:

Go

$ go get https://github.com/golang/snappy
package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"github.com/golang/snappy"
)

func main() {
	// Read and decode the decompressed file
	// 读取并解码解压缩的文件
	plainhex, err := ioutil.ReadFile(os.Args[1])
	if err != nil {
		log.Fatalf("Failed to read decompressed file %s: %v", os.Args[1], err)
	}
	plain, err := hex.DecodeString(string(plainhex))
	if err != nil {
		log.Fatalf("Failed to decode decompressed file: %v", err)
	}
	// Read and decode the compressed file
	// 读取并解码压缩的文件
	comphex, err := ioutil.ReadFile(os.Args[2])
	if err != nil {
		log.Fatalf("Failed to read compressed file %s: %v", os.Args[2], err)
	}
	comp, err := hex.DecodeString(string(comphex))
	if err != nil {
		log.Fatalf("Failed to decode compressed file: %v", err)
	}
	// Make sure they match
	// 确保它们匹配
	decomp, err := snappy.Decode(nil, comp)
	if err != nil {
		log.Fatalf("Failed to decompress compressed file: %v", err)
	}
	if !bytes.Equal(plain, decomp) {
		fmt.Println("Booo, decompressed file does not match provided plain text!")
		return
	}
	fmt.Println("Yay, decompressed data matched provided plain text!")
}
$ go run main.go block.rlp block.go.snappy
Yay, decompressed data matched provided plain text!

$ go run main.go block.rlp block.py.snappy
Yay, decompressed data matched provided plain text!

Python

$ pip install python-snappy
import snappy
import sys

# Read and decode the decompressed file
# 读取并解码解压缩的文件
with open(sys.argv[1], 'rb') as file:
    plainhex = file.read()

plain = plainhex.decode("hex")

# Read and decode the compressed file
# 读取并解码压缩的文件
with open(sys.argv[2], 'rb') as file:
    comphex = file.read()

comp = comphex.decode("hex")

# Make sure they match
# 确保它们匹配
decomp = snappy.uncompress(comp)
if plain != decomp:
    print "Booo, decompressed file does not match provided plain text!"
else:
    print "Yay, decompressed data matched provided plain text!"
$ python main.py block.rlp block.go.snappy
Yay, decompressed data matched provided plain text!

$ python main.py block.rlp block.py.snappy
Yay, decompressed data matched provided plain text!

参考

  • Snappy 网站:https://google.github.io/snappy/
  • Snappy 规范:https://github.com/google/snappy/blob/master/format_description.txt

版权

CC0 下放弃版权及相关权利。

Citation

Please cite this document as:

Péter Szilágyi <peter@ethereum.org>, "EIP-706: DEVp2p snappy 压缩," Ethereum Improvement Proposals, no. 706, September 2017. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-706.