remove faststreams backend for eth2_network (#3617)
unmaintained, incompatible with altair+
This commit is contained in:
parent
fec933432d
commit
c6915ed3e0
|
@ -301,13 +301,6 @@ declareHistogram nbc_resolve_time,
|
||||||
"Time(s) used while resolving peer information",
|
"Time(s) used while resolving peer information",
|
||||||
buckets = delayBuckets
|
buckets = delayBuckets
|
||||||
|
|
||||||
const
|
|
||||||
snappy_implementation {.strdefine.} = "libp2p"
|
|
||||||
|
|
||||||
const useNativeSnappy = when snappy_implementation == "native": true
|
|
||||||
elif snappy_implementation == "libp2p": false
|
|
||||||
else: {.fatal: "Please set snappy_implementation to either 'libp2p' or 'native'".}
|
|
||||||
|
|
||||||
const
|
const
|
||||||
libp2p_pki_schemes {.strdefine.} = ""
|
libp2p_pki_schemes {.strdefine.} = ""
|
||||||
|
|
||||||
|
@ -638,10 +631,198 @@ template sendUserHandlerResultAsChunkImpl*(stream: Connection,
|
||||||
handlerResult: auto): untyped =
|
handlerResult: auto): untyped =
|
||||||
writeChunk(stream, some Success, SSZ.encode(handlerResult))
|
writeChunk(stream, some Success, SSZ.encode(handlerResult))
|
||||||
|
|
||||||
when useNativeSnappy:
|
proc uncompressFramedStream*(conn: Connection,
|
||||||
include faststreams_backend
|
expectedSize: int): Future[Result[seq[byte], cstring]]
|
||||||
else:
|
{.async.} =
|
||||||
include libp2p_streams_backend
|
var header: array[framingHeader.len, byte]
|
||||||
|
try:
|
||||||
|
await conn.readExactly(addr header[0], header.len)
|
||||||
|
except LPStreamEOFError, LPStreamIncompleteError:
|
||||||
|
return err "Unexpected EOF before snappy header"
|
||||||
|
|
||||||
|
if header != framingHeader:
|
||||||
|
return err "Incorrect snappy header"
|
||||||
|
|
||||||
|
static:
|
||||||
|
doAssert maxCompressedFrameDataLen >= maxUncompressedFrameDataLen.uint64
|
||||||
|
|
||||||
|
var
|
||||||
|
frameData = newSeq[byte](maxCompressedFrameDataLen + 4)
|
||||||
|
output = newSeqUninitialized[byte](expectedSize)
|
||||||
|
written = 0
|
||||||
|
|
||||||
|
while written < expectedSize:
|
||||||
|
var frameHeader: array[4, byte]
|
||||||
|
try:
|
||||||
|
await conn.readExactly(addr frameHeader[0], frameHeader.len)
|
||||||
|
except LPStreamEOFError, LPStreamIncompleteError:
|
||||||
|
return err "Snappy frame header missing"
|
||||||
|
|
||||||
|
let (id, dataLen) = decodeFrameHeader(frameHeader)
|
||||||
|
|
||||||
|
if dataLen > frameData.len:
|
||||||
|
# In theory, compressed frames could be bigger and still result in a
|
||||||
|
# valid, small snappy frame, but this would mean they are not getting
|
||||||
|
# compressed correctly
|
||||||
|
return err "Snappy frame too big"
|
||||||
|
|
||||||
|
if dataLen > 0:
|
||||||
|
try:
|
||||||
|
await conn.readExactly(addr frameData[0], dataLen)
|
||||||
|
except LPStreamEOFError, LPStreamIncompleteError:
|
||||||
|
return err "Incomplete snappy frame"
|
||||||
|
|
||||||
|
if id == chunkCompressed:
|
||||||
|
if dataLen < 6: # At least CRC + 2 bytes of frame data
|
||||||
|
return err "Compressed snappy frame too small"
|
||||||
|
|
||||||
|
let
|
||||||
|
crc = uint32.fromBytesLE frameData.toOpenArray(0, 3)
|
||||||
|
uncompressed =
|
||||||
|
snappy.uncompress(
|
||||||
|
frameData.toOpenArray(4, dataLen - 1),
|
||||||
|
output.toOpenArray(written, output.high)).valueOr:
|
||||||
|
return err "Failed to decompress content"
|
||||||
|
|
||||||
|
if maskedCrc(
|
||||||
|
output.toOpenArray(written, written + uncompressed-1)) != crc:
|
||||||
|
return err "Snappy content CRC checksum failed"
|
||||||
|
|
||||||
|
written += uncompressed
|
||||||
|
|
||||||
|
elif id == chunkUncompressed:
|
||||||
|
if dataLen < 5: # At least one byte of data
|
||||||
|
return err "Uncompressed snappy frame too small"
|
||||||
|
|
||||||
|
let uncompressed = dataLen - 4
|
||||||
|
|
||||||
|
if uncompressed > maxUncompressedFrameDataLen.int:
|
||||||
|
return err "Snappy frame size too large"
|
||||||
|
|
||||||
|
if uncompressed > output.len - written:
|
||||||
|
return err "Too much data"
|
||||||
|
|
||||||
|
let crc = uint32.fromBytesLE frameData.toOpenArray(0, 3)
|
||||||
|
if maskedCrc(frameData.toOpenArray(4, dataLen - 1)) != crc:
|
||||||
|
return err "Snappy content CRC checksum failed"
|
||||||
|
|
||||||
|
output[written..<written + uncompressed] =
|
||||||
|
frameData.toOpenArray(4, dataLen-1)
|
||||||
|
written += uncompressed
|
||||||
|
|
||||||
|
elif id < 0x80:
|
||||||
|
# Reserved unskippable chunks (chunk types 0x02-0x7f)
|
||||||
|
# if we encounter this type of chunk, stop decoding
|
||||||
|
# the spec says it is an error
|
||||||
|
return err "Invalid snappy chunk type"
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Reserved skippable chunks (chunk types 0x80-0xfe)
|
||||||
|
# including STREAM_HEADER (0xff) should be skipped
|
||||||
|
continue
|
||||||
|
|
||||||
|
return ok output
|
||||||
|
|
||||||
|
proc readChunkPayload*(conn: Connection, peer: Peer,
|
||||||
|
maxChunkSize: uint32,
|
||||||
|
MsgType: type): Future[NetRes[MsgType]] {.async.} =
|
||||||
|
let sm = now(chronos.Moment)
|
||||||
|
let size =
|
||||||
|
try: await conn.readVarint()
|
||||||
|
except LPStreamEOFError: #, LPStreamIncompleteError, InvalidVarintError
|
||||||
|
# TODO compiler error - haha, uncaught exception
|
||||||
|
# Error: unhandled exception: closureiters.nim(322, 17) `c[i].kind == nkType` [AssertionError]
|
||||||
|
return neterr UnexpectedEOF
|
||||||
|
except LPStreamIncompleteError:
|
||||||
|
return neterr UnexpectedEOF
|
||||||
|
except InvalidVarintError:
|
||||||
|
return neterr UnexpectedEOF
|
||||||
|
|
||||||
|
if size > maxChunkSize:
|
||||||
|
return neterr SizePrefixOverflow
|
||||||
|
if size == 0:
|
||||||
|
return neterr ZeroSizePrefix
|
||||||
|
|
||||||
|
# The `size.int` conversion is safe because `size` is bounded to `MAX_CHUNK_SIZE`
|
||||||
|
let data = await conn.uncompressFramedStream(size.int)
|
||||||
|
if data.isOk:
|
||||||
|
# `10` is the maximum size of variable integer on wire, so error could
|
||||||
|
# not be significant.
|
||||||
|
peer.updateNetThroughput(now(chronos.Moment) - sm,
|
||||||
|
uint64(10 + size))
|
||||||
|
return ok SSZ.decode(data.get(), MsgType)
|
||||||
|
else:
|
||||||
|
debug "Snappy decompression/read failed", msg = $data.error, conn
|
||||||
|
return neterr InvalidSnappyBytes
|
||||||
|
|
||||||
|
proc readResponseChunk(conn: Connection, peer: Peer, maxChunkSize: uint32,
|
||||||
|
MsgType: typedesc): Future[NetRes[MsgType]] {.async.} =
|
||||||
|
mixin readChunkPayload
|
||||||
|
|
||||||
|
try:
|
||||||
|
var responseCodeByte: byte
|
||||||
|
try:
|
||||||
|
await conn.readExactly(addr responseCodeByte, 1)
|
||||||
|
except LPStreamEOFError, LPStreamIncompleteError:
|
||||||
|
return neterr PotentiallyExpectedEOF
|
||||||
|
|
||||||
|
static: assert ResponseCode.low.ord == 0
|
||||||
|
if responseCodeByte > ResponseCode.high.byte:
|
||||||
|
return neterr InvalidResponseCode
|
||||||
|
|
||||||
|
let responseCode = ResponseCode responseCodeByte
|
||||||
|
case responseCode:
|
||||||
|
of InvalidRequest, ServerError, ResourceUnavailable:
|
||||||
|
let
|
||||||
|
errorMsgChunk = await readChunkPayload(
|
||||||
|
conn, peer, maxChunkSize, ErrorMsg)
|
||||||
|
errorMsg = if errorMsgChunk.isOk: errorMsgChunk.value
|
||||||
|
else: return err(errorMsgChunk.error)
|
||||||
|
errorMsgStr = toPrettyString(errorMsg.asSeq)
|
||||||
|
debug "Error response from peer", responseCode, errMsg = errorMsgStr
|
||||||
|
return err Eth2NetworkingError(kind: ReceivedErrorResponse,
|
||||||
|
responseCode: responseCode,
|
||||||
|
errorMsg: errorMsgStr)
|
||||||
|
of Success:
|
||||||
|
discard
|
||||||
|
|
||||||
|
return await readChunkPayload(conn, peer, maxChunkSize, MsgType)
|
||||||
|
|
||||||
|
except LPStreamEOFError, LPStreamIncompleteError:
|
||||||
|
return neterr UnexpectedEOF
|
||||||
|
|
||||||
|
proc readResponse(conn: Connection, peer: Peer, maxChunkSize: uint32,
|
||||||
|
MsgType: type, timeout: Duration): Future[NetRes[MsgType]] {.async.} =
|
||||||
|
when MsgType is seq:
|
||||||
|
type E = ElemType(MsgType)
|
||||||
|
var results: MsgType
|
||||||
|
while true:
|
||||||
|
# Because we interleave networking with response processing, it may
|
||||||
|
# happen that reading all chunks takes longer than a strict dealine
|
||||||
|
# timeout would allow, so we allow each chunk a new timeout instead.
|
||||||
|
# The problem is exacerbated by the large number of round-trips to the
|
||||||
|
# poll loop that each future along the way causes.
|
||||||
|
trace "reading chunk", conn
|
||||||
|
let nextFut = conn.readResponseChunk(peer, maxChunkSize, E)
|
||||||
|
if not await nextFut.withTimeout(timeout):
|
||||||
|
return neterr(ReadResponseTimeout)
|
||||||
|
let nextRes = nextFut.read()
|
||||||
|
if nextRes.isErr:
|
||||||
|
if nextRes.error.kind == PotentiallyExpectedEOF:
|
||||||
|
trace "EOF chunk", conn, err = nextRes.error
|
||||||
|
|
||||||
|
return ok results
|
||||||
|
trace "Error chunk", conn, err = nextRes.error
|
||||||
|
|
||||||
|
return err nextRes.error
|
||||||
|
else:
|
||||||
|
trace "Got chunk", conn
|
||||||
|
results.add nextRes.value
|
||||||
|
else:
|
||||||
|
let nextFut = conn.readResponseChunk(peer, maxChunkSize, MsgType)
|
||||||
|
if not await nextFut.withTimeout(timeout):
|
||||||
|
return neterr(ReadResponseTimeout)
|
||||||
|
return nextFut.read()
|
||||||
|
|
||||||
func maxChunkSize*(t: typedesc[bellatrix.SignedBeaconBlock]): uint32 =
|
func maxChunkSize*(t: typedesc[bellatrix.SignedBeaconBlock]): uint32 =
|
||||||
MAX_CHUNK_SIZE_BELLATRIX
|
MAX_CHUNK_SIZE_BELLATRIX
|
||||||
|
@ -668,9 +849,8 @@ proc makeEth2Request(peer: Peer, protocolId: string, requestBytes: Bytes,
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
|
||||||
# Read the response
|
# Read the response
|
||||||
return
|
return await readResponse(
|
||||||
await readResponse(when useNativeSnappy: libp2pInput(stream) else: stream,
|
stream, peer, maxChunkSize(ResponseMsg), ResponseMsg, timeout)
|
||||||
peer, maxChunkSize(ResponseMsg), ResponseMsg, timeout)
|
|
||||||
finally:
|
finally:
|
||||||
await stream.closeWithEOF()
|
await stream.closeWithEOF()
|
||||||
|
|
||||||
|
@ -795,17 +975,7 @@ proc handleIncomingStream(network: Eth2Node,
|
||||||
template returnResourceUnavailable(msg: string) =
|
template returnResourceUnavailable(msg: string) =
|
||||||
returnResourceUnavailable(ErrorMsg msg.toBytes)
|
returnResourceUnavailable(ErrorMsg msg.toBytes)
|
||||||
|
|
||||||
let s = when useNativeSnappy:
|
|
||||||
let fs = libp2pInput(conn)
|
|
||||||
|
|
||||||
if fs.timeoutToNextByte(TTFB_TIMEOUT):
|
|
||||||
returnInvalidRequest(errorMsgLit "Request first byte not sent in time")
|
|
||||||
|
|
||||||
fs
|
|
||||||
else:
|
|
||||||
# TODO(zah) The TTFB timeout is not implemented in LibP2P streams back-end
|
# TODO(zah) The TTFB timeout is not implemented in LibP2P streams back-end
|
||||||
conn
|
|
||||||
|
|
||||||
let deadline = sleepAsync RESP_TIMEOUT
|
let deadline = sleepAsync RESP_TIMEOUT
|
||||||
|
|
||||||
const isEmptyMsg = when MsgRec is object:
|
const isEmptyMsg = when MsgRec is object:
|
||||||
|
@ -823,7 +993,7 @@ proc handleIncomingStream(network: Eth2Node,
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
awaitWithTimeout(
|
awaitWithTimeout(
|
||||||
readChunkPayload(s, peer, maxChunkSize(MsgRec), MsgRec), deadline):
|
readChunkPayload(conn, peer, maxChunkSize(MsgRec), MsgRec), deadline):
|
||||||
returnInvalidRequest(
|
returnInvalidRequest(
|
||||||
errorMsgLit "Request full data not sent in time")
|
errorMsgLit "Request full data not sent in time")
|
||||||
|
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2022 Status Research & Development GmbH
|
|
||||||
# Licensed and distributed under either of
|
|
||||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
||||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
||||||
|
|
||||||
type
|
|
||||||
LibP2PInputStream = ref object of InputStream
|
|
||||||
conn: Connection
|
|
||||||
|
|
||||||
const
|
|
||||||
closingErrMsg = "Failed to close LibP2P stream"
|
|
||||||
readingErrMsg = "Failed to read from LibP2P stream"
|
|
||||||
|
|
||||||
proc libp2pReadOnce(s: LibP2PInputStream,
|
|
||||||
dst: pointer, dstLen: Natural): Future[Natural] {.async.} =
|
|
||||||
fsTranslateErrors readingErrMsg:
|
|
||||||
try:
|
|
||||||
return implementSingleRead(s.buffers, dst, dstLen, ReadFlags {},
|
|
||||||
readStartAddr, readLen):
|
|
||||||
await s.conn.readOnce(readStartAddr, readLen)
|
|
||||||
except LPStreamEOFError:
|
|
||||||
s.buffers.eofReached = true
|
|
||||||
|
|
||||||
proc libp2pCloseWait(s: LibP2PInputStream) {.async.} =
|
|
||||||
fsTranslateErrors closingErrMsg:
|
|
||||||
await safeClose(s.conn)
|
|
||||||
|
|
||||||
# TODO: Use the Raising type here
|
|
||||||
let libp2pInputVTable = InputStreamVTable(
|
|
||||||
readSync: proc (s: InputStream, dst: pointer, dstLen: Natural): Natural
|
|
||||||
{.nimcall, gcsafe, raises: [IOError, Defect].} =
|
|
||||||
doAssert(false, "synchronous reading is not allowed")
|
|
||||||
,
|
|
||||||
readAsync: proc (s: InputStream, dst: pointer, dstLen: Natural): Future[Natural]
|
|
||||||
{.nimcall, gcsafe, raises: [IOError, Defect].} =
|
|
||||||
fsTranslateErrors "Unexpected exception from merely forwarding a future":
|
|
||||||
return libp2pReadOnce(Libp2pInputStream s, dst, dstLen)
|
|
||||||
,
|
|
||||||
closeSync: proc (s: InputStream)
|
|
||||||
{.nimcall, gcsafe, raises: [IOError, Defect].} =
|
|
||||||
fsTranslateErrors closingErrMsg:
|
|
||||||
s.closeFut = Libp2pInputStream(s).conn.close()
|
|
||||||
,
|
|
||||||
closeAsync: proc (s: InputStream): Future[void]
|
|
||||||
{.nimcall, gcsafe, raises: [IOError, Defect].} =
|
|
||||||
fsTranslateErrors "Unexpected exception from merely forwarding a future":
|
|
||||||
return libp2pCloseWait(Libp2pInputStream s)
|
|
||||||
)
|
|
||||||
|
|
||||||
func libp2pInput*(conn: Connection,
|
|
||||||
pageSize = defaultPageSize): AsyncInputStream =
|
|
||||||
AsyncInputStream LibP2PInputStream(
|
|
||||||
vtable: vtableAddr libp2pInputVTable,
|
|
||||||
buffers: initPageBuffers(pageSize),
|
|
||||||
conn: conn)
|
|
||||||
|
|
||||||
proc readSizePrefix(s: AsyncInputStream,
|
|
||||||
maxSize: uint32): Future[NetRes[uint32]] {.async.} =
|
|
||||||
var parser: VarintParser[uint32, ProtoBuf]
|
|
||||||
while s.readable:
|
|
||||||
case parser.feedByte(s.read)
|
|
||||||
of Done:
|
|
||||||
let res = parser.getResult
|
|
||||||
if res > maxSize:
|
|
||||||
return neterr SizePrefixOverflow
|
|
||||||
else:
|
|
||||||
return ok res
|
|
||||||
of Overflow:
|
|
||||||
return neterr SizePrefixOverflow
|
|
||||||
of Incomplete:
|
|
||||||
continue
|
|
||||||
|
|
||||||
return neterr UnexpectedEOF
|
|
||||||
|
|
||||||
proc readSszValue(s: AsyncInputStream,
|
|
||||||
size: int,
|
|
||||||
MsgType: type): Future[NetRes[MsgType]] {.async.} =
|
|
||||||
if s.readable(size):
|
|
||||||
s.withReadableRange(size, r):
|
|
||||||
return r.readValue(SSZ, MsgType)
|
|
||||||
else:
|
|
||||||
return neterr UnexpectedEOF
|
|
||||||
|
|
||||||
proc readChunkPayload(s: AsyncInputStream,
|
|
||||||
noSnappy: bool,
|
|
||||||
MsgType: type): Future[NetRes[MsgType]] {.async.} =
|
|
||||||
# TODO this needs to sometimes be MAX_CHUNK_SIZE_BELLATRIX, for at least
|
|
||||||
# bellatrix.SignedBeaconBlock
|
|
||||||
let prefix = await readSizePrefix(s, MAX_CHUNK_SIZE)
|
|
||||||
let size = if prefix.isOk: prefix.value.int
|
|
||||||
else: return err(prefix.error)
|
|
||||||
|
|
||||||
if size > 0:
|
|
||||||
let processingFut = if noSnappy:
|
|
||||||
readSszValue(s, size, MsgType)
|
|
||||||
else:
|
|
||||||
executePipeline(uncompressFramedStream,
|
|
||||||
readSszValue(size, MsgType))
|
|
||||||
|
|
||||||
return await processingFut
|
|
||||||
else:
|
|
||||||
return neterr ZeroSizePrefix
|
|
||||||
|
|
||||||
proc readResponseChunk(s: AsyncInputStream,
|
|
||||||
noSnappy: bool,
|
|
||||||
MsgType: typedesc): Future[NetRes[MsgType]] {.async.} =
|
|
||||||
let responseCodeByte = s.read
|
|
||||||
|
|
||||||
static: assert ResponseCode.low.ord == 0
|
|
||||||
if responseCodeByte > ResponseCode.high.byte:
|
|
||||||
return neterr InvalidResponseCode
|
|
||||||
|
|
||||||
let responseCode = ResponseCode responseCodeByte
|
|
||||||
case responseCode:
|
|
||||||
of InvalidRequest, ServerError, ResourceUnavailable:
|
|
||||||
let errorMsgChunk = await readChunkPayload(s, noSnappy, string)
|
|
||||||
let errorMsg = if errorMsgChunk.isOk: errorMsgChunk.value
|
|
||||||
else: return err(errorMsgChunk.error)
|
|
||||||
return err Eth2NetworkingError(kind: ReceivedErrorResponse,
|
|
||||||
responseCode: responseCode,
|
|
||||||
errorMsg: toPrettyString(errorMsg.asSeq()))
|
|
||||||
of Success:
|
|
||||||
discard
|
|
||||||
|
|
||||||
return await readChunkPayload(s, noSnappy, MsgType)
|
|
||||||
|
|
||||||
proc readResponse(s: AsyncInputStream,
|
|
||||||
noSnappy: bool,
|
|
||||||
MsgType: type): Future[NetRes[MsgType]] {.gcsafe, async.} =
|
|
||||||
when MsgType is seq:
|
|
||||||
type E = ElemType(MsgType)
|
|
||||||
var results: MsgType
|
|
||||||
while s.readable:
|
|
||||||
results.add(? await s.readResponseChunk(noSnappy, E))
|
|
||||||
return ok results
|
|
||||||
else:
|
|
||||||
if s.readable:
|
|
||||||
return await s.readResponseChunk(noSnappy, MsgType)
|
|
||||||
else:
|
|
||||||
return neterr UnexpectedEOF
|
|
|
@ -1,200 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2022 Status Research & Development GmbH
|
|
||||||
# Licensed and distributed under either of
|
|
||||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
||||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
||||||
|
|
||||||
# TODO: How can this be tested?
|
|
||||||
proc uncompressFramedStream*(conn: Connection,
|
|
||||||
expectedSize: int): Future[Result[seq[byte], cstring]]
|
|
||||||
{.async.} =
|
|
||||||
var header: array[framingHeader.len, byte]
|
|
||||||
try:
|
|
||||||
await conn.readExactly(addr header[0], header.len)
|
|
||||||
except LPStreamEOFError, LPStreamIncompleteError:
|
|
||||||
return err "Unexpected EOF before snappy header"
|
|
||||||
|
|
||||||
if header != framingHeader:
|
|
||||||
return err "Incorrect snappy header"
|
|
||||||
|
|
||||||
static:
|
|
||||||
doAssert maxCompressedFrameDataLen >= maxUncompressedFrameDataLen.uint64
|
|
||||||
|
|
||||||
var
|
|
||||||
frameData = newSeq[byte](maxCompressedFrameDataLen + 4)
|
|
||||||
output = newSeqUninitialized[byte](expectedSize)
|
|
||||||
written = 0
|
|
||||||
|
|
||||||
while written < expectedSize:
|
|
||||||
var frameHeader: array[4, byte]
|
|
||||||
try:
|
|
||||||
await conn.readExactly(addr frameHeader[0], frameHeader.len)
|
|
||||||
except LPStreamEOFError, LPStreamIncompleteError:
|
|
||||||
return err "Snappy frame header missing"
|
|
||||||
|
|
||||||
let (id, dataLen) = decodeFrameHeader(frameHeader)
|
|
||||||
|
|
||||||
if dataLen > frameData.len:
|
|
||||||
# In theory, compressed frames could be bigger and still result in a
|
|
||||||
# valid, small snappy frame, but this would mean they are not getting
|
|
||||||
# compressed correctly
|
|
||||||
return err "Snappy frame too big"
|
|
||||||
|
|
||||||
if dataLen > 0:
|
|
||||||
try:
|
|
||||||
await conn.readExactly(addr frameData[0], dataLen)
|
|
||||||
except LPStreamEOFError, LPStreamIncompleteError:
|
|
||||||
return err "Incomplete snappy frame"
|
|
||||||
|
|
||||||
if id == chunkCompressed:
|
|
||||||
if dataLen < 6: # At least CRC + 2 bytes of frame data
|
|
||||||
return err "Compressed snappy frame too small"
|
|
||||||
|
|
||||||
let
|
|
||||||
crc = uint32.fromBytesLE frameData.toOpenArray(0, 3)
|
|
||||||
uncompressed =
|
|
||||||
snappy.uncompress(
|
|
||||||
frameData.toOpenArray(4, dataLen - 1),
|
|
||||||
output.toOpenArray(written, output.high)).valueOr:
|
|
||||||
return err "Failed to decompress content"
|
|
||||||
|
|
||||||
if maskedCrc(
|
|
||||||
output.toOpenArray(written, written + uncompressed-1)) != crc:
|
|
||||||
return err "Snappy content CRC checksum failed"
|
|
||||||
|
|
||||||
written += uncompressed
|
|
||||||
|
|
||||||
elif id == chunkUncompressed:
|
|
||||||
if dataLen < 5: # At least one byte of data
|
|
||||||
return err "Uncompressed snappy frame too small"
|
|
||||||
|
|
||||||
let uncompressed = dataLen - 4
|
|
||||||
|
|
||||||
if uncompressed > maxUncompressedFrameDataLen.int:
|
|
||||||
return err "Snappy frame size too large"
|
|
||||||
|
|
||||||
if uncompressed > output.len - written:
|
|
||||||
return err "Too much data"
|
|
||||||
|
|
||||||
let crc = uint32.fromBytesLE frameData.toOpenArray(0, 3)
|
|
||||||
if maskedCrc(frameData.toOpenArray(4, dataLen - 1)) != crc:
|
|
||||||
return err "Snappy content CRC checksum failed"
|
|
||||||
|
|
||||||
output[written..<written + uncompressed] =
|
|
||||||
frameData.toOpenArray(4, dataLen-1)
|
|
||||||
written += uncompressed
|
|
||||||
|
|
||||||
elif id < 0x80:
|
|
||||||
# Reserved unskippable chunks (chunk types 0x02-0x7f)
|
|
||||||
# if we encounter this type of chunk, stop decoding
|
|
||||||
# the spec says it is an error
|
|
||||||
return err "Invalid snappy chunk type"
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Reserved skippable chunks (chunk types 0x80-0xfe)
|
|
||||||
# including STREAM_HEADER (0xff) should be skipped
|
|
||||||
continue
|
|
||||||
|
|
||||||
return ok output
|
|
||||||
|
|
||||||
proc readChunkPayload*(conn: Connection, peer: Peer,
|
|
||||||
maxChunkSize: uint32,
|
|
||||||
MsgType: type): Future[NetRes[MsgType]] {.async.} =
|
|
||||||
let sm = now(chronos.Moment)
|
|
||||||
let size =
|
|
||||||
try: await conn.readVarint()
|
|
||||||
except LPStreamEOFError: #, LPStreamIncompleteError, InvalidVarintError
|
|
||||||
# TODO compiler error - haha, uncaught exception
|
|
||||||
# Error: unhandled exception: closureiters.nim(322, 17) `c[i].kind == nkType` [AssertionError]
|
|
||||||
return neterr UnexpectedEOF
|
|
||||||
except LPStreamIncompleteError:
|
|
||||||
return neterr UnexpectedEOF
|
|
||||||
except InvalidVarintError:
|
|
||||||
return neterr UnexpectedEOF
|
|
||||||
|
|
||||||
if size > maxChunkSize:
|
|
||||||
return neterr SizePrefixOverflow
|
|
||||||
if size == 0:
|
|
||||||
return neterr ZeroSizePrefix
|
|
||||||
|
|
||||||
# The `size.int` conversion is safe because `size` is bounded to `MAX_CHUNK_SIZE`
|
|
||||||
let data = await conn.uncompressFramedStream(size.int)
|
|
||||||
if data.isOk:
|
|
||||||
# `10` is the maximum size of variable integer on wire, so error could
|
|
||||||
# not be significant.
|
|
||||||
peer.updateNetThroughput(now(chronos.Moment) - sm,
|
|
||||||
uint64(10 + size))
|
|
||||||
return ok SSZ.decode(data.get(), MsgType)
|
|
||||||
else:
|
|
||||||
debug "Snappy decompression/read failed", msg = $data.error, conn
|
|
||||||
return neterr InvalidSnappyBytes
|
|
||||||
|
|
||||||
proc readResponseChunk(conn: Connection, peer: Peer, maxChunkSize: uint32,
|
|
||||||
MsgType: typedesc): Future[NetRes[MsgType]] {.async.} =
|
|
||||||
mixin readChunkPayload
|
|
||||||
|
|
||||||
try:
|
|
||||||
var responseCodeByte: byte
|
|
||||||
try:
|
|
||||||
await conn.readExactly(addr responseCodeByte, 1)
|
|
||||||
except LPStreamEOFError, LPStreamIncompleteError:
|
|
||||||
return neterr PotentiallyExpectedEOF
|
|
||||||
|
|
||||||
static: assert ResponseCode.low.ord == 0
|
|
||||||
if responseCodeByte > ResponseCode.high.byte:
|
|
||||||
return neterr InvalidResponseCode
|
|
||||||
|
|
||||||
let responseCode = ResponseCode responseCodeByte
|
|
||||||
case responseCode:
|
|
||||||
of InvalidRequest, ServerError, ResourceUnavailable:
|
|
||||||
let
|
|
||||||
errorMsgChunk = await readChunkPayload(
|
|
||||||
conn, peer, maxChunkSize, ErrorMsg)
|
|
||||||
errorMsg = if errorMsgChunk.isOk: errorMsgChunk.value
|
|
||||||
else: return err(errorMsgChunk.error)
|
|
||||||
errorMsgStr = toPrettyString(errorMsg.asSeq)
|
|
||||||
debug "Error response from peer", responseCode, errMsg = errorMsgStr
|
|
||||||
return err Eth2NetworkingError(kind: ReceivedErrorResponse,
|
|
||||||
responseCode: responseCode,
|
|
||||||
errorMsg: errorMsgStr)
|
|
||||||
of Success:
|
|
||||||
discard
|
|
||||||
|
|
||||||
return await readChunkPayload(conn, peer, maxChunkSize, MsgType)
|
|
||||||
|
|
||||||
except LPStreamEOFError, LPStreamIncompleteError:
|
|
||||||
return neterr UnexpectedEOF
|
|
||||||
|
|
||||||
proc readResponse(conn: Connection, peer: Peer, maxChunkSize: uint32,
|
|
||||||
MsgType: type, timeout: Duration): Future[NetRes[MsgType]] {.async.} =
|
|
||||||
when MsgType is seq:
|
|
||||||
type E = ElemType(MsgType)
|
|
||||||
var results: MsgType
|
|
||||||
while true:
|
|
||||||
# Because we interleave networking with response processing, it may
|
|
||||||
# happen that reading all chunks takes longer than a strict dealine
|
|
||||||
# timeout would allow, so we allow each chunk a new timeout instead.
|
|
||||||
# The problem is exacerbated by the large number of round-trips to the
|
|
||||||
# poll loop that each future along the way causes.
|
|
||||||
trace "reading chunk", conn
|
|
||||||
let nextFut = conn.readResponseChunk(peer, maxChunkSize, E)
|
|
||||||
if not await nextFut.withTimeout(timeout):
|
|
||||||
return neterr(ReadResponseTimeout)
|
|
||||||
let nextRes = nextFut.read()
|
|
||||||
if nextRes.isErr:
|
|
||||||
if nextRes.error.kind == PotentiallyExpectedEOF:
|
|
||||||
trace "EOF chunk", conn, err = nextRes.error
|
|
||||||
|
|
||||||
return ok results
|
|
||||||
trace "Error chunk", conn, err = nextRes.error
|
|
||||||
|
|
||||||
return err nextRes.error
|
|
||||||
else:
|
|
||||||
trace "Got chunk", conn
|
|
||||||
results.add nextRes.value
|
|
||||||
else:
|
|
||||||
let nextFut = conn.readResponseChunk(peer, maxChunkSize, MsgType)
|
|
||||||
if not await nextFut.withTimeout(timeout):
|
|
||||||
return neterr(ReadResponseTimeout)
|
|
||||||
return nextFut.read()
|
|
|
@ -118,8 +118,6 @@ switch("passL", "-fno-omit-frame-pointer")
|
||||||
# for heap-usage-by-instance-type metrics and object base-type strings
|
# for heap-usage-by-instance-type metrics and object base-type strings
|
||||||
--define:nimTypeNames
|
--define:nimTypeNames
|
||||||
|
|
||||||
# switch("define", "snappy_implementation=libp2p")
|
|
||||||
|
|
||||||
# TODO https://github.com/status-im/nimbus-eth2/issues/3130
|
# TODO https://github.com/status-im/nimbus-eth2/issues/3130
|
||||||
# We are still seeing problems with the websock package, se we stick to using news:
|
# We are still seeing problems with the websock package, se we stick to using news:
|
||||||
switch("define", "json_rpc_websocket_package=news")
|
switch("define", "json_rpc_websocket_package=news")
|
||||||
|
|
Loading…
Reference in New Issue