Skip to content

Commit 025864d

Browse files
authored
New protocol version (#883)
* Fix scan block version parsing #882 * Use varint and add onion address type #882 * Add tests * Finish tests for each message type * Fix size of filter event message * Fix other parts of project * Fix header parsing on client and server * Update watsnew for 24 release
1 parent 40205e3 commit 025864d

File tree

21 files changed

+587
-281
lines changed

21 files changed

+587
-281
lines changed

android-version.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# CI tracks the file and publish testing versions if the code is changed.
22
{
3-
code = "23";
3+
code = "24";
44
name = "Alpha";
55
}

index-protocol/ergvein-index-protocol.cabal

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,18 @@ test-suite ergvein-index-protocol-test
7575
base
7676
, attoparsec
7777
, attoparsec-binary
78+
, base16-bytestring
7879
, bytestring
7980
, containers >= 0.6 && < 0.7
8081
, ergvein-index-protocol
8182
, ergvein-wallet-types
83+
, QuickCheck
84+
, quickcheck-instances
8285
, tasty
8386
, tasty-discover
8487
, tasty-hspec
8588
, tasty-hunit
8689
, tasty-quickcheck
87-
, QuickCheck
88-
, quickcheck-instances
8990
, vector
9091
default-extensions:
9192
OverloadedStrings

index-protocol/src/Ergvein/Index/Protocol/Deserialization.hs

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
{-# LANGUAGE MultiWayIf #-}
12
module Ergvein.Index.Protocol.Deserialization where
23

34
import Codec.Compression.GZip
45
import Control.Monad
56
import Data.Attoparsec.Binary
67
import Data.Attoparsec.ByteString
7-
import Data.Scientific
8+
import Data.Fixed
9+
import Data.Text (Text)
10+
import Data.Text.Encoding
11+
import Data.Text.Encoding.Error
812
import Data.Word
913

1014
import Ergvein.Index.Protocol.Types
@@ -42,7 +46,7 @@ word32toMessageType = \case
4246

4347
currencyCodeParser :: Parser CurrencyCode
4448
currencyCodeParser = do
45-
w <- anyWord32le
49+
w <- varInt
4650
case word32ToCurrencyCode w of
4751
Nothing -> fail "Invalid currency code"
4852
Just c -> pure c
@@ -63,6 +67,14 @@ word8toFeeLevel = \case
6367
2 -> Just FeeCheap
6468
_ -> Nothing
6569

70+
varInt :: Integral a => Parser a
71+
varInt = do
72+
w <- anyWord8
73+
if | w == 0xFF -> fmap fromIntegral anyWord64le
74+
| w == 0xFE -> fmap fromIntegral anyWord32le
75+
| w == 0xFD -> fmap fromIntegral anyWord16le
76+
| otherwise -> pure $ fromIntegral w
77+
6678
versionParser :: Parser ProtocolVersion
6779
versionParser = do
6880
bs :: S.Bitstream S.Right <- S.fromBits <$> anyWord32be
@@ -77,25 +89,29 @@ versionParser = do
7789

7890
messageHeaderParser :: Parser MessageHeader
7991
messageHeaderParser = do
80-
messageType <- messageTypeParser
81-
messageSize <- anyWord32le
82-
pure $ MessageHeader messageType messageSize
92+
mt <- messageTypeParser
93+
if not $ messageHasPayload mt then pure $ MessageHeader mt 0 else do
94+
messageSize <- varInt
95+
pure $ MessageHeader mt messageSize
96+
97+
messageLengthParser :: Parser Word32
98+
messageLengthParser = varInt
8399

84100
messageTypeParser :: Parser MessageType
85-
messageTypeParser = guardJust "out of message type bounds" . word32toMessageType =<< anyWord32le
101+
messageTypeParser = guardJust "out of message type bounds" . word32toMessageType =<< varInt
86102

87103
rejectCodeParser :: Parser RejectCode
88-
rejectCodeParser = guardJust "out of reject type bounds" . word32toRejectType =<< anyWord32le
104+
rejectCodeParser = guardJust "out of reject type bounds" . word32toRejectType =<< varInt
89105

90106
feeLevelParser :: Parser FeeLevel
91107
feeLevelParser = guardJust "out of feeLevel type bounds" . word8toFeeLevel =<< anyWord8
92108

93109
versionBlockParser :: Parser ScanBlock
94110
versionBlockParser = do
95111
currency <- currencyCodeParser
96-
version <- anyWord32le
97-
scanHeight <- anyWord64le
98-
height <- anyWord64le
112+
version <- versionParser
113+
scanHeight <- varInt
114+
height <- varInt
99115

100116
pure $ ScanBlock
101117
{ scanBlockCurrency = currency
@@ -104,11 +120,16 @@ versionBlockParser = do
104120
, scanBlockHeight = height
105121
}
106122

107-
filterParser :: Parser BlockFilter
108-
filterParser = do
109-
blockIdLength <- fromIntegral <$> anyWord32le
110-
blockId <- Parse.take blockIdLength
111-
blockFilterLength <- fromIntegral <$> anyWord32le
123+
blockIdLength :: CurrencyCode -> Int
124+
blockIdLength = \case
125+
BTC -> 32
126+
TBTC -> 32
127+
_ -> 32 -- TODO: edit for other currencies if differ
128+
129+
filterParser :: CurrencyCode -> Parser BlockFilter
130+
filterParser c = do
131+
blockId <- Parse.take $ blockIdLength c
132+
blockFilterLength <- fromIntegral <$> (varInt :: Parser Word32)
112133
blockFilter <- Parse.take blockFilterLength
113134

114135
pure $ BlockFilter
@@ -121,26 +142,32 @@ addressParser = do
121142
addrType <- maybe (fail "Invalid address type") pure
122143
. word8ToIPType
123144
=<< anyWord8
124-
addrPort <- anyWord16le
125-
addr <- Parse.take (if addrType == IPV4 then 4 else 16)
126-
pure $ Address
127-
{ addressType = addrType
128-
, addressPort = addrPort
129-
, addressAddress = addr
130-
}
145+
case addrType of
146+
IPV4 -> AddressIpv4 <$> anyWord32be <*> anyWord16be
147+
IPV6 -> AddressIpv6 <$> (IpV6 <$> anyWord32be <*> anyWord32be <*> anyWord32be <*> anyWord32be) <*> anyWord16be
148+
OnionV3 -> AddressOnionV3 <$> Parse.take (fromIntegral $ addressSize OnionV3) <*> anyWord16be
149+
150+
textParser :: Parser Text
151+
textParser = do
152+
l :: Word32 <- varInt
153+
bs <- Parse.take (fromIntegral l)
154+
pure $ decodeUtf8With lenientDecode bs
131155

132156
messageParser :: MessageType -> Parser Message
133157
messageParser MPingType = MPing <$> anyWord64le
134158

135159
messageParser MPongType = MPong <$> anyWord64le
136160

137-
messageParser MRejectType = MReject . Reject <$> rejectCodeParser
161+
messageParser MRejectType = fmap MReject $ Reject
162+
<$> messageTypeParser
163+
<*> rejectCodeParser
164+
<*> textParser
138165

139166
messageParser MVersionType = do
140167
version <- versionParser
141168
time <- fromIntegral <$> anyWord64le
142169
nonce <- anyWord64le
143-
currencies <- anyWord32le
170+
currencies <- varInt :: Parser Word32
144171
versionBlocks <- UV.fromList <$> replicateM (fromIntegral currencies) versionBlockParser
145172

146173
pure $ MVersion $ Version
@@ -150,12 +177,12 @@ messageParser MVersionType = do
150177
, versionScanBlocks = versionBlocks
151178
}
152179

153-
messageParser MVersionACKType = MVersionACK VersionACK <$ word8 0
180+
messageParser MVersionACKType = pure $ MVersionACK VersionACK
154181

155182
messageParser MFiltersRequestType = do
156183
currency <- currencyCodeParser
157-
start <- anyWord64le
158-
amount <- anyWord64le
184+
start <- varInt
185+
amount <- varInt
159186

160187
pure $ MFiltersRequest $ FilterRequest
161188
{ filterRequestMsgCurrency = currency
@@ -165,11 +192,11 @@ messageParser MFiltersRequestType = do
165192

166193
messageParser MFiltersResponseType = do
167194
currency <- currencyCodeParser
168-
amount <- anyWord32le
195+
amount :: Word32 <- varInt
169196
filtersString <- takeLazyByteString
170197

171198
let unzippedFilters = LBS.toStrict $ decompress filtersString
172-
parser = V.fromList <$> replicateM (fromIntegral amount) filterParser
199+
parser = V.fromList <$> replicateM (fromIntegral amount) (filterParser currency)
173200

174201
case parseOnly parser unzippedFilters of
175202
Right parsedFilters -> pure $ MFiltersResponse $ FilterResponse
@@ -181,10 +208,9 @@ messageParser MFiltersResponseType = do
181208

182209
messageParser MFilterEventType = do
183210
currency <- currencyCodeParser
184-
height <- anyWord64le
185-
blockIdLength <- fromIntegral <$> anyWord32le
186-
blockId <- Parse.take blockIdLength
187-
blockFilterLength <- fromIntegral <$> anyWord32le
211+
height <- varInt
212+
blockId <- Parse.take (blockIdLength currency)
213+
blockFilterLength <- fromIntegral <$> (varInt :: Parser Word32)
188214
blockFilter <- Parse.take blockFilterLength
189215

190216
pure $ MFiltersEvent $ FilterEvent
@@ -195,69 +221,68 @@ messageParser MFilterEventType = do
195221
}
196222

197223
messageParser MFeeRequestType = do
198-
amount <- anyWord32le
224+
amount :: Word32 <- varInt
199225
curs <- replicateM (fromIntegral amount) currencyCodeParser
200226
pure $ MFeeRequest curs
201227

202228
messageParser MFeeResponseType = do
203-
amount <- anyWord32le
229+
amount :: Word32 <- varInt
204230
resps <- replicateM (fromIntegral amount) parseFeeResp
205231
pure $ MFeeResponse resps
206232

207-
messageParser MPeerRequestType = MPeerRequest PeerRequest <$ word8 0
233+
messageParser MPeerRequestType = pure $ MPeerRequest PeerRequest
208234

209235
messageParser MPeerResponseType = do
210-
amount <- anyWord32le
236+
amount :: Word32 <- varInt
211237
addresses <- V.fromList <$> replicateM (fromIntegral amount) addressParser
212238
pure $ MPeerResponse $ PeerResponse
213239
{ peerResponseAddresses = addresses
214240
}
215241

216242
messageParser MIntroducePeerType = do
217-
amount <- anyWord32le
243+
amount :: Word32 <- varInt
218244
addresses <- V.fromList <$> replicateM (fromIntegral amount) addressParser
219245
pure $ MPeerIntroduce $ PeerIntroduce
220246
{ peerIntroduceAddresses = addresses
221247
}
222248

223249
messageParser MRatesRequestType = do
224-
n <- fmap fromIntegral anyWord32le
250+
n <- fmap fromIntegral (varInt :: Parser Word32)
225251
cfs <- replicateM n cfParser
226252
pure $ MRatesRequest $ RatesRequest $ M.fromList cfs
227253

228254
messageParser MRatesResponseType = do
229-
n <- fmap fromIntegral anyWord32le
255+
n <- fmap fromIntegral (varInt :: Parser Word32)
230256
cfds <- replicateM n cfdParser
231257
pure $ MRatesResponse $ RatesResponse $ M.fromList cfds
232258

233259
enumParser :: Enum a => Parser a
234-
enumParser = fmap (toEnum . fromIntegral) anyWord32le
260+
enumParser = fmap (toEnum . fromIntegral) (varInt :: Parser Word32)
235261

236262
cfParser :: Parser (CurrencyCode, [Fiat])
237263
cfParser = do
238264
c <- enumParser
239-
n <- fmap fromIntegral anyWord32le
265+
n <- fmap fromIntegral (varInt :: Parser Word32)
240266
fmap (c, ) $ replicateM n enumParser
241267

242-
cfdParser :: Parser (CurrencyCode, M.Map Fiat Double)
268+
cfdParser :: Parser (CurrencyCode, M.Map Fiat Centi)
243269
cfdParser = do
244270
c <- enumParser
245-
n <- fmap fromIntegral anyWord32le
271+
n <- fmap fromIntegral (varInt :: Parser Word32)
246272
fmap ((c,) . M.fromList) $ replicateM n fdParser
247273

248-
fdParser :: Parser (Fiat, Double)
249-
fdParser = (,) <$> enumParser <*> parseDouble
274+
fdParser :: Parser (Fiat, Centi)
275+
fdParser = (,) <$> enumParser <*> parseCenti
250276

251-
parseDouble :: Parser Double
252-
parseDouble = do
253-
c <- fromIntegral <$> anyWord64le
254-
e <- fromIntegral <$> anyWord64le
255-
pure $ toRealFloat $ scientific c e
277+
parseCenti :: Parser Centi
278+
parseCenti = do
279+
w <- anyWord64le
280+
pure $ MkFixed $ fromIntegral w
256281

257282
parseCurrencyPair :: Parser (CurrencyCode, Fiat)
258283
parseCurrencyPair = (,)
259-
<$> (fmap (toEnum . fromIntegral) anyWord32le)
260-
<*> (fmap (toEnum . fromIntegral) anyWord32le)
284+
<$> (fmap (toEnum . fromIntegral) (varInt :: Parser Word32))
285+
<*> (fmap (toEnum . fromIntegral) (varInt :: Parser Word32))
261286

262287
parseFeeResp :: Parser FeeResp
263288
parseFeeResp = do
@@ -268,14 +293,14 @@ parseFeeResp = do
268293
_ -> genericParser currency
269294
where
270295
btcParser isTest = do
271-
h <- (,) <$> anyWord64le <*> anyWord64le
272-
m <- (,) <$> anyWord64le <*> anyWord64le
273-
l <- (,) <$> anyWord64le <*> anyWord64le
296+
h <- (,) <$> varInt <*> varInt
297+
m <- (,) <$> varInt <*> varInt
298+
l <- (,) <$> varInt <*> varInt
274299
pure $ FeeRespBTC isTest $ FeeBundle h m l
275300
genericParser cur = FeeRespGeneric cur
276-
<$> anyWord64le
277-
<*> anyWord64le
278-
<*> anyWord64le
301+
<$> varInt
302+
<*> varInt
303+
<*> varInt
279304

280305
parseMessage :: MessageType -> BS.ByteString -> Either String (Message, BS.ByteString)
281306
parseMessage msgType source =

0 commit comments

Comments
 (0)