From 3b63d9148006ad161c67574844d9461ee3c58946 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Tue, 11 Feb 2020 16:54:43 +0300 Subject: [PATCH 1/4] accounting: Use little endian in number encoding NeoFS smart-contract uses little endian for number of signatures, GAS amount and block height. If we encode these parameters in little endian, smart-contract will not reverse bytes during execution therefore will be cheaper. --- accounting/fixtures/cheque.sh | 2 +- accounting/fixtures/cheque_data | Bin 650 -> 650 bytes accounting/types.go | 12 ++++++------ accounting/types_test.go | 2 ++ 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/accounting/fixtures/cheque.sh b/accounting/fixtures/cheque.sh index 93368138..581402a9 100755 --- a/accounting/fixtures/cheque.sh +++ b/accounting/fixtures/cheque.sh @@ -1,6 +1,6 @@ #!/bin/bash -CHEQUE=d6520dabb6cb9b981792608c73670eff14775e9a65bbc189271723ba2703c53263e8d6e522dc32203339dcd8eee9c6b7439a0000000053724e000000000000001e61000603012d47e76210aec73be39ab3d186e0a40fe8d86bfa3d4fabfda57ba13b88f96abe1de4c7ecd46cb32081c0ff199e0b32708d2ce709dd146ce096484073a9b15a259ca799f8d848eb5bea16f6d0842a0181ccd47384af2cdb0fd0af0819e8a08802f7528ce97c9a93558efe7d4f62577aabdf771c931f54a71be6ad21e7d9cc1777686ad19b5dc4b80d7b8decf90054c5aad66c0e6fe63d8473b751cd77c1bd0557516e0f3e7d0ccb485809023b0c08a89f33ae38b2f99ce3f1ebc7905dddf0ed0f023e00f03a16e8707ce045eb42ee80d392451541ee510dc18e1c8befbac54d7426087d37d32d836537d317deafbbd193002a36f80fbdfbf3a730cf011bc6c75c7e6d5724f3adee7015fcb3068d321e2ae555e79107be0c46070efdae2f724dbc9f0340750b92789821683283bcb98e32b7e032b94f267b6964613fc31a7ce5813fddeea47a1db525634237e924178b5c8ea745549ae60aa3570ce6cf52e370e6ab87652bdf8a179176f1acaf48896bef9ab300818a53f410d86241d506a550f4915403fef27f744e829131d0ec980829fafa51db1714c2761d9f78762c008c323e9d6612e4f9efdc609f191fd9ca5431dd9dc037130150107ab8769780d728e9ffdf314019b57c8d2b940b9ec078afa951ed8b06c1bf352edd2037e29b8f24cca3ec700368a6f5829fb2a34fa03d0308ae6b05f433f2904d9a852fed1f5d2eb598ca79475b74ef6394e712d275cd798062c6d8e41fad822ac5a4fcb167f0a2e196f61f9f65a0adef9650f49150e7eb7bb08dd1739fa6e86b341f1b2cf5657fcd200637e8 +CHEQUE=7849b02d01cc7f7734295fa815ea64ec4d2012e45b8781eb891723ba2703c53263e8d6e522dc32203339dcd8eee9c6b7439a00ea56fa00000000611e000000000000060003012d47e76210aec73be39ab3d186e0a40fe8d86bfa3d4fabfda57ba13b88f96a8ebe4360627a1326f13fb9516c0dbc4af90f116e44bd33f4d04a0d1633afa243ad4f2fa9cd933e7631a619b5132cec6983906aba757af5590434124b232a43e302f7528ce97c9a93558efe7d4f62577aabdf771c931f54a71be6ad21e7d9cc177744b4b9781cf0c29adb503f33d2df9f810ebf33a774849848984cf7e2bbebd48ef0cd8592fbf9b6aee1dc74803e31c95a02dbbd5fd9783f9ecbcbf444b5942f830368a6f5829fb2a34fa03d0308ae6b05f433f2904d9a852fed1f5d2eb598ca794770adb1ece9dccd1c7ad98f709cfb890e3bdd5973dcdd838111fae2efa4c3c09ea2133e5d7c6eac6ae24afcce46db7c9f4dc154f123c835adff4e0b7e19bcffda0340750b92789821683283bcb98e32b7e032b94f267b6964613fc31a7ce5813fddad8298f71dfdc4f9b3e353f969727af476d43b12a25727cf6b9c73ae7152266d995bec807068ad2156288c4d946aeb17ebca787d498a1b87b9dae1bcd935763403fef27f744e829131d0ec980829fafa51db1714c2761d9f78762c008c323e9d66db9b5086d355897e52fe065e14f1cc70334248349aa4c7a3e6e3dc8f8693b1511c73dc88e6d6e8b6c6c68de922f351b5b1543917af2f2a3588aebfbd1ff3fac6023e00f03a16e8707ce045eb42ee80d392451541ee510dc18e1c8befbac54d742648b58f379b5337d9b74c5a61afb8ef3db7f3eb0454d6823777b613a3ee22cd6ce47e4fa72170d49267b773cc09c123654e0bcd7278aa2ae1e7c85d049b557a3c DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) echo $CHEQUE | xxd -p -r > $DIR/cheque_data diff --git a/accounting/fixtures/cheque_data b/accounting/fixtures/cheque_data index cd7b286642c3d1fc8d0080985c4d070013761b63..a1530960f321d4d6d1cb7f11f9cf1de82006ffc4 100644 GIT binary patch delta 485 zcmVzU3_k= zYT`=#&PLmOpH0D3@gvAJt^ZC7ei^*~+LLDiAwaEynD-t1#QC%1Q~7Cfdh~YGI})N- zC(mn~bFOhxCT*Ep?0|4+tszz@j7^kk>lf?FczsEV8;7~t;k?;3b~Ka70U^ZLe<1_2>JNVShQn^QO0w@g}Luek3$xAW@+RMvtwceWFw?jp@>lWxK(NCl@2SCj-SYW9Zi9BHS_{ zGdbMY?&-$2Lz(~p002{RP5=M^000009$^3mkwhUtz8&Pp?9^&K8?-SF)XlPLlrKnQ&|(=CH#H`5p1ue;Hc04g^4554>Ir!da}8^*_6er;DI^R4c1 z75uXXjWQl8JTXgSZl;;xCb int64(len(buf[offset:])) { diff --git a/accounting/types_test.go b/accounting/types_test.go index 424993a8..14aee3b7 100644 --- a/accounting/types_test.go +++ b/accounting/types_test.go @@ -80,5 +80,7 @@ func TestCheque(t *testing.T) { require.Equal(t, expect, actual) require.NoError(t, cheque.Verify()) + require.Equal(t, cheque.Height, uint64(7777)) + require.Equal(t, cheque.Amount, decimal.NewGAS(42)) }) } From de09878df18f8f268872e69f45021bd135de10e8 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 13 Feb 2020 18:10:46 +0300 Subject: [PATCH 2/4] Response meta header --- docs/object.md | 10 ++++++++++ docs/service.md | 14 ++++++++++++++ object/service.pb.go | Bin 123021 -> 131925 bytes object/service.proto | 18 +++++++++++++++++- service/meta.pb.go | Bin 9960 -> 14999 bytes service/meta.proto | 10 ++++++++++ 6 files changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index d13b6768..ce9252bc 100644 --- a/docs/object.md +++ b/docs/object.md @@ -161,6 +161,10 @@ DeleteResponse is empty because we cannot guarantee permanent object removal in distributed system. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Meta | [service.ResponseMetaHeader](#service.ResponseMetaHeader) | | ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message) | + @@ -186,6 +190,7 @@ in distributed system. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | Hashes | [bytes](#bytes) | repeated | Hashes is a homomorphic hashes of all ranges | +| Meta | [service.ResponseMetaHeader](#service.ResponseMetaHeader) | | ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message) | @@ -211,6 +216,7 @@ in distributed system. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | Fragment | [bytes](#bytes) | | Fragment of object's payload | +| Meta | [service.ResponseMetaHeader](#service.ResponseMetaHeader) | | ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message) | @@ -237,6 +243,7 @@ in distributed system. | ----- | ---- | ----- | ----------- | | object | [Object](#object.Object) | | Object header and some payload | | Chunk | [bytes](#bytes) | | Chunk of remaining payload | +| Meta | [service.ResponseMetaHeader](#service.ResponseMetaHeader) | | ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message) | @@ -263,6 +270,7 @@ in distributed system. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | Object | [Object](#object.Object) | | Object without payload | +| Meta | [service.ResponseMetaHeader](#service.ResponseMetaHeader) | | ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message) | @@ -300,6 +308,7 @@ in distributed system. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | Address | [refs.Address](#refs.Address) | | Address of object (container id + object id) | +| Meta | [service.ResponseMetaHeader](#service.ResponseMetaHeader) | | ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message) | @@ -326,6 +335,7 @@ in distributed system. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | Addresses | [refs.Address](#refs.Address) | repeated | Addresses of found objects | +| Meta | [service.ResponseMetaHeader](#service.ResponseMetaHeader) | | ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message) | diff --git a/docs/service.md b/docs/service.md index 454fab42..90e1bd2b 100644 --- a/docs/service.md +++ b/docs/service.md @@ -7,6 +7,7 @@ - Messages - [RequestMetaHeader](#service.RequestMetaHeader) + - [ResponseMetaHeader](#service.ResponseMetaHeader) - [service/verify.proto](#service/verify.proto) @@ -49,6 +50,19 @@ RequestMetaHeader contains information about request meta headers | Epoch | [uint64](#uint64) | | Epoch for user can be empty, because node sets epoch to the actual value | | Version | [uint32](#uint32) | | Version defines protocol version TODO: not used for now, should be implemented in future | + + + +### Message ResponseMetaHeader +ResponseMetaHeader contains meta information based on request processing by server +(should be embedded into message) + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Epoch | [uint64](#uint64) | | Current NeoFS epoch on server | +| Version | [uint32](#uint32) | | Version defines protocol version TODO: not used for now, should be implemented in future | + diff --git a/object/service.pb.go b/object/service.pb.go index 1ecf6d9b34d58a5b9c714504fdc04c3d1274b4ee..46891e2f97ab73398f4f4cf1c3b3fffbfb802000 100644 GIT binary patch delta 7238 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 delta 5934 zcmZXYU5H&*6~{R-W^N`kowiPz)Y|k;S`rO0Ip^Ga?m1V`hYpBjAT6~h661K!mmk=s zXu;8!(<%rGRd6f!!9EQ7AhjxmOSuMoXp1iqkt!(GS_>AdQk=YK^CIZl|KDb~Y97v< z?6dd!{ICC7`~K!Dy(gYudgjd|?z_j{=H5JV$~FCa+)w(qxcg7v;(pLyv-iaPwSV0G z{Z~h3Pp*FS_)34_pX(povJ)rW1KS(6d)0l({{G(n{;mIx*-uaZaK(N3;iK-}%`@&p zTW8!;+x^)q+1)F*El9A}GxwFBZmLK7?(w_c?@k^2ul>_s+!Nh2dvWvf%Id~4d+z1!W3$^Ic;m?W!q)z!%jFjyZTepNo$mC<-o2<{_SE)| zSO3SFOP4<|`P?V9f88`3c3*txylFY%wl+89#Y>+!_svJVvb$XGp_SS8m8|!cA|JZx z=O6AyXZ!I^Rr}|t@y}-9pV{#2^5J+V8S1k>6xx*N^C0)@x!#*p@7g)hdtLgKLF}JH zt&gN;toKas^+*c3Lc0o`%k)03bXqIJ!oOG2ld@A*6q1xmQZd&4iFWE8X(bwJi8Fsp z&^YtYkpwkzp&Lj8{vc^286(MywW)3Wu~H%$)@4x{%LkmR^EWcm>|kcL?Q0f(40kzqpx2CO1!sCB+J z@_ejkI3-(ZodRg1k~Dxr1;!V5XBDb*t(FR%7%5k*3<(KNA-&R|5S+$7in24* zjs6cfkW4BWW2Iz5iscC)LS8GWv208PiH1aHI&4`t1w%?qB0v%dGdlG8OL@W*$N(;k z#5$soU<(yp$?Q_H$8eSe(M;$K0Wp{jPisF8T420>3WTZ;0B95?oHkt9rhy!-36;_S z%d-w*Qxc%B2!i~E3QsN@>0c9gwJka?yOeC|hiVAXldbpzfkTHN6M9dOD&L3*pbMl# z?t^GSyip8enoKMGgxeBlwg9C13rfQDoJ7md<28#VJ4ulK2ZoWxdt`1`U=tI7JsjS)(U5o z+KYVy?WLmdkmv!og7nB#Dl;dxs)I_lSPgjvjlu(<1Kf2u>68Z&KsTYA(ZL6NRm7-e zY^kJ^J{pH0X%o#X(Xm!f4uG4nT&uK>@uNcZ#q9*GCC54K1HEz{(0}jBJ@ob&&?rI?+ysE$Rc!HNnFL znscG8lvS50l(dBufXdHS?^N_+rCSfMDxuOW^uBuKf^~E60s|Ww;1xD8NYYFwS*Wq& zKqX1Jd?xgaKfXnnHedxQ*+^zfj25(fCZfPxa)&vSu@RbJrZD!mv#>Qp(-FOt4@9X| zxLHc&9Ld$dvcpuz{0I3NnVD3E8i+7ZlW2`P#3F-)4-zd!UEAg_xlZF}=%hBoSdT4A z3AIo6VU(vDv1KUoty5`$YpDhrKjL6F5}=OX?cgV%q?<01AG12352F=nU>0Kjp#mr( zhR#gJKy^cfA@mQW2TW@6?slNvz;W`{K%FKi!wZ zOp>*#A>4fkS*p@xcwtfq;So(R)lnJLd#->{X<>Gg(9pRQyev7p-0deCbaPD@%3}7? zO#_Q4NH7PpP_9ssq;Ts}HG*XG(+6oH{@_-OBh0fS<}dr>>sL7;48RK16K)a{NLI0+ zXIkBQ4cvrbMOrSCc_v4|KC}Xfz;u>*KYs-%DJGODQv;)&v4PhBL+(%dc5Ecq6JZap zZ~=TWK`f?}qYlvhpjWd_i5A`oB5jHAK(%l%0|u$DDPleGdAPs{8S`Jb)@0JaJsuozDWaXz zX6bnR;$ih;FbGIjkiv~$kf3518X!okFz*7FHEzyrr@eRr$%Nl46ac7!F(+7o9z97I zFl55E+0ttWx{k%P1%iDcka_-RM`slxz0t%UoG!e%3aTu>|Q z!nmtl)H1^@Z84Wg3>aKBx!sVA#mWfjR*4z(y$0qVo*WC8FI5R_*@M#Ka@K$1`Fp3E zcSN_&uD$r4Tc^LgIlAUv$$IY9^GojPjs5r8SO5I2rRmLgM@w$?<)d!*#QA3pi`JffI=a@IUs#U5c6j=ybJ3kzod0$`T07{f z*EgouPevpAt$2O@?c1Z}L(_lvqqk0X&qo*COWBHP@6EUR(fba#EC1Y>+t$YpOrJXu zeb|1%U7NrCRP@4ueHqgey=d8e{3A=#3;pQv`SO|Q*@(R-uH58yKeaS}=Fi|g!gpZQ?VedFSi n|MIwi9KYwPGH!Mk2(>DY^UXV9V4(^2bqSCn7t@k_+I{~_m-OZZ_cU|i)bsg&qQw2^U^{IE9Oj6Tt zQ?VEMIDLNgZ`;8T4TVh{bLgDdF` z2F>&;gOAed41P}Q45s;GSm!0ie&z2ls0pvG@X>t*?xhB9+kSw(Tl-yHEFO=2@&u)# zW8W)P&^dY|wuK|b_!7Pe&l9Q|?qzn9BkghpiG8a^V^yP3HEEQqC(#7nj2W>a@M2$S z(X;_kfrJD_v5{uc>{t+60~1%OfOU!1_~sGGN@6}yuNnAM z4kYr7b&2j;8j9P$$}C2I8;gTh$cc4Sux39Zl?oG~**lWbDPsi| Date: Fri, 14 Feb 2020 12:14:19 +0300 Subject: [PATCH 3/4] Implement Epoch and Version setters on ResponseMetaHeader --- service/meta.go | 6 ++++++ service/meta_test.go | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/service/meta.go b/service/meta.go index 95c1b13a..dc2cdd36 100644 --- a/service/meta.go +++ b/service/meta.go @@ -62,6 +62,12 @@ const ( ErrIncorrectTTL = internal.Error("incorrect ttl") ) +// SetVersion sets protocol version to ResponseMetaHeader. +func (m *ResponseMetaHeader) SetVersion(v uint32) { m.Version = v } + +// SetEpoch sets Epoch to ResponseMetaHeader. +func (m *ResponseMetaHeader) SetEpoch(v uint64) { m.Epoch = v } + // SetVersion sets protocol version to RequestMetaHeader. func (m *RequestMetaHeader) SetVersion(v uint32) { m.Version = v } diff --git a/service/meta_test.go b/service/meta_test.go index e208dfe1..083ccd66 100644 --- a/service/meta_test.go +++ b/service/meta_test.go @@ -88,3 +88,17 @@ func TestMetaRequest(t *testing.T) { }) } } + +func TestRequestMetaHeader_SetEpoch(t *testing.T) { + m := new(ResponseMetaHeader) + epoch := uint64(3) + m.SetEpoch(epoch) + require.Equal(t, epoch, m.GetEpoch()) +} + +func TestRequestMetaHeader_SetVersion(t *testing.T) { + m := new(ResponseMetaHeader) + version := uint32(3) + m.SetVersion(version) + require.Equal(t, version, m.GetVersion()) +} From 04ead038592aa32eac60e25751c0654033eccb67 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Tue, 18 Feb 2020 12:04:09 +0300 Subject: [PATCH 4/4] Update changelog for v0.4.0 --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 124d3495..d8325681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog -This is the changelog for NeoFS Proto +This is the changelog for NeoFS API + +## [0.4.0] - 2020-02-18 + +### Added +- Meta header for all gRPC responses. It contains epoch stamp and version number. +### Changed +- Endianness in accounting cheque. Now it uses little endian for cheaper +decoding in neofs smart-contract. ## [0.3.2] - 2020-02-10 @@ -189,3 +197,4 @@ Initial public release [0.3.0]: https://github.com/nspcc-dev/neofs-api/compare/v0.2.14...v0.3.0 [0.3.1]: https://github.com/nspcc-dev/neofs-api/compare/v0.3.0...v0.3.1 [0.3.2]: https://github.com/nspcc-dev/neofs-api/compare/v0.3.1...v0.3.2 +[0.4.0]: https://github.com/nspcc-dev/neofs-api/compare/v0.3.2...v0.4.0