diff --git a/config/local.yaml b/config/local.yaml index 7b0ae478c..c1d6a81db 100644 --- a/config/local.yaml +++ b/config/local.yaml @@ -317,6 +317,9 @@ checks: postage-depth: 21 postage-label: test-label request-timeout: 5m + upload-r-level: 2 + download-r-level: 2 + cache : false timeout: 5m type: soc ci-content-availability: diff --git a/config/public-testnet.yaml b/config/public-testnet.yaml index ebd787358..87dfb6d67 100644 --- a/config/public-testnet.yaml +++ b/config/public-testnet.yaml @@ -102,6 +102,9 @@ checks: postage-depth: 22 postage-label: test-label request-timeout: 5m + upload-r-level: 2 + download-r-level: 3 + cache : true timeout: 15m type: soc pt-pushsync-chunks: diff --git a/go.mod b/go.mod index 88725da12..774f2a91f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ toolchain go1.25.2 replace github.com/codahale/hdrhistogram => github.com/HdrHistogram/hdrhistogram-go v1.1.2 +replace github.com/ethersphere/bee/v2 => github.com/ethersphere/bee/v2 v2.6.1-0.20251112182101-77fa1e3cdd23 + require ( github.com/ethereum/go-ethereum v1.15.11 github.com/ethersphere/bee/v2 v2.6.0 @@ -37,6 +39,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/armon/go-radix v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -68,6 +71,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -118,6 +122,7 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.14 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/uber/jaeger-lib v2.2.0+incompatible // indirect @@ -145,6 +150,9 @@ require ( k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect lukechampine.com/blake3 v1.2.1 // indirect + resenje.org/feed v0.1.2 // indirect + resenje.org/multex v0.1.0 // indirect + resenje.org/singleflight v0.4.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/go.sum b/go.sum index 0ce8c4c84..6e5928889 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ github.com/ethereum/go-ethereum v1.15.11 h1:JK73WKeu0WC0O1eyX+mdQAVHUV+UR1a9VB/d github.com/ethereum/go-ethereum v1.15.11/go.mod h1:mf8YiHIb0GR4x4TipcvBUPxJLw1mFdmxzoDi11sDRoI= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= -github.com/ethersphere/bee/v2 v2.6.0 h1:r799kEmHNXzcXjwiTh+ZuUCqpj+vbeOg3hSUPQoaARg= -github.com/ethersphere/bee/v2 v2.6.0/go.mod h1:C2WAuC6f4sngyQ15Rw1x+7WiYjTdpkB4K3s5yKhCRRQ= +github.com/ethersphere/bee/v2 v2.6.1-0.20251112182101-77fa1e3cdd23 h1:XeHmrtva6pWTvr97pdd4Zo9XfMoGK1MHyJRrzh2CnnE= +github.com/ethersphere/bee/v2 v2.6.1-0.20251112182101-77fa1e3cdd23/go.mod h1:0bCIUIsZWp5MNh9hTTYPbmhvKFNddaxzkwUwGo8RS4s= github.com/ethersphere/bmt v0.1.4 h1:+rkWYNtMgDx6bkNqGdWu+U9DgGI1rRZplpSW3YhBr1Q= github.com/ethersphere/bmt v0.1.4/go.mod h1:Yd8ft1U69WDuHevZc/rwPxUv1rzPSMpMnS6xbU53aY8= github.com/ethersphere/ethproxy v0.0.5 h1:j5Mkm45jqmkET6NwGaJtaxOSFbhoAfOKzHiwHl6DBT0= @@ -96,6 +96,8 @@ github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNP github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -141,12 +143,23 @@ github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQg github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -174,6 +187,7 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= @@ -272,10 +286,18 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= @@ -407,9 +429,12 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -417,16 +442,24 @@ golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -444,6 +477,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= @@ -460,6 +494,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -468,14 +508,19 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -496,6 +541,12 @@ k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +resenje.org/feed v0.1.2 h1:3OianQkoI4EalWx1SlzHtGjUMsoB4XTJQbeehWiyeFI= +resenje.org/feed v0.1.2/go.mod h1:ABlv4P3svuZY3dkZq3un+XIEoX+TDwbGEkjLcSP8TnM= +resenje.org/multex v0.1.0 h1:am9Ndt8dIAeGVaztD8ClsSX+e0EP3mj6UdsvjukKZig= +resenje.org/multex v0.1.0/go.mod h1:3rHOoMrzqLNzgGWPcl/1GfzN52g7iaPXhbvTQ8TjGaM= +resenje.org/singleflight v0.4.0 h1:NdOEhCxEikK2S2WxGjZV9EGSsItolQKslOOi6pE1tJc= +resenje.org/singleflight v0.4.0/go.mod h1:lAgQK7VfjG6/pgredbQfmV0RvG/uVhKo6vSuZ0vCWfk= resenje.org/x v0.6.0 h1:afn9E4XhglF4y9Kq0VH5tdSyjnsVKxiYgB6HFj7ebss= resenje.org/x v0.6.0/go.mod h1:qgwe4MCzh57EkkMDurg24ug7HHfZtAjtBkmCihNmOpM= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/pkg/bee/api/api.go b/pkg/bee/api/api.go index 60d864faa..64b85f194 100644 --- a/pkg/bee/api/api.go +++ b/pkg/bee/api/api.go @@ -25,6 +25,7 @@ const ( swarmPinHeader = "Swarm-Pin" swarmTagHeader = "Swarm-Tag" swarmCacheDownloadHeader = "Swarm-Cache" + SwarmRedundancyLevelHeader = "Swarm-Redundancy-Level" swarmRedundancyFallbackMode = "Swarm-Redundancy-Fallback-Mode" swarmOnlyRootChunk = "Swarm-Only-Root-Chunk" swarmSocSignatureHeader = "Swarm-Soc-Signature" diff --git a/pkg/bee/api/feed.go b/pkg/bee/api/feed.go index 76b71de6e..6d7599c23 100644 --- a/pkg/bee/api/feed.go +++ b/pkg/bee/api/feed.go @@ -69,7 +69,7 @@ func (f *FeedService) CreateRootManifest(ctx context.Context, signer crypto.Sign } // UpdateWithRootChunk updates a feed with a root chunk -func (f *FeedService) UpdateWithRootChunk(ctx context.Context, signer crypto.Signer, topic []byte, i uint64, ch swarm.Chunk, o UploadOptions) (*SocResponse, error) { +func (f *FeedService) UpdateWithRootChunk(ctx context.Context, signer crypto.Signer, topic []byte, i uint64, ch swarm.Chunk, o UploadOptions) (*SOCResponse, error) { ownerHex, err := ownerFromSigner(signer) if err != nil { return nil, err @@ -88,7 +88,7 @@ func (f *FeedService) UpdateWithRootChunk(ctx context.Context, signer crypto.Sig signatureBytes := chunkData[swarm.HashSize : swarm.HashSize+swarm.SocSignatureSize] id := hex.EncodeToString(idBytes) sig := hex.EncodeToString(signatureBytes) - res, err := f.client.SOC.UploadSOC(ctx, ownerHex, id, sig, bytes.NewReader(ch.Data()), o.BatchID) + res, err := f.client.SOC.UploadSOC(ctx, ownerHex, id, sig, bytes.NewReader(ch.Data()), o.BatchID, nil) if err != nil { return nil, err } diff --git a/pkg/bee/api/soc.go b/pkg/bee/api/soc.go index 1fc84bd9b..5aab6e338 100644 --- a/pkg/bee/api/soc.go +++ b/pkg/bee/api/soc.go @@ -6,22 +6,30 @@ import ( "io" "net/http" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/swarm" ) // PSSService represents Bee's PSS service type SOCService service -type SocResponse struct { +type SOCResponse struct { Reference swarm.Address } +type SOCOptions struct { + RLevel *redundancy.Level +} + // Sends a PSS message to a recipienct with a specific topic -func (p *SOCService) UploadSOC(ctx context.Context, owner, ID, signature string, data io.Reader, batchID string) (*SocResponse, error) { +func (p *SOCService) UploadSOC(ctx context.Context, owner, ID, signature string, data io.Reader, batchID string, opt *SOCOptions) (*SOCResponse, error) { h := http.Header{} h.Add(postageStampBatchHeader, batchID) + if opt != nil && opt.RLevel != nil { + h.Add(SwarmRedundancyLevelHeader, fmt.Sprintf("%d", *opt.RLevel)) + } url := fmt.Sprintf("/%s/soc/%s/%s?sig=%s", apiVersion, owner, ID, signature) - resp := SocResponse{} + resp := SOCResponse{} return &resp, p.client.requestWithHeader(ctx, http.MethodPost, url, h, data, &resp) } diff --git a/pkg/bee/client.go b/pkg/bee/client.go index a43acae31..91d596c8a 100644 --- a/pkg/bee/client.go +++ b/pkg/bee/client.go @@ -615,8 +615,8 @@ func (c *Client) SendPSSMessage(ctx context.Context, nodeAddress swarm.Address, } // UploadSOC uploads a single owner chunk to a node with a E -func (c *Client) UploadSOC(ctx context.Context, owner, ID, signature string, data []byte, batchID string) (swarm.Address, error) { - resp, err := c.api.SOC.UploadSOC(ctx, owner, ID, signature, bytes.NewReader(data), batchID) +func (c *Client) UploadSOC(ctx context.Context, owner, ID, signature string, data []byte, batchID string, opt *api.SOCOptions) (swarm.Address, error) { + resp, err := c.api.SOC.UploadSOC(ctx, owner, ID, signature, bytes.NewReader(data), batchID, opt) if err != nil { return swarm.ZeroAddress, err } @@ -1024,7 +1024,7 @@ func (c *Client) CreateRootFeedManifest(ctx context.Context, signer crypto.Signe } // UpdateFeedWithRootChunk updates a feed with a root chunk -func (c *Client) UpdateFeedWithRootChunk(ctx context.Context, signer crypto.Signer, topic []byte, i uint64, ch swarm.Chunk, o api.UploadOptions) (*api.SocResponse, error) { +func (c *Client) UpdateFeedWithRootChunk(ctx context.Context, signer crypto.Signer, topic []byte, i uint64, ch swarm.Chunk, o api.UploadOptions) (*api.SOCResponse, error) { return c.api.Feed.UpdateWithRootChunk(ctx, signer, topic, i, ch, o) } diff --git a/pkg/check/gsoc/gsoc.go b/pkg/check/gsoc/gsoc.go index 87f390998..07063db15 100644 --- a/pkg/check/gsoc/gsoc.go +++ b/pkg/check/gsoc/gsoc.go @@ -194,7 +194,7 @@ func uploadSoc(ctx context.Context, client *bee.Client, payload string, resource return fmt.Errorf("make soc: %w", err) } - _, err = client.UploadSOC(ctx, d.Owner, hex.EncodeToString(resourceId), d.Sig, d.Data, batchID) + _, err = client.UploadSOC(ctx, d.Owner, hex.EncodeToString(resourceId), d.Sig, d.Data, batchID, nil) if err != nil { return fmt.Errorf("upload soc: %w", err) } diff --git a/pkg/check/soc/README.md b/pkg/check/soc/README.md new file mode 100644 index 000000000..da3157f6e --- /dev/null +++ b/pkg/check/soc/README.md @@ -0,0 +1,63 @@ +# SOC Check Documentation + +## Overview + +The SOC (Single Owner Chunk) check validates the upload and retrieval of SOC chunks in the Swarm network, with configurable redundancy levels and cache behavior. + +## What it does + +1. **SOC Chunk Creation**: Creates a SOC chunk with test payload "Hello Swarm :)" using cryptographic signing +2. **Upload**: Uploads the SOC chunk to a bee node with specified redundancy level +3. **Original Retrieval**: Downloads and validates the original chunk from the upload node +4. **Replica Testing**: Tests retrieval of replica chunks created by the redundancy system + +## Key Features + +### Configurable Redundancy + +- **Upload Redundancy Level**: Controls how many replica chunks are created during upload +- **Download Redundancy Level**: Controls how many replica chunks are tested during download +- Supports levels: NONE, MEDIUM, STRONG, INSANE, PARANOID + +### Cache Behavior Control + +- **Cache = true**: Downloads replicas from local node storage (cache) +- **Cache = false**: Downloads replicas from network peers with retry logic + +### Multi-Node Support + +- **Cache = true**: Uses single node for upload and download +- **Cache = false**: Uses separate nodes (upload to node1, download replicas from node2) + +### Retry Logic + +When `Cache = false`, implements retry mechanism: + +- Up to 5 retry attempts per replica download +- 1-second delay between retries +- Allows time for chunks to propagate through the network + +## Validation Logic + +The check validates that: + +- Original chunk data matches after upload/download +- Expected number of replica retrievals succeed/fail based on redundancy levels +- Failed replica retrievals return expected error types (HTTP 500 "read chunk failed") +- Replica chunk data matches the original chunk data + +## Use Cases + +- **Network Dispersal Testing**: Verify chunks are properly distributed across peer nodes +- **Cache vs Network Performance**: Compare local cache vs network retrieval speeds +- **Redundancy Validation**: Ensure redundancy levels work as expected +- **Error Handling**: Test proper error responses when replicas are unavailable + +## Configuration + +```yaml +cache: true/false # Local cache vs network retrieval +upload-r-level: 3 # Upload redundancy level +download-r-level: 4 # Download redundancy level +request-timeout: 5m # Timeout for operations +``` diff --git a/pkg/check/soc/soc.go b/pkg/check/soc/soc.go index e1326bbd4..fa390b70c 100644 --- a/pkg/check/soc/soc.go +++ b/pkg/check/soc/soc.go @@ -7,12 +7,17 @@ import ( "encoding/hex" "errors" "fmt" + "strings" "time" "github.com/ethersphere/bee/v2/pkg/cac" "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/replicas/combinator" "github.com/ethersphere/bee/v2/pkg/soc" "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/ethersphere/beekeeper/pkg/bee" + "github.com/ethersphere/beekeeper/pkg/bee/api" "github.com/ethersphere/beekeeper/pkg/beekeeper" "github.com/ethersphere/beekeeper/pkg/logging" "github.com/ethersphere/beekeeper/pkg/orchestration" @@ -26,6 +31,9 @@ type Options struct { PostageDepth uint64 PostageLabel string RequestTimeout time.Duration + UploadRLevel redundancy.Level + DownloadRLevel redundancy.Level + Cache bool // // if true fetches from bee local storage, if false fetches from network (peers) } // NewDefaultOptions returns new default options @@ -36,6 +44,9 @@ func NewDefaultOptions() Options { PostageDepth: 16, PostageLabel: "test-label", RequestTimeout: 5 * time.Minute, + UploadRLevel: redundancy.PARANOID, + DownloadRLevel: redundancy.PARANOID, + Cache: true, } } @@ -104,50 +115,240 @@ func (c *Check) Run(ctx context.Context, cluster orchestration.Cluster, opts any return fmt.Errorf("shuffled full node clients: %w", err) } - if len(nodes) < 1 { - return fmt.Errorf("soc test requires at least 1 full node") + minNodesRequired := 1 + if !o.Cache { + minNodesRequired = 2 // Need at least 2 nodes when cache is disabled } - node := nodes[0] - nodeName := node.Name() - c.logger.Infof("using node %s for soc test", nodeName) + if len(nodes) < minNodesRequired { + return fmt.Errorf("soc test requires at least %d full node(s) (cache=%t)", minNodesRequired, o.Cache) + } + + uploadNode := nodes[0] + uploadNodeName := uploadNode.Name() + c.logger.Infof("using node %s for soc upload", uploadNodeName) owner := hex.EncodeToString(ownerBytes) id := hex.EncodeToString(idBytes) sig := hex.EncodeToString(signatureBytes) - batchID, err := node.GetOrCreateMutableBatch(ctx, o.PostageTTL, o.PostageDepth, o.PostageLabel) + batchID, err := uploadNode.GetOrCreateMutableBatch(ctx, o.PostageTTL, o.PostageDepth, o.PostageLabel) if err != nil { - return fmt.Errorf("node %s: batch id %w", nodeName, err) + return fmt.Errorf("node %s: batch id %w", uploadNodeName, err) } - c.logger.Infof("node %s: batch id %s", nodeName, batchID) - c.logger.Infof("soc: submitting soc chunk %s to node %s", sch.Address().String(), nodeName) + c.logger.Infof("node %s: batch id %s", uploadNodeName, batchID) + c.logger.Infof("soc: submitting soc chunk %s to node %s", sch.Address().String(), uploadNodeName) c.logger.Infof("soc: owner %s", owner) c.logger.Infof("soc: id %s", id) c.logger.Infof("soc: sig %s", sig) - ref, err := node.UploadSOC(ctx, owner, id, sig, ch.Data(), batchID) + socOptions := &api.SOCOptions{ + RLevel: &o.UploadRLevel, + } + + ref, err := uploadNode.UploadSOC(ctx, owner, id, sig, ch.Data(), batchID, socOptions) if err != nil { - return fmt.Errorf("node %s: upload soc chunk %w", nodeName, err) + return fmt.Errorf("node %s: upload soc chunk %w", uploadNodeName, err) } - c.logger.Infof("soc: chunk uploaded to node %s", nodeName) + c.logger.Infof("soc: chunk uploaded to node %s, reference %s", uploadNodeName, ref.String()) - retrieved, err := node.DownloadChunk(ctx, ref, "", nil) + retrieved, err := uploadNode.DownloadChunk(ctx, ref, "", nil) if err != nil { - return fmt.Errorf("node %s: download soc chunk %w", nodeName, err) + return fmt.Errorf("node %s: download soc chunk %w", uploadNodeName, err) } - c.logger.Infof("soc: chunk retrieved from node %s", nodeName) + c.logger.Infof("soc: original chunk retrieved from node %s", uploadNodeName) if !bytes.Equal(retrieved, chunkData) { return errors.New("soc: retrieved chunk data does NOT match soc chunk") } + // Select node for replica downloads + downloadNode := uploadNode + downloadNodeName := uploadNodeName + if !o.Cache && len(nodes) > 1 { + downloadNode = nodes[1] // Use different node for replica downloads when cache is disabled + downloadNodeName = downloadNode.Name() + c.logger.Infof("using node %s for replica downloads (different from upload node)", downloadNodeName) + } + + replicaErrs := c.testReplicaRetrieval(ctx, downloadNode, downloadNodeName, ref, chunkData, o) + + return replicaErrs +} + +// downloadReplicaWithRetry downloads a replica chunk with retry logic when cache is disabled +func (c *Check) downloadReplicaWithRetry(ctx context.Context, node *bee.Client, nodeName string, addr swarm.Address, chunkNumber int, opts Options) ([]byte, error) { + // If Cache is false, retry downloading replicas to allow time for chunks to be pushed to peers + maxRetries := 1 + retryDelay := 0 * time.Second + if !opts.Cache { + maxRetries = 5 + retryDelay = 1 * time.Second + } + + var retrievedReplica []byte + var err error + + for attempt := 0; attempt < maxRetries; attempt++ { + if attempt > 0 { + c.logger.Infof("node %s: retrying download of replica chunk %d (attempt %d/%d) after %v", + nodeName, chunkNumber, attempt+1, maxRetries, retryDelay) + time.Sleep(retryDelay) + } + + retrievedReplica, err = node.DownloadChunk(ctx, addr, "", &api.DownloadOptions{ + Cache: &opts.Cache, + }) + if err == nil { + break // Success, exit retry loop + } + + if attempt < maxRetries-1 && !opts.Cache { + c.logger.Infof("node %s: download soc replica chunk %d failed (attempt %d/%d): %v (address: %s)", + nodeName, chunkNumber, attempt+1, maxRetries, err, addr.String()) + } + } + + return retrievedReplica, err +} + +// testReplicaRetrieval tests replica retrieval with configurable redundancy levels and automatic error handling +func (c *Check) testReplicaRetrieval(ctx context.Context, node *bee.Client, nodeName string, ref swarm.Address, originalChunkData []byte, opts Options) error { + countTotal, countGood, countBad := 0, 0, 0 + + replicaIter := combinator.IterateReplicaAddresses(ref, int(opts.DownloadRLevel)) + var replicaErrors []error + + uploadExpected := opts.UploadRLevel.GetReplicaCount() + downloadExpected := opts.DownloadRLevel.GetReplicaCount() + + expectedSuccesses := min(uploadExpected, downloadExpected) + expectedFailures := downloadExpected - expectedSuccesses + + cacheMode := "from cache" + if !opts.Cache { + cacheMode = "from network" + } + c.logger.Infof("soc: testing replica retrieval with upload level %s (%d replicas) and download level %s (%d replicas to check) %s", + redundancyLevelName(opts.UploadRLevel), uploadExpected, + redundancyLevelName(opts.DownloadRLevel), downloadExpected, cacheMode) + c.logger.Infof("soc: expecting %d successful retrievals and %d failures", expectedSuccesses, expectedFailures) + + for addr := range replicaIter { + countTotal++ + if addr.Equal(ref) { + c.logger.Errorf("found original chunk address among replicas on position %d", countTotal) + continue + } + + retrievedReplica, err := c.downloadReplicaWithRetry(ctx, node, nodeName, addr, countTotal, opts) + + if err != nil { + countBad++ + retries := 1 + if !opts.Cache { + retries = 5 + } + c.logger.Infof("node %s: download soc replica chunk %d failed after %d attempts: %v (address: %s)", + nodeName, countTotal, retries, err, addr.String()) + replicaErrors = append(replicaErrors, err) + } else { + countGood++ + c.logger.Infof("soc: replica chunk %d (%s) retrieved successfully from node %s", + countTotal, addr.String(), nodeName) + + if !bytes.Equal(retrievedReplica, originalChunkData) { + return fmt.Errorf("soc: retrieved replica chunk %d data does NOT match original soc chunk", countTotal) + } + } + } + + c.logger.Infof("soc: replica retrieval summary for node %s: total=%d, successful=%d, failed=%d", + nodeName, countTotal, countGood, countBad) + + // Validate total attempts + if countTotal != downloadExpected { + return fmt.Errorf("soc: expected to check %d replicas (download level %s), but checked %d", + downloadExpected, redundancyLevelName(opts.DownloadRLevel), countTotal) + } + + // Validate expected vs actual results + if countGood != expectedSuccesses { + return fmt.Errorf("soc: expected %d successful retrievals but got %d (upload level %s provides %d replicas)", + expectedSuccesses, countGood, redundancyLevelName(opts.UploadRLevel), uploadExpected) + } + + if countBad != expectedFailures { + return fmt.Errorf("soc: expected %d failed retrievals but got %d (difference between %d download attempts and %d available replicas)", + expectedFailures, countBad, downloadExpected, uploadExpected) + } + + // Validate that replica errors are the expected type when we have failures + if expectedFailures > 0 { + if err := validateReplicaErrors(replicaErrors, expectedFailures); err != nil { + return fmt.Errorf("soc: replica error validation failed: %w", err) + } + c.logger.Infof("soc: validated %d replica errors are expected type (HTTP 500 'read chunk failed')", expectedFailures) + } + + // Log success + if expectedFailures > 0 { + c.logger.Infof("soc: test passed with expected pattern: %d/%d successful retrievals (upload level %s < download level %s)", + countGood, countTotal, redundancyLevelName(opts.UploadRLevel), redundancyLevelName(opts.DownloadRLevel)) + } else { + c.logger.Infof("soc: test passed with all %d replicas retrieved successfully (upload level %s >= download level %s)", + countGood, redundancyLevelName(opts.UploadRLevel), redundancyLevelName(opts.DownloadRLevel)) + } + + return nil +} + +// isExpectedReplicaError checks if an error is the expected "read chunk failed" HTTP 500 error +func isExpectedReplicaError(err error) bool { + if err == nil { + return false + } + errStr := err.Error() + // Check for the pattern: response message "read chunk failed": status: 500 Internal Server Error + return strings.Contains(errStr, `response message "read chunk failed"`) && + strings.Contains(errStr, "500 Internal Server Error") +} + +// validateReplicaErrors checks that all replica errors are the expected type +func validateReplicaErrors(errors []error, expectedCount int) error { + if len(errors) != expectedCount { + return fmt.Errorf("expected %d replica errors, got %d", expectedCount, len(errors)) + } + + for i, err := range errors { + if !isExpectedReplicaError(err) { + return fmt.Errorf("replica error %d is not expected type: %w", i+1, err) + } + } return nil } +// redundancyLevelName returns a human-readable name for redundancy level +func redundancyLevelName(level redundancy.Level) string { + switch level { + case redundancy.NONE: + return "NONE" + case redundancy.MEDIUM: + return "MEDIUM" + case redundancy.STRONG: + return "STRONG" + case redundancy.INSANE: + return "INSANE" + case redundancy.PARANOID: + return "PARANOID" + default: + return fmt.Sprintf("UNKNOWN(%d)", level) + } +} + func randomID() ([]byte, error) { key := make([]byte, 32) diff --git a/pkg/config/check.go b/pkg/config/check.go index 13009b50c..0f17b5faa 100644 --- a/pkg/config/check.go +++ b/pkg/config/check.go @@ -6,6 +6,7 @@ import ( "reflect" "time" + fredundancy "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/beekeeper/pkg/beekeeper" "github.com/ethersphere/beekeeper/pkg/check/act" "github.com/ethersphere/beekeeper/pkg/check/balances" @@ -450,11 +451,14 @@ var Checks = map[string]CheckType{ NewAction: soc.NewCheck, NewOptions: func(checkGlobalConfig CheckGlobalConfig, check Check) (any, error) { checkOpts := new(struct { - GasPrice *string `yaml:"gas-price"` - PostageTTL *time.Duration `yaml:"postage-ttl"` - PostageDepth *uint64 `yaml:"postage-depth"` - PostageLabel *string `yaml:"postage-label"` - RequestTimeout *time.Duration `yaml:"request-timeout"` + GasPrice *string `yaml:"gas-price"` + PostageTTL *time.Duration `yaml:"postage-ttl"` + PostageDepth *uint64 `yaml:"postage-depth"` + PostageLabel *string `yaml:"postage-label"` + RequestTimeout *time.Duration `yaml:"request-timeout"` + UploadRLevel *fredundancy.Level `yaml:"upload-r-level"` + DownloadRLevel *fredundancy.Level `yaml:"download-r-level"` + Cache *bool `yaml:"cache"` }) if err := check.Options.Decode(checkOpts); err != nil { return nil, fmt.Errorf("decoding check %s options: %w", check.Type, err)