diff --git a/mod/chain-spec/go.mod b/mod/chain-spec/go.mod index ee6ebc0169..7ed953b767 100644 --- a/mod/chain-spec/go.mod +++ b/mod/chain-spec/go.mod @@ -3,13 +3,24 @@ module github.com/berachain/beacon-kit/mod/chain-spec go 1.23.0 require ( + github.com/berachain/beacon-kit/mod/errors v0.0.0-20240610210054-bfdc14c4013c github.com/berachain/beacon-kit/mod/primitives v0.0.0-20240911165923-82f71ec86570 github.com/stretchr/testify v1.9.0 ) require ( + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/getsentry/sentry-go v0.28.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/mod/chain-spec/go.sum b/mod/chain-spec/go.sum index 5f2efaee22..53f19deb43 100644 --- a/mod/chain-spec/go.sum +++ b/mod/chain-spec/go.sum @@ -1,20 +1,73 @@ +github.com/berachain/beacon-kit/mod/errors v0.0.0-20240610210054-bfdc14c4013c h1:rPoD2zVkIzuMC4R/XMuwx6eanJL8ccu37sLro+eIj3Y= +github.com/berachain/beacon-kit/mod/errors v0.0.0-20240610210054-bfdc14c4013c/go.mod h1:xgngH5/PYbyW+YDEmRhbBy3V333GXsNWF4DAkjYCmfs= github.com/berachain/beacon-kit/mod/primitives v0.0.0-20240911165923-82f71ec86570 h1:w0Gkg31VQRFDv0EJjYgVtlpza7kSaJq7U28zxZjfZeE= github.com/berachain/beacon-kit/mod/primitives v0.0.0-20240911165923-82f71ec86570/go.mod h1:Mrq1qol8vbkgZp2IMPFwngg75qE3k9IvT2MouBEhuus= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= +github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +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/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +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/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/mod/chain-spec/pkg/chain/chain_spec.go b/mod/chain-spec/pkg/chain/chain_spec.go index 5d65f45e2c..7aa3da1c1c 100644 --- a/mod/chain-spec/pkg/chain/chain_spec.go +++ b/mod/chain-spec/pkg/chain/chain_spec.go @@ -183,6 +183,17 @@ type Spec[ // GetCometBFTConfigForSlot retrieves the CometBFT config for a specific // slot. GetCometBFTConfigForSlot(slot SlotT) CometBFTConfigT + + // Berachain Values + + // EVMInflationAddress returns the address on the EVM which will receive + // the inflation amount of native EVM balance through a withdrawal every + // block. + EVMInflationAddress() ExecutionAddressT + + // EVMInflationPerBlock returns the amount of native EVM balance (in Gwei) + // to be minted to the EVMInflationAddress via a withdrawal every block. + EVMInflationPerBlock() uint64 } // chainSpec is a concrete implementation of the ChainSpec interface, holding @@ -207,14 +218,29 @@ func NewChainSpec[ CometBFTConfigT any, ](data SpecData[ DomainTypeT, EpochT, ExecutionAddressT, SlotT, CometBFTConfigT, -]) Spec[ +]) (Spec[ DomainTypeT, EpochT, ExecutionAddressT, SlotT, CometBFTConfigT, -] { - return &chainSpec[ +], error) { + c := &chainSpec[ DomainTypeT, EpochT, ExecutionAddressT, SlotT, CometBFTConfigT, ]{ Data: data, } + return c, c.validate() +} + +// validate ensures that the chain spec is valid, returning error if it is not. +func (c *chainSpec[ + DomainTypeT, EpochT, ExecutionAddressT, SlotT, CometBFTConfigT, +]) validate() error { + if c.MaxWithdrawalsPerPayload() <= 1 { + return ErrInsufficientMaxWithdrawalsPerPayload + } + + // EVM Inflation values can be zero or non-zero, no validation needed. + + // TODO: Add more validation rules here. + return nil } // MinDepositAmount returns the minimum deposit amount required. @@ -476,3 +502,19 @@ func (c chainSpec[ ]) GetCometBFTConfigForSlot(_ SlotT) CometBFTConfigT { return c.Data.CometValues } + +// EVMInflationAddress returns the address on the EVM which will receive the +// inflation amount of native EVM balance through a withdrawal every block. +func (c chainSpec[ + DomainTypeT, EpochT, ExecutionAddressT, SlotT, CometBFTConfigT, +]) EVMInflationAddress() ExecutionAddressT { + return c.Data.EVMInflationAddress +} + +// EVMInflationPerBlock returns the amount of native EVM balance (in Gwei) to +// be minted to the EVMInflationAddress via a withdrawal every block. +func (c chainSpec[ + DomainTypeT, EpochT, ExecutionAddressT, SlotT, CometBFTConfigT, +]) EVMInflationPerBlock() uint64 { + return c.Data.EVMInflationPerBlock +} diff --git a/mod/chain-spec/pkg/chain/data.go b/mod/chain-spec/pkg/chain/data.go index 3c3ae2a637..308e563906 100644 --- a/mod/chain-spec/pkg/chain/data.go +++ b/mod/chain-spec/pkg/chain/data.go @@ -144,6 +144,15 @@ type SpecData[ // KZGCommitmentInclusionProofDepth is the depth of the KZG inclusion proof. KZGCommitmentInclusionProofDepth uint64 `mapstructure:"kzg-commitment-inclusion-proof-depth"` - // CometValues + // Comet Values CometValues CometBFTConfigT `mapstructure:"comet-bft-config"` + + // Berachain Values + // + // EVMInflationAddress is the address on the EVM which will receive the + // inflation amount of native EVM balance through a withdrawal every block. + EVMInflationAddress ExecutionAddressT `mapstructure:"evm-inflation-address"` + // EVMInflationPerBlock is the amount of native EVM balance (in Gwei) to be + // minted to the EVMInflationAddress via a withdrawal every block. + EVMInflationPerBlock uint64 `mapstructure:"evm-inflation-per-block"` } diff --git a/mod/chain-spec/pkg/chain/errors.go b/mod/chain-spec/pkg/chain/errors.go new file mode 100644 index 0000000000..6dc55f59ef --- /dev/null +++ b/mod/chain-spec/pkg/chain/errors.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package chain + +import "github.com/berachain/beacon-kit/mod/errors" + +var ( + // ErrInsufficientMaxWithdrawalsPerPayload is returned when the max + // withdrawals per payload less than 2. Must allow at least one for the EVM + // inflation withdrawal, and at least one more for a validator withdrawal + // per block. + ErrInsufficientMaxWithdrawalsPerPayload = errors.New( + "max withdrawals per payload must be greater than 1") +) diff --git a/mod/chain-spec/pkg/chain/helpers_test.go b/mod/chain-spec/pkg/chain/helpers_test.go index d0d97a5b62..1de79f616c 100644 --- a/mod/chain-spec/pkg/chain/helpers_test.go +++ b/mod/chain-spec/pkg/chain/helpers_test.go @@ -37,8 +37,10 @@ type ( cometBFTConfig struct{} ) +// TODO: Add setupValid, setupInvalid functions and use in each test. + // Create an instance of chainSpec with test data. -var spec = chain.NewChainSpec( +var spec, _ = chain.NewChainSpec( chain.SpecData[ domainType, epoch, executionAddress, slot, cometBFTConfig, ]{ @@ -46,6 +48,7 @@ var spec = chain.NewChainSpec( ElectraForkEpoch: 10, SlotsPerEpoch: 32, MinEpochsForBlobsSidecarsRequest: 5, + MaxWithdrawalsPerPayload: 2, }, ) diff --git a/mod/config/pkg/spec/base.go b/mod/config/pkg/spec/base.go new file mode 100644 index 0000000000..8f0ca9dc57 --- /dev/null +++ b/mod/config/pkg/spec/base.go @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package spec + +import ( + "github.com/berachain/beacon-kit/mod/chain-spec/pkg/chain" + "github.com/berachain/beacon-kit/mod/primitives/pkg/common" + "github.com/berachain/beacon-kit/mod/primitives/pkg/crypto" + "github.com/berachain/beacon-kit/mod/primitives/pkg/math" + cmttypes "github.com/cometbft/cometbft/types" +) + +const ( + // Default DepositContractAddress is the default address of the pre-deployed + // beacon deposit contract. + DefaultDepositContractAddress = "0x4242424242424242424242424242424242424242" +) + +// BaseSpec returns a chain spec with default values. +// +//nolint:mnd // bet. +func BaseSpec() chain.SpecData[ + common.DomainType, + math.Epoch, + common.ExecutionAddress, + math.Slot, + any, +] { + cmtConsensusParams := cmttypes.DefaultConsensusParams() + cmtConsensusParams.Validator.PubKeyTypes = []string{crypto.CometBLSType} + + return chain.SpecData[ + common.DomainType, + math.Epoch, + common.ExecutionAddress, + math.Slot, + any, + ]{ + // Gwei value constants. + MinDepositAmount: 1e9, + MaxEffectiveBalance: 32e9, + EjectionBalance: 16e9, + EffectiveBalanceIncrement: 1e9, + // Time parameters constants. + SlotsPerEpoch: 32, + MinEpochsToInactivityPenalty: 4, + SlotsPerHistoricalRoot: 8, + // Signature domains. + DomainTypeProposer: common.DomainType{ + 0x00, 0x00, 0x00, 0x00, + }, + DomainTypeAttester: common.DomainType{ + 0x01, 0x00, 0x00, 0x00, + }, + DomainTypeRandao: common.DomainType{ + 0x02, 0x00, 0x00, 0x00, + }, + DomainTypeDeposit: common.DomainType{ + 0x03, 0x00, 0x00, 0x00, + }, + DomainTypeVoluntaryExit: common.DomainType{ + 0x04, 0x00, 0x00, 0x00, + }, + DomainTypeSelectionProof: common.DomainType{ + 0x05, 0x00, 0x00, 0x00, + }, + DomainTypeAggregateAndProof: common.DomainType{ + 0x06, 0x00, 0x00, 0x00, + }, + DomainTypeApplicationMask: common.DomainType{ + 0x00, 0x00, 0x00, 0x01, + }, + // Eth1-related values. + DepositContractAddress: common.NewExecutionAddressFromHex( + DefaultDepositContractAddress, + ), + DepositEth1ChainID: 1, + Eth1FollowDistance: 1, + TargetSecondsPerEth1Block: 3, + // Fork-related values. + DenebPlusForkEpoch: 9999999999999998, + ElectraForkEpoch: 9999999999999999, + // State list length constants. + EpochsPerHistoricalVector: 8, + EpochsPerSlashingsVector: 8, + HistoricalRootsLimit: 8, + ValidatorRegistryLimit: 1099511627776, + // Max operations per block constants. + MaxDepositsPerBlock: 16, + // Slashing + ProportionalSlashingMultiplier: 1, + // Capella values. + MaxWithdrawalsPerPayload: 16, + MaxValidatorsPerWithdrawalsSweep: 1 << 14, + // Deneb values. + MinEpochsForBlobsSidecarsRequest: 4096, + MaxBlobCommitmentsPerBlock: 16, + MaxBlobsPerBlock: 6, + FieldElementsPerBlob: 4096, + BytesPerBlob: 131072, + KZGCommitmentInclusionProofDepth: 17, + // Comet values. + CometValues: cmtConsensusParams, + } +} diff --git a/mod/config/pkg/spec/betnet.go b/mod/config/pkg/spec/betnet.go index edaef1254c..289b663ca6 100644 --- a/mod/config/pkg/spec/betnet.go +++ b/mod/config/pkg/spec/betnet.go @@ -27,13 +27,13 @@ import ( ) // BetnetChainSpec is the ChainSpec for the localnet. -func BetnetChainSpec() chain.Spec[ +func BetnetChainSpec() (chain.Spec[ common.DomainType, math.Epoch, common.ExecutionAddress, math.Slot, any, -] { +], error) { testnetSpec := BaseSpec() testnetSpec.DepositEth1ChainID = BetnetEth1ChainID return chain.NewChainSpec(testnetSpec) diff --git a/mod/config/pkg/spec/boonet.go b/mod/config/pkg/spec/boonet.go index 83213baba2..95976f79fa 100644 --- a/mod/config/pkg/spec/boonet.go +++ b/mod/config/pkg/spec/boonet.go @@ -27,14 +27,34 @@ import ( ) // BoonetChainSpec is the ChainSpec for the localnet. -func BoonetChainSpec() chain.Spec[ +func BoonetChainSpec() (chain.Spec[ common.DomainType, math.Epoch, common.ExecutionAddress, math.Slot, any, -] { +], error) { testnetSpec := BaseSpec() + + // Chain ID is 80000. testnetSpec.DepositEth1ChainID = BoonetEth1ChainID + + // BGT contract address. + testnetSpec.EVMInflationAddress = common.NewExecutionAddressFromHex( + "0x289274787bAF083C15A45a174b7a8e44F0720660", + ) + + // 2.5 BERA per block minting. + // + // TODO: determine correct value for boonet upgrade. + testnetSpec.EVMInflationPerBlock = 2.5e9 + + // MaxValidatorsPerWithdrawalsSweep is 43 because we expect at least 46 + // validators in the total validators set. We choose a prime number smaller + // than the minimum amount of total validators possible. + // + // TODO: Determine correct value for boonet upgrade. + testnetSpec.MaxValidatorsPerWithdrawalsSweep = 43 + return chain.NewChainSpec(testnetSpec) } diff --git a/mod/config/pkg/spec/constants.go b/mod/config/pkg/spec/chain_ids.go similarity index 86% rename from mod/config/pkg/spec/constants.go rename to mod/config/pkg/spec/chain_ids.go index 2b0cea5548..cf978fdbd2 100644 --- a/mod/config/pkg/spec/constants.go +++ b/mod/config/pkg/spec/chain_ids.go @@ -22,13 +22,13 @@ package spec const ( - // BoonetEth1ChainID is the chain ID for the local devnet. + // BoonetEth1ChainID is the chain ID for a local devnet. BoonetEth1ChainID uint64 = 80000 - // BetnetEth1ChainID is the chain ID for the local devnet. + // BetnetEth1ChainID is the chain ID for a local devnet. BetnetEth1ChainID uint64 = 80088 - // DevnetEth1ChainID is the chain ID for the local devnet. + // DevnetEth1ChainID is the chain ID for a local devnet. DevnetEth1ChainID uint64 = 80087 // TestnetEth1ChainID is the chain ID for the bArtio testnet. diff --git a/mod/config/pkg/spec/devnet.go b/mod/config/pkg/spec/devnet.go index 9e189b1d83..66c575ea2c 100644 --- a/mod/config/pkg/spec/devnet.go +++ b/mod/config/pkg/spec/devnet.go @@ -26,15 +26,29 @@ import ( "github.com/berachain/beacon-kit/mod/primitives/pkg/math" ) -// DevnetChainSpec is the ChainSpec for the localnet. -func DevnetChainSpec() chain.Spec[ +const ( + // DevnetEVMInflationAddress is the address of the EVM inflation contract. + DevnetEVMInflationAddress = "0x6942069420694206942069420694206942069420" + + // DevnetEVMInflationPerBlock is the amount of native EVM balance (in units + // of Gwei) to be minted per EL block. + DevnetEVMInflationPerBlock = 10e9 +) + +// DevnetChainSpec is the ChainSpec for the localnet. Also used for e2e tests +// in the kurtosis network. +func DevnetChainSpec() (chain.Spec[ common.DomainType, math.Epoch, common.ExecutionAddress, math.Slot, any, -] { - testnetSpec := BaseSpec() - testnetSpec.DepositEth1ChainID = DevnetEth1ChainID - return chain.NewChainSpec(testnetSpec) +], error) { + devnetSpec := BaseSpec() + devnetSpec.DepositEth1ChainID = DevnetEth1ChainID + devnetSpec.EVMInflationAddress = common.NewExecutionAddressFromHex( + DevnetEVMInflationAddress, + ) + devnetSpec.EVMInflationPerBlock = DevnetEVMInflationPerBlock + return chain.NewChainSpec(devnetSpec) } diff --git a/mod/config/pkg/spec/special_cases.go b/mod/config/pkg/spec/special_cases.go index b21549f9c4..236888eb7c 100644 --- a/mod/config/pkg/spec/special_cases.go +++ b/mod/config/pkg/spec/special_cases.go @@ -20,19 +20,19 @@ package spec -import "math" - // Special cased Bartio for some ad-hoc handling due to the way // some bugs were handled on Bartio. To be removed. const ( - BartioChainID uint64 = 80084 + BartioChainID = TestnetEth1ChainID //nolint:lll // temporary. BArtioValRoot = "0x9147586693b6e8faa837715c0f3071c2000045b54233901c2e7871b15872bc43" ) -const ( // Planned hard-fork upgrades on boonet. +// Planned hard-fork upgrades on boonet. To be removed. +const ( BoonetFork1Height uint64 = 69420 - BoonetFork2Height uint64 = math.MaxUint64 + // TODO: modify this to be the actual fork height. Avoid overflow. + BoonetFork2Height uint64 = 99999999999999 ) diff --git a/mod/config/pkg/spec/testnet.go b/mod/config/pkg/spec/testnet.go index a9d2970506..aca8c25f8e 100644 --- a/mod/config/pkg/spec/testnet.go +++ b/mod/config/pkg/spec/testnet.go @@ -23,105 +23,18 @@ package spec import ( "github.com/berachain/beacon-kit/mod/chain-spec/pkg/chain" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" - "github.com/berachain/beacon-kit/mod/primitives/pkg/crypto" "github.com/berachain/beacon-kit/mod/primitives/pkg/math" - cmttypes "github.com/cometbft/cometbft/types" ) -// TestnetChainSpec is the ChainSpec for the localnet. -func TestnetChainSpec() chain.Spec[ +// TestnetChainSpec is the ChainSpec for the bArtio testnet. +func TestnetChainSpec() (chain.Spec[ common.DomainType, math.Epoch, common.ExecutionAddress, math.Slot, any, -] { +], error) { testnetSpec := BaseSpec() testnetSpec.DepositEth1ChainID = TestnetEth1ChainID return chain.NewChainSpec(testnetSpec) } - -//nolint:mnd // bet. -func BaseSpec() chain.SpecData[ - common.DomainType, - math.Epoch, - common.ExecutionAddress, - math.Slot, - any, -] { - cmtConsensusParams := cmttypes.DefaultConsensusParams() - cmtConsensusParams.Validator.PubKeyTypes = []string{crypto.CometBLSType} - - return chain.SpecData[ - common.DomainType, - math.Epoch, - common.ExecutionAddress, - math.Slot, - any, - ]{ - // // Gwei value constants. - MinDepositAmount: uint64(1e9), - MaxEffectiveBalance: uint64(32e9), - EjectionBalance: uint64(16e9), - EffectiveBalanceIncrement: uint64(1e9), - // Time parameters constants. - SlotsPerEpoch: 32, - MinEpochsToInactivityPenalty: 4, - SlotsPerHistoricalRoot: 8, - // Signature domains. - DomainTypeProposer: common.DomainType{ - 0x00, 0x00, 0x00, 0x00, - }, - DomainTypeAttester: common.DomainType{ - 0x01, 0x00, 0x00, 0x00, - }, - DomainTypeRandao: common.DomainType{ - 0x02, 0x00, 0x00, 0x00, - }, - DomainTypeDeposit: common.DomainType{ - 0x03, 0x00, 0x00, 0x00, - }, - DomainTypeVoluntaryExit: common.DomainType{ - 0x04, 0x00, 0x00, 0x00, - }, - DomainTypeSelectionProof: common.DomainType{ - 0x05, 0x00, 0x00, 0x00, - }, - DomainTypeAggregateAndProof: common.DomainType{ - 0x06, 0x00, 0x00, 0x00, - }, - DomainTypeApplicationMask: common.DomainType{ - 0x00, 0x00, 0x00, 0x01, - }, - // Eth1-related values. - DepositContractAddress: common.NewExecutionAddressFromHex( - "0x4242424242424242424242424242424242424242", - ), - DepositEth1ChainID: uint64(80084), - Eth1FollowDistance: 1, - TargetSecondsPerEth1Block: 3, - // Fork-related values. - DenebPlusForkEpoch: 9999999999999998, - ElectraForkEpoch: 9999999999999999, - // State list length constants. - EpochsPerHistoricalVector: 8, - EpochsPerSlashingsVector: 8, - HistoricalRootsLimit: 8, - ValidatorRegistryLimit: 1099511627776, - // Max operations per block constants. - MaxDepositsPerBlock: 16, - // Slashing - ProportionalSlashingMultiplier: 1, - // Capella values. - MaxWithdrawalsPerPayload: 16, - MaxValidatorsPerWithdrawalsSweep: 1 << 14, - // Deneb values. - MinEpochsForBlobsSidecarsRequest: 4096, - MaxBlobCommitmentsPerBlock: 16, - MaxBlobsPerBlock: 6, - FieldElementsPerBlob: 4096, - BytesPerBlob: 131072, - KZGCommitmentInclusionProofDepth: 17, - CometValues: cmtConsensusParams, - } -} diff --git a/mod/da/pkg/store/pruner_test.go b/mod/da/pkg/store/pruner_test.go index 6b3b12ee56..506b041780 100644 --- a/mod/da/pkg/store/pruner_test.go +++ b/mod/da/pkg/store/pruner_test.go @@ -107,18 +107,20 @@ func TestBuildPruneRangeFn(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cs := chain.NewChainSpec( + cs, err := chain.NewChainSpec( chain.SpecData[ bytes.B4, math.U64, common.ExecutionAddress, math.U64, any, ]{ SlotsPerEpoch: tt.slotsPerEpoch, MinEpochsForBlobsSidecarsRequest: tt.minEpochs, + MaxWithdrawalsPerPayload: 2, }, ) + require.NoError(t, err) pruneFn := store.BuildPruneRangeFn[MockBeaconBlock]( cs, ) - event := async.NewEvent[MockBeaconBlock]( + event := async.NewEvent( context.Background(), async.EventID("mock"), MockBeaconBlock{ diff --git a/mod/node-api/backend/mocks/beacon_state.mock.go b/mod/node-api/backend/mocks/beacon_state.mock.go index ef3004fd8d..99c0d2c4b9 100644 --- a/mod/node-api/backend/mocks/beacon_state.mock.go +++ b/mod/node-api/backend/mocks/beacon_state.mock.go @@ -26,6 +26,53 @@ func (_m *BeaconState[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, Fo return &BeaconState_Expecter[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT]{mock: &_m.Mock} } +// EVMInflationWithdrawal provides a mock function with given fields: +func (_m *BeaconState[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT]) EVMInflationWithdrawal() WithdrawalT { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for EVMInflationWithdrawal") + } + + var r0 WithdrawalT + if rf, ok := ret.Get(0).(func() WithdrawalT); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(WithdrawalT) + } + } + + return r0 +} + +// BeaconState_EVMInflationWithdrawal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EVMInflationWithdrawal' +type BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT any, Eth1DataT any, ExecutionPayloadHeaderT any, ForkT any, ValidatorT any, ValidatorsT any, WithdrawalT any] struct { + *mock.Call +} + +// EVMInflationWithdrawal is a helper method to define mock.On call +func (_e *BeaconState_Expecter[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT]) EVMInflationWithdrawal() *BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT] { + return &BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT]{Call: _e.mock.On("EVMInflationWithdrawal")} +} + +func (_c *BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT]) Run(run func()) *BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT] { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT]) Return(_a0 WithdrawalT) *BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT] { + _c.Call.Return(_a0) + return _c +} + +func (_c *BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT]) RunAndReturn(run func() WithdrawalT) *BeaconState_EVMInflationWithdrawal_Call[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT] { + _c.Call.Return(run) + return _c +} + // ExpectedWithdrawals provides a mock function with given fields: func (_m *BeaconState[BeaconBlockHeaderT, Eth1DataT, ExecutionPayloadHeaderT, ForkT, ValidatorT, ValidatorsT, WithdrawalT]) ExpectedWithdrawals() ([]WithdrawalT, error) { ret := _m.Called() diff --git a/mod/node-core/pkg/components/chain_spec.go b/mod/node-core/pkg/components/chain_spec.go index c70bb3b9fa..4bb7128ff8 100644 --- a/mod/node-core/pkg/components/chain_spec.go +++ b/mod/node-core/pkg/components/chain_spec.go @@ -28,28 +28,32 @@ import ( ) const ( - ChainSpecTypeEnvVar = "CHAIN_SPEC" - DevnetChainSpecType = "devnet" - BetnetChainSpecType = "betnet" - BoonetChainSpecType = "boonet" + ChainSpecTypeEnvVar = "CHAIN_SPEC" + DevnetChainSpecType = "devnet" + BetnetChainSpecType = "betnet" + BoonetChainSpecType = "boonet" + TestnetChainSpecType = "testnet" ) // ProvideChainSpec provides the chain spec based on the environment variable. -func ProvideChainSpec() common.ChainSpec { +func ProvideChainSpec() (common.ChainSpec, error) { // TODO: This is hood as fuck needs to be improved // but for now we ball to get CI unblocked. - specType := os.Getenv(ChainSpecTypeEnvVar) - var chainSpec common.ChainSpec - switch specType { + var ( + chainSpec common.ChainSpec + err error + ) + switch os.Getenv(ChainSpecTypeEnvVar) { case DevnetChainSpecType: - chainSpec = spec.DevnetChainSpec() + chainSpec, err = spec.DevnetChainSpec() case BetnetChainSpecType: - chainSpec = spec.BetnetChainSpec() + chainSpec, err = spec.BetnetChainSpec() case BoonetChainSpecType: - chainSpec = spec.BoonetChainSpec() + chainSpec, err = spec.BoonetChainSpec() + case TestnetChainSpecType: + fallthrough default: - chainSpec = spec.TestnetChainSpec() + chainSpec, err = spec.TestnetChainSpec() } - - return chainSpec + return chainSpec, err } diff --git a/mod/node-core/pkg/components/interfaces.go b/mod/node-core/pkg/components/interfaces.go index 025b4a05cd..6e7bfa811d 100644 --- a/mod/node-core/pkg/components/interfaces.go +++ b/mod/node-core/pkg/components/interfaces.go @@ -1083,6 +1083,7 @@ type ( // ReadOnlyWithdrawals only has read access to withdrawal methods. ReadOnlyWithdrawals[WithdrawalT any] interface { + EVMInflationWithdrawal() WithdrawalT ExpectedWithdrawals() ([]WithdrawalT, error) } ) diff --git a/mod/primitives/pkg/common/execution.go b/mod/primitives/pkg/common/execution.go index 3addc635fb..ece76afa56 100644 --- a/mod/primitives/pkg/common/execution.go +++ b/mod/primitives/pkg/common/execution.go @@ -21,6 +21,7 @@ package common import ( + "bytes" "encoding" "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" @@ -97,6 +98,11 @@ func NewExecutionAddressFromHex(input string) ExecutionAddress { return ExecutionAddress(hex.MustToBytes(input)) } +// Equals returns true if the two addresses are the same. +func (a ExecutionAddress) Equals(other ExecutionAddress) bool { + return bytes.Equal(a[:], other[:]) +} + // Hex converts an address to a hex string. func (a ExecutionAddress) Hex() string { return string(a.checksumHex()) } diff --git a/mod/state-transition/pkg/core/helpers_test.go b/mod/state-transition/pkg/core/core_test.go similarity index 70% rename from mod/state-transition/pkg/core/helpers_test.go rename to mod/state-transition/pkg/core/core_test.go index c92f1bc971..e4366ceca0 100644 --- a/mod/state-transition/pkg/core/helpers_test.go +++ b/mod/state-transition/pkg/core/core_test.go @@ -30,15 +30,19 @@ import ( "cosmossdk.io/store" "cosmossdk.io/store/metrics" storetypes "cosmossdk.io/store/types" + "github.com/berachain/beacon-kit/mod/chain-spec/pkg/chain" "github.com/berachain/beacon-kit/mod/consensus-types/pkg/types" engineprimitives "github.com/berachain/beacon-kit/mod/engine-primitives/pkg/engine-primitives" "github.com/berachain/beacon-kit/mod/log/pkg/noop" "github.com/berachain/beacon-kit/mod/node-core/pkg/components" nodemetrics "github.com/berachain/beacon-kit/mod/node-core/pkg/components/metrics" + "github.com/berachain/beacon-kit/mod/primitives/pkg/bytes" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" - "github.com/berachain/beacon-kit/mod/primitives/pkg/crypto" + cryptomocks "github.com/berachain/beacon-kit/mod/primitives/pkg/crypto/mocks" + "github.com/berachain/beacon-kit/mod/primitives/pkg/math" "github.com/berachain/beacon-kit/mod/primitives/pkg/transition" "github.com/berachain/beacon-kit/mod/state-transition/pkg/core" + "github.com/berachain/beacon-kit/mod/state-transition/pkg/core/mocks" statedb "github.com/berachain/beacon-kit/mod/state-transition/pkg/core/state" "github.com/berachain/beacon-kit/mod/storage/pkg/beacondb" "github.com/berachain/beacon-kit/mod/storage/pkg/db" @@ -46,6 +50,7 @@ import ( "github.com/berachain/beacon-kit/mod/storage/pkg/encoding" dbm "github.com/cosmos/cosmos-db" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -84,38 +89,8 @@ type ( *engineprimitives.Withdrawal, types.WithdrawalCredentials, ] -) -func createStateProcessor( - cs common.ChainSpec, - execEngine core.ExecutionEngine[ - *types.ExecutionPayload, - *types.ExecutionPayloadHeader, - engineprimitives.Withdrawals, - ], - ds *depositstore.KVStore[*types.Deposit], - signer crypto.BLSSigner, - fGetAddressFromPubKey func(crypto.BLSPubkey) ([]byte, error), -) *core.StateProcessor[ - *types.BeaconBlock, - *types.BeaconBlockBody, - *types.BeaconBlockHeader, - *TestBeaconStateT, - *transition.Context, - *types.Deposit, - *types.Eth1Data, - *types.ExecutionPayload, - *types.ExecutionPayloadHeader, - *types.Fork, - *types.ForkData, - *TestKVStoreT, - *types.Validator, - types.Validators, - *engineprimitives.Withdrawal, - engineprimitives.Withdrawals, - types.WithdrawalCredentials, -] { - return core.NewStateProcessor[ + TestStateProcessorT = core.StateProcessor[ *types.BeaconBlock, *types.BeaconBlockBody, *types.BeaconBlockHeader, @@ -133,16 +108,8 @@ func createStateProcessor( *engineprimitives.Withdrawal, engineprimitives.Withdrawals, types.WithdrawalCredentials, - ]( - noop.NewLogger[any](), - cs, - execEngine, - ds, - signer, - fGetAddressFromPubKey, - nodemetrics.NewNoOpTelemetrySink(), - ) -} + ] +) type testKVStoreService struct { ctx sdk.Context @@ -208,6 +175,110 @@ func initTestStores() ( nil } +func setupChain(t *testing.T, chainSpecType string) chain.Spec[ + bytes.B4, math.U64, common.ExecutionAddress, math.U64, any, +] { + t.Helper() + + t.Setenv(components.ChainSpecTypeEnvVar, chainSpecType) + cs, err := components.ProvideChainSpec() + require.NoError(t, err) + + return cs +} + +func setupState( + t *testing.T, cs chain.Spec[ + bytes.B4, math.U64, common.ExecutionAddress, math.U64, any, + ], +) ( + *TestStateProcessorT, + *TestBeaconStateT, + *depositstore.KVStore[*types.Deposit], + *transition.Context, +) { + t.Helper() + + execEngine := mocks.NewExecutionEngine[ + *types.ExecutionPayload, + *types.ExecutionPayloadHeader, + engineprimitives.Withdrawals, + ](t) + + mocksSigner := &cryptomocks.BLSSigner{} + mocksSigner.On( + "VerifySignature", + mock.Anything, mock.Anything, mock.Anything, + ).Return(nil) + + dummyProposerAddr := []byte{0xff} + + kvStore, depositStore, err := initTestStores() + require.NoError(t, err) + beaconState := new(TestBeaconStateT).NewFromDB(kvStore, cs) + + sp := core.NewStateProcessor[ + *types.BeaconBlock, + *types.BeaconBlockBody, + *types.BeaconBlockHeader, + *TestBeaconStateT, + *transition.Context, + *types.Deposit, + *types.Eth1Data, + *types.ExecutionPayload, + *types.ExecutionPayloadHeader, + *types.Fork, + *types.ForkData, + *TestKVStoreT, + *types.Validator, + types.Validators, + *engineprimitives.Withdrawal, + engineprimitives.Withdrawals, + types.WithdrawalCredentials, + ]( + noop.NewLogger[any](), + cs, + execEngine, + depositStore, + mocksSigner, + func(bytes.B48) ([]byte, error) { + return dummyProposerAddr, nil + }, + nodemetrics.NewNoOpTelemetrySink(), + ) + + ctx := &transition.Context{ + SkipPayloadVerification: true, + SkipValidateResult: true, + ProposerAddress: dummyProposerAddr, + } + + return sp, beaconState, depositStore, ctx +} + +func progressStateToSlot( + t *testing.T, + beaconState *TestBeaconStateT, + slot math.U64, +) { + t.Helper() + + if slot == math.U64(0) { + t.Fatal("for genesis slot, use InitializePreminedBeaconStateFromEth1") + } + + err := beaconState.SetSlot(slot) + require.NoError(t, err) + err = beaconState.SetLatestBlockHeader(types.NewBeaconBlockHeader( + slot, + math.U64(0), + common.Root{}, + common.Root{}, + common.Root{}, + )) + require.NoError(t, err) +} + func buildNextBlock( t *testing.T, beaconState *TestBeaconStateT, diff --git a/mod/state-transition/pkg/core/errors.go b/mod/state-transition/pkg/core/errors.go index 69015866b0..7659dbac0f 100644 --- a/mod/state-transition/pkg/core/errors.go +++ b/mod/state-transition/pkg/core/errors.go @@ -87,7 +87,22 @@ var ( // in a block exceeds the maximum allowed. ErrExceedMaximumWithdrawals = errors.New("exceeds maximum withdrawals") + // ErrZeroWithdrawals is returned when the number of withdrawals in a + // block is zero. At least the EVM inflation withdrawal is always expected. + ErrZeroWithdrawals = errors.New("zero withdrawals") + // ErrNumWithdrawalsMismatch is returned when the number of withdrawals // in a block does not match the expected value. ErrNumWithdrawalsMismatch = errors.New("number of withdrawals mismatch") + + // ErrFirstWithdrawalNotEVMInflation is returned when the first withdrawal + // in a block is not the EVM inflation withdrawal. + ErrFirstWithdrawalNotEVMInflation = errors.New( + "first withdrawal is not the EVM inflation withdrawal", + ) + + // ErrWithdrawalMismatch is returned when the withdrawals in a payload do + // not match the local state's expected value. + ErrWithdrawalMismatch = errors.New( + "withdrawal mismatch between local state and payload") ) diff --git a/mod/state-transition/pkg/core/interfaces.go b/mod/state-transition/pkg/core/interfaces.go index 6631e65374..c5ff805492 100644 --- a/mod/state-transition/pkg/core/interfaces.go +++ b/mod/state-transition/pkg/core/interfaces.go @@ -177,5 +177,6 @@ type ReadOnlyEth1Data[Eth1DataT, ExecutionPayloadHeaderT any] interface { // ReadOnlyWithdrawals only has read access to withdrawal methods. type ReadOnlyWithdrawals[WithdrawalT any] interface { + EVMInflationWithdrawal() WithdrawalT ExpectedWithdrawals() ([]WithdrawalT, error) } diff --git a/mod/state-transition/pkg/core/state/constants.go b/mod/state-transition/pkg/core/state/constants.go new file mode 100644 index 0000000000..6d66433f6b --- /dev/null +++ b/mod/state-transition/pkg/core/state/constants.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package state + +import ( + "math" +) + +const ( + // EVMInflationWithdrawalIndex is the fixed withdrawal index to be used for + // the EVM inflation withdrawal in every block. It should remain unused + // during processing. + EVMInflationWithdrawalIndex = math.MaxUint64 + + // EVMInflationWithdrawalValidatorIndex is the fixed validator index to be + // used for the EVM inflation withdrawal in every block. It should remain + // unused during processing. + EVMInflationWithdrawalValidatorIndex = math.MaxUint64 +) + +// Boonet special case for emergency minting of EVM tokens. TODO: remove with +// other special cases. +const ( + // EVMMintingAddress is the address at which we mint EVM tokens to. + EVMMintingAddress = "0x8a73D1380345942F1cb32541F1b19C40D8e6C94B" + + // EVMMintingAmount is the amount of EVM tokens to mint. + EVMMintingAmount uint64 = 530000000000000000 +) diff --git a/mod/state-transition/pkg/core/state/statedb.go b/mod/state-transition/pkg/core/state/statedb.go index bac38eea97..8a2d925fa3 100644 --- a/mod/state-transition/pkg/core/state/statedb.go +++ b/mod/state-transition/pkg/core/state/statedb.go @@ -27,19 +27,6 @@ import ( "github.com/berachain/beacon-kit/mod/primitives/pkg/math" ) -const ( - // EVMMintingSlot is the slot at which we force a single withdrawal to - // mint EVMMintingAmount EVM tokens to EVMMintingAddress. No other - // withdrawals are inserted at this slot. - EVMMintingSlot = spec.BoonetFork1Height - - // EVMMintingAddress is the address at which we mint EVM tokens to. - EVMMintingAddress = "0x8a73D1380345942F1cb32541F1b19C40D8e6C94B" - - // EVMMintingAmount is the amount of EVM tokens to mint. - EVMMintingAmount uint64 = 530000000000000000 -) - // StateDB is the underlying struct behind the BeaconState interface. // //nolint:revive // todo fix somehow @@ -200,7 +187,10 @@ func (s *StateDB[ // ExpectedWithdrawals as defined in the Ethereum 2.0 Specification: // https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#new-get_expected_withdrawals // -//nolint:lll,funlen +// NOTE: This function is modified from the spec to allow a fixed withdrawal +// (as the first withdrawal) used for EVM inflation. +// +//nolint:lll,funlen // TODO: Simplify when dropping special cases. func (s *StateDB[ _, _, _, _, _, _, ValidatorT, _, WithdrawalT, _, ]) ExpectedWithdrawals() ([]WithdrawalT, error) { @@ -209,6 +199,7 @@ func (s *StateDB[ balance math.Gwei withdrawalAddress common.ExecutionAddress withdrawals = make([]WithdrawalT, 0) + withdrawal WithdrawalT ) slot, err := s.GetSlot() @@ -216,9 +207,14 @@ func (s *StateDB[ return nil, err } - // Slot used to mint EVM tokens. - if slot.Unwrap() == EVMMintingSlot { - var withdrawal WithdrawalT + // Handle special cases wherever it's necessary + switch { + case s.cs.DepositEth1ChainID() == spec.BartioChainID: + // nothing special to do + + case s.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && + slot == math.U64(spec.BoonetFork1Height): + // Slot used to emergency mint EVM tokens on Boonet. withdrawals = append(withdrawals, withdrawal.New( 0, // NOT USED 0, // NOT USED @@ -226,6 +222,15 @@ func (s *StateDB[ math.Gwei(EVMMintingAmount), )) return withdrawals, nil + + case s.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && + slot < math.U64(spec.BoonetFork2Height): + // Boonet inherited the Bartio behaviour pre BoonetFork2Height + // nothing specific to do + + default: + // The first withdrawal is fixed to be the EVM inflation withdrawal. + withdrawals = append(withdrawals, s.EVMInflationWithdrawal()) } epoch := math.Epoch(slot.Unwrap() / s.cs.SlotsPerEpoch()) @@ -269,8 +274,6 @@ func (s *StateDB[ // Set the amount of the withdrawal depending on the balance of the // validator. - var withdrawal WithdrawalT - //nolint:gocritic // ok. if validator.IsFullyWithdrawable(balance, epoch) { withdrawals = append(withdrawals, withdrawal.New( @@ -309,8 +312,7 @@ func (s *StateDB[ } // Cap the number of withdrawals to the maximum allowed per payload. - //#nosec:G701 // won't overflow in practice. - if len(withdrawals) == int(s.cs.MaxWithdrawalsPerPayload()) { + if uint64(len(withdrawals)) == s.cs.MaxWithdrawalsPerPayload() { break } @@ -323,6 +325,22 @@ func (s *StateDB[ return withdrawals, nil } +// EVMInflationWithdrawal returns the withdrawal used for EVM balance inflation. +// +// NOTE: The withdrawal index and validator index are both set to 0 as they are +// not used during processing. +func (s *StateDB[ + _, _, _, _, _, _, _, _, WithdrawalT, _, +]) EVMInflationWithdrawal() WithdrawalT { + var withdrawal WithdrawalT + return withdrawal.New( + EVMInflationWithdrawalIndex, + EVMInflationWithdrawalValidatorIndex, + s.cs.EVMInflationAddress(), + math.Gwei(s.cs.EVMInflationPerBlock()), + ) +} + // GetMarshallable is the interface for the beacon store. // //nolint:funlen,gocognit // todo fix somehow diff --git a/mod/state-transition/pkg/core/state_processor_genesis.go b/mod/state-transition/pkg/core/state_processor_genesis.go index 082b94df2a..77947bc23c 100644 --- a/mod/state-transition/pkg/core/state_processor_genesis.go +++ b/mod/state-transition/pkg/core/state_processor_genesis.go @@ -63,7 +63,6 @@ func (sp *StateProcessor[ } // Eth1DepositIndex will be set in processDeposit - if err := st.SetEth1Data( eth1Data.New( common.Root{}, @@ -143,9 +142,7 @@ func (sp *StateProcessor[ return nil, err } - if err = st.SetNextWithdrawalValidatorIndex( - 0, - ); err != nil { + if err = st.SetNextWithdrawalValidatorIndex(0); err != nil { return nil, err } diff --git a/mod/state-transition/pkg/core/state_processor_genesis_test.go b/mod/state-transition/pkg/core/state_processor_genesis_test.go index a6e91a322c..b69e6333da 100644 --- a/mod/state-transition/pkg/core/state_processor_genesis_test.go +++ b/mod/state-transition/pkg/core/state_processor_genesis_test.go @@ -24,69 +24,45 @@ import ( "testing" "github.com/berachain/beacon-kit/mod/chain-spec/pkg/chain" - "github.com/berachain/beacon-kit/mod/config/pkg/spec" "github.com/berachain/beacon-kit/mod/consensus-types/pkg/types" - engineprimitives "github.com/berachain/beacon-kit/mod/engine-primitives/pkg/engine-primitives" - "github.com/berachain/beacon-kit/mod/primitives/pkg/bytes" + "github.com/berachain/beacon-kit/mod/node-core/pkg/components" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" "github.com/berachain/beacon-kit/mod/primitives/pkg/constants" - cryptomocks "github.com/berachain/beacon-kit/mod/primitives/pkg/crypto/mocks" "github.com/berachain/beacon-kit/mod/primitives/pkg/math" "github.com/berachain/beacon-kit/mod/primitives/pkg/version" - "github.com/berachain/beacon-kit/mod/state-transition/pkg/core/mocks" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestInitialize(t *testing.T) { - // Create state processor to test - cs := spec.BetnetChainSpec() - execEngine := mocks.NewExecutionEngine[ - *types.ExecutionPayload, - *types.ExecutionPayloadHeader, - engineprimitives.Withdrawals, - ](t) - mocksSigner := &cryptomocks.BLSSigner{} - - kvStore, depositStore, err := initTestStores() - require.NoError(t, err) - beaconState := new(TestBeaconStateT).NewFromDB(kvStore, cs) - - sp := createStateProcessor( - cs, - execEngine, - depositStore, - mocksSigner, - dummyProposerAddressVerifier, - ) + cs := setupChain(t, components.BetnetChainSpecType) + sp, st, _, _ := setupState(t, cs) - // create test input var ( deposits = []*types.Deposit{ { - Pubkey: [48]byte{0x01}, + Pubkey: [48]byte{0x00}, Amount: math.Gwei(cs.MaxEffectiveBalance()), - Index: uint64(0), + Index: 0, }, { - Pubkey: [48]byte{0x02}, + Pubkey: [48]byte{0x01}, Amount: math.Gwei(cs.MaxEffectiveBalance() / 2), - Index: uint64(1), + Index: 1, }, { - Pubkey: [48]byte{0x03}, + Pubkey: [48]byte{0x02}, Amount: math.Gwei(cs.EffectiveBalanceIncrement()), - Index: uint64(2), + Index: 2, }, { - Pubkey: [48]byte{0x04}, + Pubkey: [48]byte{0x03}, Amount: math.Gwei(2 * cs.MaxEffectiveBalance()), - Index: uint64(3), + Index: 3, }, { - Pubkey: [48]byte{0x05}, + Pubkey: [48]byte{0x04}, Amount: math.Gwei(cs.EffectiveBalanceIncrement() * 2 / 3), - Index: uint64(4), + Index: 4, }, } executionPayloadHeader = new(types.ExecutionPayloadHeader).Empty() @@ -97,18 +73,9 @@ func TestInitialize(t *testing.T) { } ) - // define mocks expectations - mocksSigner.On( - "VerifySignature", - mock.Anything, mock.Anything, mock.Anything, - ).Return(nil) - // run test vals, err := sp.InitializePreminedBeaconStateFromEth1( - beaconState, - deposits, - executionPayloadHeader, - fork.CurrentVersion, + st, deposits, executionPayloadHeader, fork.CurrentVersion, ) // check outputs @@ -116,20 +83,20 @@ func TestInitialize(t *testing.T) { require.Len(t, vals, len(deposits)) // check beacon state changes - resSlot, err := beaconState.GetSlot() + resSlot, err := st.GetSlot() require.NoError(t, err) require.Equal(t, math.Slot(0), resSlot) - resFork, err := beaconState.GetFork() + resFork, err := st.GetFork() require.NoError(t, err) require.Equal(t, fork, resFork) for _, dep := range deposits { - checkValidatorNonBartio(t, cs, beaconState, dep) + checkValidatorNonBartio(t, cs, st, dep) } // check that validator index is duly set - latestValIdx, err := beaconState.GetEth1DepositIndex() + latestValIdx, err := st.GetEth1DepositIndex() require.NoError(t, err) require.Equal(t, uint64(len(deposits)-1), latestValIdx) } @@ -161,54 +128,35 @@ func checkValidatorNonBartio( } func TestInitializeBartio(t *testing.T) { - // Create state processor to test - cs := spec.TestnetChainSpec() - execEngine := mocks.NewExecutionEngine[ - *types.ExecutionPayload, - *types.ExecutionPayloadHeader, - engineprimitives.Withdrawals, - ](t) - mocksSigner := &cryptomocks.BLSSigner{} - - kvStore, depositStore, err := initTestStores() - require.NoError(t, err) - beaconState := new(TestBeaconStateT).NewFromDB(kvStore, cs) - - sp := createStateProcessor( - cs, - execEngine, - depositStore, - mocksSigner, - dummyProposerAddressVerifier, - ) + cs := setupChain(t, components.TestnetChainSpecType) + sp, st, _, _ := setupState(t, cs) - // create test inputs var ( deposits = []*types.Deposit{ { - Pubkey: [48]byte{0x01}, + Pubkey: [48]byte{0x00}, Amount: math.Gwei(cs.MaxEffectiveBalance()), - Index: uint64(0), + Index: 0, }, { - Pubkey: [48]byte{0x02}, + Pubkey: [48]byte{0x01}, Amount: math.Gwei(cs.MaxEffectiveBalance() / 2), - Index: uint64(1), + Index: 1, }, { - Pubkey: [48]byte{0x03}, + Pubkey: [48]byte{0x02}, Amount: math.Gwei(cs.EffectiveBalanceIncrement()), - Index: uint64(2), + Index: 2, }, { - Pubkey: [48]byte{0x04}, + Pubkey: [48]byte{0x03}, Amount: math.Gwei(2 * cs.MaxEffectiveBalance()), - Index: uint64(3), + Index: 3, }, { - Pubkey: [48]byte{0x05}, + Pubkey: [48]byte{0x04}, Amount: math.Gwei(cs.EffectiveBalanceIncrement() * 2 / 3), - Index: uint64(4), + Index: 4, }, } executionPayloadHeader = new(types.ExecutionPayloadHeader).Empty() @@ -219,18 +167,9 @@ func TestInitializeBartio(t *testing.T) { } ) - // define mocks expectations - mocksSigner.On( - "VerifySignature", - mock.Anything, mock.Anything, mock.Anything, - ).Return(nil) - // run test vals, err := sp.InitializePreminedBeaconStateFromEth1( - beaconState, - deposits, - executionPayloadHeader, - fork.CurrentVersion, + st, deposits, executionPayloadHeader, fork.CurrentVersion, ) // check outputs @@ -238,20 +177,20 @@ func TestInitializeBartio(t *testing.T) { require.Len(t, vals, len(deposits)) // check beacon state changes - resSlot, err := beaconState.GetSlot() + resSlot, err := st.GetSlot() require.NoError(t, err) require.Equal(t, math.Slot(0), resSlot) - resFork, err := beaconState.GetFork() + resFork, err := st.GetFork() require.NoError(t, err) require.Equal(t, fork, resFork) for _, dep := range deposits { - checkValidatorBartio(t, cs, beaconState, dep) + checkValidatorBartio(t, cs, st, dep) } // check that validator index is duly set - latestValIdx, err := beaconState.GetEth1DepositIndex() + latestValIdx, err := st.GetEth1DepositIndex() require.NoError(t, err) require.Equal(t, uint64(len(deposits)-1), latestValIdx) } @@ -322,9 +261,3 @@ func commonChecksValidators( require.Equal(t, math.Gwei(0), val.EffectiveBalance) } } - -// in genesis UTs we don't need to verify proposer address -// (no one proposes genesis), hence the dummy implementation. -func dummyProposerAddressVerifier(bytes.B48) ([]byte, error) { - return nil, nil -} diff --git a/mod/state-transition/pkg/core/state_processor_staking.go b/mod/state-transition/pkg/core/state_processor_staking.go index 19eee2dc8b..1d5831673c 100644 --- a/mod/state-transition/pkg/core/state_processor_staking.go +++ b/mod/state-transition/pkg/core/state_processor_staking.go @@ -21,16 +21,11 @@ package core import ( - "fmt" - "github.com/berachain/beacon-kit/mod/config/pkg/spec" "github.com/berachain/beacon-kit/mod/consensus-types/pkg/types" - "github.com/berachain/beacon-kit/mod/errors" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" "github.com/berachain/beacon-kit/mod/primitives/pkg/math" "github.com/berachain/beacon-kit/mod/primitives/pkg/version" - "github.com/berachain/beacon-kit/mod/state-transition/pkg/core/state" - "github.com/davecgh/go-spew/spew" ) // processOperations processes the operations and ensures they match the @@ -177,110 +172,3 @@ func (sp *StateProcessor[ } return st.IncreaseBalance(idx, dep.GetAmount()) } - -// processWithdrawals as per the Ethereum 2.0 specification. -// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#new-process_withdrawals -// -//nolint:lll -func (sp *StateProcessor[ - BeaconBlockT, _, _, BeaconStateT, _, _, _, _, _, _, _, _, _, _, _, _, _, -]) processWithdrawals( - st BeaconStateT, - blk BeaconBlockT, -) error { - body := blk.GetBody() - - // Dequeue and verify the logs. - var ( - nextValidatorIndex math.ValidatorIndex - payload = body.GetExecutionPayload() - payloadWithdrawals = payload.GetWithdrawals() - ) - - // Get the expected withdrawals. - expectedWithdrawals, err := st.ExpectedWithdrawals() - if err != nil { - return err - } - numWithdrawals := len(expectedWithdrawals) - - // Ensure the withdrawals have the same length - if numWithdrawals != len(payloadWithdrawals) { - return errors.Wrapf( - ErrNumWithdrawalsMismatch, - "withdrawals do not match expected length %d, got %d", - len(expectedWithdrawals), len(payloadWithdrawals), - ) - } - - // Slot used to mint EVM tokens. - slot := blk.GetSlot() - if slot.Unwrap() == state.EVMMintingSlot { - // Sanity check. - wd := expectedWithdrawals[0] - if !wd.Equals(payloadWithdrawals[0]) { - return fmt.Errorf( - "minting withdrawal does not match expected %s, got %s", - spew.Sdump(wd), spew.Sdump(payloadWithdrawals[0]), - ) - } - - // No processing needed. - return nil - } - - // Compare and process each withdrawal. - for i, wd := range expectedWithdrawals { - // Ensure the withdrawals match the local state. - if !wd.Equals(payloadWithdrawals[i]) { - return errors.Wrapf( - ErrNumWithdrawalsMismatch, - "withdrawals do not match expected %s, got %s", - spew.Sdump(wd), spew.Sdump(payloadWithdrawals[i]), - ) - } - - // Then we process the withdrawal. - if err = st.DecreaseBalance( - wd.GetValidatorIndex(), wd.GetAmount(), - ); err != nil { - return err - } - } - - // Update the next withdrawal index if this block contained withdrawals - if numWithdrawals != 0 { - // Next sweep starts after the latest withdrawal's validator index - if err = st.SetNextWithdrawalIndex( - (expectedWithdrawals[numWithdrawals-1].GetIndex() + 1).Unwrap(), - ); err != nil { - return err - } - } - - totalValidators, err := st.GetTotalValidators() - if err != nil { - return err - } - - // Update the next validator index to start the next withdrawal sweep - //#nosec:G701 // won't overflow in practice. - if numWithdrawals == int(sp.cs.MaxWithdrawalsPerPayload()) { - // Next sweep starts after the latest withdrawal's validator index - nextValidatorIndex = - (expectedWithdrawals[len(expectedWithdrawals)-1].GetIndex() + 1) % - math.U64(totalValidators) - } else { - // Advance sweep by the max length of the sweep if there was not - // a full set of withdrawals - nextValidatorIndex, err = st.GetNextWithdrawalValidatorIndex() - if err != nil { - return err - } - nextValidatorIndex += math.ValidatorIndex( - sp.cs.MaxValidatorsPerWithdrawalsSweep()) - nextValidatorIndex %= math.ValidatorIndex(totalValidators) - } - - return st.SetNextWithdrawalValidatorIndex(nextValidatorIndex) -} diff --git a/mod/state-transition/pkg/core/state_processor_staking_test.go b/mod/state-transition/pkg/core/state_processor_staking_test.go index 59a5067391..fe52d2c473 100644 --- a/mod/state-transition/pkg/core/state_processor_staking_test.go +++ b/mod/state-transition/pkg/core/state_processor_staking_test.go @@ -23,53 +23,28 @@ package core_test import ( "testing" + "github.com/berachain/beacon-kit/mod/chain-spec/pkg/chain" "github.com/berachain/beacon-kit/mod/config/pkg/spec" "github.com/berachain/beacon-kit/mod/consensus-types/pkg/types" engineprimitives "github.com/berachain/beacon-kit/mod/engine-primitives/pkg/engine-primitives" - "github.com/berachain/beacon-kit/mod/primitives/pkg/bytes" + "github.com/berachain/beacon-kit/mod/node-core/pkg/components" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" - cryptomocks "github.com/berachain/beacon-kit/mod/primitives/pkg/crypto/mocks" "github.com/berachain/beacon-kit/mod/primitives/pkg/math" - "github.com/berachain/beacon-kit/mod/primitives/pkg/transition" "github.com/berachain/beacon-kit/mod/primitives/pkg/version" - "github.com/berachain/beacon-kit/mod/state-transition/pkg/core/mocks" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) // TestTransitionUpdateValidators shows that when validator is // updated (increasing amount), corrensponding balance is updated. func TestTransitionUpdateValidators(t *testing.T) { - // Create state processor to test - cs := spec.BetnetChainSpec() - execEngine := mocks.NewExecutionEngine[ - *types.ExecutionPayload, - *types.ExecutionPayloadHeader, - engineprimitives.Withdrawals, - ](t) - mocksSigner := &cryptomocks.BLSSigner{} - dummyProposerAddr := []byte{0xff} - - kvStore, depositStore, err := initTestStores() - require.NoError(t, err) - beaconState := new(TestBeaconStateT).NewFromDB(kvStore, cs) - - sp := createStateProcessor( - cs, - execEngine, - depositStore, - mocksSigner, - func(bytes.B48) ([]byte, error) { - return dummyProposerAddr, nil - }, - ) + cs := setupChain(t, components.BoonetChainSpecType) + sp, st, ds, ctx := setupState(t, cs) var ( maxBalance = math.Gwei(cs.MaxEffectiveBalance()) minBalance = math.Gwei(cs.EffectiveBalanceIncrement()) - emptyAddress = common.ExecutionAddress{} emptyCredentials = types.NewCredentialsFromExecutionAddress( - emptyAddress, + common.ExecutionAddress{}, ) ) @@ -78,96 +53,307 @@ func TestTransitionUpdateValidators(t *testing.T) { var ( genDeposits = []*types.Deposit{ { - Pubkey: [48]byte{0x01}, + Pubkey: [48]byte{0x00}, Credentials: emptyCredentials, Amount: maxBalance - 3*minBalance, - Index: uint64(0), + Index: 0, }, { - Pubkey: [48]byte{0x02}, + Pubkey: [48]byte{0x01}, Credentials: emptyCredentials, Amount: maxBalance - 6*minBalance, - Index: uint64(1), + Index: 1, }, } genPayloadHeader = new(types.ExecutionPayloadHeader).Empty() genVersion = version.FromUint32[common.Version](version.Deneb) ) - - mocksSigner.On( - "VerifySignature", - mock.Anything, mock.Anything, mock.Anything, - ).Return(nil) - - _, err = sp.InitializePreminedBeaconStateFromEth1( - beaconState, - genDeposits, - genPayloadHeader, - genVersion, + _, err := sp.InitializePreminedBeaconStateFromEth1( + st, genDeposits, genPayloadHeader, genVersion, ) require.NoError(t, err) // create test inputs - var ( - ctx = &transition.Context{ - SkipPayloadVerification: true, - SkipValidateResult: true, - ProposerAddress: dummyProposerAddr, - } - blkDeposit = &types.Deposit{ + blkDeposits := []*types.Deposit{ + { Pubkey: genDeposits[0].Pubkey, Credentials: emptyCredentials, Amount: minBalance, // avoid breaching maxBalance Index: uint64(len(genDeposits)), - } - ) - + }, + } blk := buildNextBlock( t, - beaconState, + st, &types.BeaconBlockBody{ ExecutionPayload: &types.ExecutionPayload{ Timestamp: 10, ExtraData: []byte("testing"), Transactions: [][]byte{}, - Withdrawals: []*engineprimitives.Withdrawal{}, // no withdrawals + Withdrawals: []*engineprimitives.Withdrawal{}, BaseFeePerGas: math.NewU256(0), }, Eth1Data: &types.Eth1Data{}, - Deposits: []*types.Deposit{blkDeposit}, + Deposits: blkDeposits, }, ) // make sure included deposit is already available in deposit store - require.NoError(t, depositStore.EnqueueDeposits( - []*types.Deposit{blkDeposit}), - ) + require.NoError(t, ds.EnqueueDeposits(blkDeposits)) // run the test - vals, err := sp.Transition(ctx, beaconState, blk) + vals, err := sp.Transition(ctx, st, blk) // check outputs require.NoError(t, err) require.Zero(t, vals) // just update, no new validators // check validator is duly updated - expectedValBalance := genDeposits[0].Amount + blkDeposit.Amount - idx, err := beaconState.ValidatorIndexByPubkey(genDeposits[0].Pubkey) + expectedValBalance := genDeposits[0].Amount + blkDeposits[0].Amount + idx, err := st.ValidatorIndexByPubkey(genDeposits[0].Pubkey) require.NoError(t, err) require.Equal(t, math.U64(genDeposits[0].Index), idx) - val, err := beaconState.ValidatorByIndex(idx) + val, err := st.ValidatorByIndex(idx) require.NoError(t, err) require.Equal(t, genDeposits[0].Pubkey, val.Pubkey) require.Equal(t, expectedValBalance, val.EffectiveBalance) // check validator balance is updated - valBal, err := beaconState.GetBalance(idx) + valBal, err := st.GetBalance(idx) require.NoError(t, err) require.Equal(t, expectedValBalance, valBal) - // check that validator index is duly set - latestValIdx, err := beaconState.GetEth1DepositIndex() + // check that validator index is duly set (1-indexed here, to be fixed) + latestValIdx, err := st.GetEth1DepositIndex() require.NoError(t, err) require.Equal(t, uint64(len(genDeposits)), latestValIdx) } + +func TestTransitionWithdrawals(t *testing.T) { + cs := setupChain(t, components.BoonetChainSpecType) + sp, st, _, ctx := setupState(t, cs) + + var ( + maxBalance = math.Gwei(cs.MaxEffectiveBalance()) + minBalance = math.Gwei(cs.EffectiveBalanceIncrement()) + credentials0 = types.NewCredentialsFromExecutionAddress( + common.ExecutionAddress{}, + ) + address1 = common.ExecutionAddress{0x01} + credentials1 = types.NewCredentialsFromExecutionAddress(address1) + ) + + // Setup initial state so that validator 1 is partially withdrawable. + var ( + genDeposits = []*types.Deposit{ + { + Pubkey: [48]byte{0x00}, + Credentials: credentials0, + Amount: maxBalance - 3*minBalance, + Index: 0, + }, + { + Pubkey: [48]byte{0x01}, + Credentials: credentials1, + Amount: maxBalance + minBalance, + Index: 1, + }, + } + genPayloadHeader = new(types.ExecutionPayloadHeader).Empty() + genVersion = version.FromUint32[common.Version](version.Deneb) + ) + _, err := sp.InitializePreminedBeaconStateFromEth1( + st, genDeposits, genPayloadHeader, genVersion, + ) + require.NoError(t, err) + + // Progress state to fork 2. + progressStateToSlot(t, st, math.U64(spec.BoonetFork2Height)) + + // Assert validator 1 balance before withdrawal. + val1Bal, err := st.GetBalance(math.U64(1)) + require.NoError(t, err) + require.Equal(t, maxBalance+minBalance, val1Bal) + + // Create test inputs. + blk := buildNextBlock( + t, + st, + &types.BeaconBlockBody{ + ExecutionPayload: &types.ExecutionPayload{ + Timestamp: 10, + ExtraData: []byte("testing"), + Transactions: [][]byte{}, + Withdrawals: []*engineprimitives.Withdrawal{ + // The first withdrawal is always for EVM inflation. + st.EVMInflationWithdrawal(), + // Partially withdraw validator 1 by minBalance. + { + Index: 0, + Validator: 1, + Amount: minBalance, + Address: address1, + }, + }, + BaseFeePerGas: math.NewU256(0), + }, + Eth1Data: &types.Eth1Data{}, + Deposits: []*types.Deposit{}, + }, + ) + + // Run the test. + _, err = sp.Transition(ctx, st, blk) + + // Check outputs and ensure withdrawals in payload is consistent with + // statedb expected withdrawals. + require.NoError(t, err) + + // Assert validator 1 balance after withdrawal. + val1BalAfter, err := st.GetBalance(math.U64(1)) + require.NoError(t, err) + require.Equal(t, maxBalance, val1BalAfter) +} + +func TestTransitionMaxWithdrawals(t *testing.T) { + // Use custom chain spec with max withdrawals set to 2. + csData := spec.BaseSpec() + csData.DepositEth1ChainID = spec.BoonetEth1ChainID + csData.MaxWithdrawalsPerPayload = 2 + cs, err := chain.NewChainSpec(csData) + require.NoError(t, err) + + sp, st, _, ctx := setupState(t, cs) + + var ( + maxBalance = math.Gwei(cs.MaxEffectiveBalance()) + minBalance = math.Gwei(cs.EffectiveBalanceIncrement()) + address0 = common.ExecutionAddress{} + credentials0 = types.NewCredentialsFromExecutionAddress(address0) + address1 = common.ExecutionAddress{0x01} + credentials1 = types.NewCredentialsFromExecutionAddress(address1) + ) + + // Setup initial state so that both validators are partially withdrawable. + var ( + genDeposits = []*types.Deposit{ + { + Pubkey: [48]byte{0x00}, + Credentials: credentials0, + Amount: maxBalance + minBalance, + Index: 0, + }, + { + Pubkey: [48]byte{0x01}, + Credentials: credentials1, + Amount: maxBalance + minBalance, + Index: 1, + }, + } + genPayloadHeader = new(types.ExecutionPayloadHeader).Empty() + genVersion = version.FromUint32[common.Version](version.Deneb) + ) + _, err = sp.InitializePreminedBeaconStateFromEth1( + st, genDeposits, genPayloadHeader, genVersion, + ) + require.NoError(t, err) + + // Progress state to fork 2. + progressStateToSlot(t, st, math.U64(spec.BoonetFork2Height)) + + // Assert validator balances before withdrawal. + val0Bal, err := st.GetBalance(math.U64(0)) + require.NoError(t, err) + require.Equal(t, maxBalance+minBalance, val0Bal) + + val1Bal, err := st.GetBalance(math.U64(1)) + require.NoError(t, err) + require.Equal(t, maxBalance+minBalance, val1Bal) + + // Create test inputs. + blk := buildNextBlock( + t, + st, + &types.BeaconBlockBody{ + ExecutionPayload: &types.ExecutionPayload{ + Timestamp: 10, + ExtraData: []byte("testing"), + Transactions: [][]byte{}, + Withdrawals: []*engineprimitives.Withdrawal{ + // The first withdrawal is always for EVM inflation. + st.EVMInflationWithdrawal(), + // Partially withdraw validator 0 by minBalance. + { + Index: 0, + Validator: 0, + Amount: minBalance, + Address: address0, + }, + }, + BaseFeePerGas: math.NewU256(0), + }, + Eth1Data: &types.Eth1Data{}, + Deposits: []*types.Deposit{}, + }, + ) + + // Run the test. + _, err = sp.Transition(ctx, st, blk) + + // Check outputs and ensure withdrawals in payload is consistent with + // statedb expected withdrawals. + require.NoError(t, err) + + // Assert validator balances after withdrawal, ensuring only validator 0 is + // withdrawn from. + val0BalAfter, err := st.GetBalance(math.U64(0)) + require.NoError(t, err) + require.Equal(t, maxBalance, val0BalAfter) + + val1BalAfter, err := st.GetBalance(math.U64(1)) + require.NoError(t, err) + require.Equal(t, maxBalance+minBalance, val1BalAfter) + + // Process the next block, ensuring that validator 1 is also withdrawn from, + // also ensuring that the state's next withdrawal (validator) index is + // appropriately incremented. + blk = buildNextBlock( + t, + st, + &types.BeaconBlockBody{ + ExecutionPayload: &types.ExecutionPayload{ + Timestamp: 11, + ExtraData: []byte("testing"), + Transactions: [][]byte{}, + Withdrawals: []*engineprimitives.Withdrawal{ + // The first withdrawal is always for EVM inflation. + st.EVMInflationWithdrawal(), + // Partially withdraw validator 1 by minBalance. + { + Index: 1, + Validator: 1, + Amount: minBalance, + Address: address1, + }, + }, + BaseFeePerGas: math.NewU256(0), + }, + Eth1Data: &types.Eth1Data{}, + Deposits: []*types.Deposit{}, + }, + ) + + // Run the test. + vals, err := sp.Transition(ctx, st, blk) + + // Check outputs and ensure withdrawals in payload is consistent with + // statedb expected withdrawals. + require.NoError(t, err) + require.Zero(t, vals) + + // Validator 1 is now withdrawn from. + val1BalAfter, err = st.GetBalance(math.U64(1)) + require.NoError(t, err) + require.Equal(t, maxBalance, val1BalAfter) +} diff --git a/mod/state-transition/pkg/core/state_processor_withdrawals.go b/mod/state-transition/pkg/core/state_processor_withdrawals.go new file mode 100644 index 0000000000..a3e096b2ba --- /dev/null +++ b/mod/state-transition/pkg/core/state_processor_withdrawals.go @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package core + +import ( + "fmt" + + "github.com/berachain/beacon-kit/mod/config/pkg/spec" + "github.com/berachain/beacon-kit/mod/errors" + "github.com/berachain/beacon-kit/mod/primitives/pkg/math" + "github.com/davecgh/go-spew/spew" +) + +// processWithdrawals as per the Ethereum 2.0 specification. +// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#new-process_withdrawals +// +// NOTE: Modified from the Ethereum 2.0 specification to support EVM inflation: +// 1. The first withdrawal MUST be a fixed EVM inflation withdrawal +// 2. Subsequent withdrawals (if any) are processed as validator withdrawals +// 3. This modification reduces the maximum validator withdrawals per block by +// one +// +//nolint:lll // TODO: Simplify when dropping special cases. +func (sp *StateProcessor[ + BeaconBlockT, _, _, BeaconStateT, _, _, _, _, _, _, _, _, _, _, _, _, _, +]) processWithdrawals( + st BeaconStateT, + blk BeaconBlockT, +) error { + // Dequeue and verify the logs. + var ( + body = blk.GetBody() + payload = body.GetExecutionPayload() + payloadWithdrawals = payload.GetWithdrawals() + ) + + // Get the expected withdrawals. + expectedWithdrawals, err := st.ExpectedWithdrawals() + if err != nil { + return err + } + + return sp.processWithdrawalsByFork( + st, expectedWithdrawals, payloadWithdrawals) +} + +func (sp *StateProcessor[ + _, _, _, BeaconStateT, _, _, _, _, _, _, + _, _, _, _, WithdrawalT, WithdrawalsT, _, +]) processWithdrawalsByFork( + st BeaconStateT, + expectedWithdrawals []WithdrawalT, + payloadWithdrawals []WithdrawalT, +) error { + slot, err := st.GetSlot() + if err != nil { + return errors.Wrap( + err, "failed loading slot while processing withdrawals", + ) + } + + // Common validations + if len(expectedWithdrawals) != len(payloadWithdrawals) { + return errors.Wrapf( + ErrNumWithdrawalsMismatch, + "withdrawals do not match expected length %d, got %d", + len(expectedWithdrawals), len(payloadWithdrawals), + ) + } + + // Chain/Fork specific processing + switch { + case sp.cs.DepositEth1ChainID() == spec.BartioChainID: + return sp.processWithdrawalsBartio( + st, + expectedWithdrawals, + payloadWithdrawals, + ) + + case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && + slot == math.U64(spec.BoonetFork1Height): + // Slot used to emergency mint EVM tokens on Boonet. + if !expectedWithdrawals[0].Equals(payloadWithdrawals[0]) { + return fmt.Errorf( + "minting withdrawal does not match expected %s, got %s", + spew.Sdump(expectedWithdrawals[0]), + spew.Sdump(payloadWithdrawals[0]), + ) + } + + return nil // No processing needed. + + case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && + slot < math.U64(spec.BoonetFork2Height): + // Boonet inherited the Bartio behaviour pre BoonetFork2Height + // nothing specific to do + return sp.processWithdrawalsBartio( + st, + expectedWithdrawals, + payloadWithdrawals, + ) + + default: + return sp.processWithdrawalsDefault( + st, + expectedWithdrawals, + payloadWithdrawals, + ) + } +} + +func (sp *StateProcessor[ + _, _, _, BeaconStateT, _, _, _, _, _, _, + _, _, _, _, WithdrawalT, WithdrawalsT, _, +]) processWithdrawalsBartio( + st BeaconStateT, + expectedWithdrawals []WithdrawalT, + payloadWithdrawals []WithdrawalT, +) error { + for i, wd := range expectedWithdrawals { + // Ensure the withdrawals match the local state. + if !wd.Equals(payloadWithdrawals[i]) { + return errors.Wrapf( + ErrWithdrawalMismatch, + "withdrawal at index %d does not match expected %s, got %s", + i, spew.Sdump(wd), spew.Sdump(payloadWithdrawals[i]), + ) + } + + // Process the validator withdrawal. + if err := st.DecreaseBalance( + wd.GetValidatorIndex(), wd.GetAmount(), + ); err != nil { + return err + } + } + + if len(expectedWithdrawals) != 0 { + if err := st.SetNextWithdrawalIndex( + (expectedWithdrawals[len(expectedWithdrawals)-1]. + GetIndex() + 1).Unwrap(), + ); err != nil { + return err + } + } + + totalValidators, err := st.GetTotalValidators() + if err != nil { + return err + } + + // Update the next validator index to start the next withdrawal sweep. + var nextValidatorIndex math.ValidatorIndex + + //#nosec:G701 // won't overflow in practice. + if len(expectedWithdrawals) == int(sp.cs.MaxWithdrawalsPerPayload()) { + nextValidatorIndex = + (expectedWithdrawals[len(expectedWithdrawals)-1].GetIndex() + 1) % + math.ValidatorIndex(totalValidators) + // Note: this is a bug, we should have used ValidatorIndex instead of + // GetIndex. processWithdrawalsDefault fixes it + } else { + // Advance sweep by the max length of the sweep if there was not a full + // set of withdrawals. + nextValidatorIndex, err = st.GetNextWithdrawalValidatorIndex() + if err != nil { + return err + } + nextValidatorIndex += math.ValidatorIndex( + sp.cs.MaxValidatorsPerWithdrawalsSweep()) + nextValidatorIndex %= math.ValidatorIndex(totalValidators) + } + + return st.SetNextWithdrawalValidatorIndex(nextValidatorIndex) +} + +func (sp *StateProcessor[ + _, _, _, BeaconStateT, _, _, _, _, _, _, + _, _, _, _, WithdrawalT, WithdrawalsT, _, +]) processWithdrawalsDefault( + st BeaconStateT, + expectedWithdrawals []WithdrawalT, + payloadWithdrawals []WithdrawalT, +) error { + // Enforce that first withdrawal is EVM inflation + if len(payloadWithdrawals) == 0 { + return ErrZeroWithdrawals + } + if !payloadWithdrawals[0].Equals(st.EVMInflationWithdrawal()) { + return ErrFirstWithdrawalNotEVMInflation + } + numWithdrawals := len(expectedWithdrawals) + + // Process all subsequent validator withdrawals. + for i := 1; i < numWithdrawals; i++ { + // Ensure the withdrawals match the local state. + if !expectedWithdrawals[i].Equals(payloadWithdrawals[i]) { + return errors.Wrapf( + ErrWithdrawalMismatch, + "withdrawal at index %d does not match expected %s, got %s", + i, + spew.Sdump(expectedWithdrawals[i]), + spew.Sdump(payloadWithdrawals[i]), + ) + } + + if err := st.DecreaseBalance( + expectedWithdrawals[i].GetValidatorIndex(), + expectedWithdrawals[i].GetAmount(), + ); err != nil { + return err + } + } + + if numWithdrawals > 1 { + if err := st.SetNextWithdrawalIndex( + (expectedWithdrawals[numWithdrawals-1].GetIndex() + 1).Unwrap(), + ); err != nil { + return err + } + } + + totalValidators, err := st.GetTotalValidators() + if err != nil { + return err + } + + // Update the next validator index to start the next withdrawal sweep. + var nextValidatorIndex math.ValidatorIndex + + //#nosec:G701 // won't overflow in practice. + if numWithdrawals == int(sp.cs.MaxWithdrawalsPerPayload()) { + // Next sweep starts after the latest withdrawal's validator index. + nextValidatorIndex = (expectedWithdrawals[numWithdrawals-1]. + GetValidatorIndex() + 1) % math.ValidatorIndex(totalValidators) + } else { + // Advance sweep by the max length of the sweep if there was not a full + // set of withdrawals. + nextValidatorIndex, err = st.GetNextWithdrawalValidatorIndex() + if err != nil { + return err + } + nextValidatorIndex += math.ValidatorIndex( + sp.cs.MaxValidatorsPerWithdrawalsSweep()) + nextValidatorIndex %= math.ValidatorIndex(totalValidators) + } + + return st.SetNextWithdrawalValidatorIndex(nextValidatorIndex) +} diff --git a/mod/state-transition/pkg/core/deposits_validation.go b/mod/state-transition/pkg/core/validation_deposits.go similarity index 100% rename from mod/state-transition/pkg/core/deposits_validation.go rename to mod/state-transition/pkg/core/validation_deposits.go diff --git a/testing/e2e/config/config.go b/testing/e2e/config/config.go index 5e450ec77e..77c922f9d9 100644 --- a/testing/e2e/config/config.go +++ b/testing/e2e/config/config.go @@ -156,180 +156,6 @@ type AdditionalService struct { Replicas int `json:"replicas"` } -// DefaultE2ETestConfig provides a default configuration for end-to-end tests, -// pre-populating with a standard set of validators and no additional -// services. -func DefaultE2ETestConfig() *E2ETestConfig { - return &E2ETestConfig{ - NetworkConfiguration: defaultNetworkConfiguration(), - NodeSettings: defaultNodeSettings(), - EthJSONRPCEndpoints: defaultEthJSONRPCEndpoints(), - AdditionalServices: defaultAdditionalServices(), - } -} - -func defaultNetworkConfiguration() NetworkConfiguration { - return NetworkConfiguration{ - Validators: defaultValidators(), - FullNodes: defaultFullNodes(), - SeedNodes: defaultSeedNodes(), - } -} - -func defaultValidators() NodeSet { - return NodeSet{ - Type: "validator", - Nodes: []Node{ - { - ElType: "nethermind", - // TODO: restore once we solve - // https://github.com/berachain/beacon-kit/issues/2177 - Replicas: 0, // nethermind cannot keep up with deposits checks - KZGImpl: "crate-crypto/go-kzg-4844", - }, - { - ElType: "geth", - Replicas: 2, //nolint:mnd // we want two replicas here - KZGImpl: "crate-crypto/go-kzg-4844", - }, - { - ElType: "reth", - Replicas: 2, //nolint:mnd // we want two replicas here - KZGImpl: "crate-crypto/go-kzg-4844", - }, - { - ElType: "erigon", - Replicas: 1, - KZGImpl: "crate-crypto/go-kzg-4844", - }, - { - ElType: "besu", - Replicas: 0, // Besu causing flakey tests. - KZGImpl: "crate-crypto/go-kzg-4844", - }, - }, - } -} - -func defaultFullNodes() NodeSet { - return NodeSet{ - Type: "full", - Nodes: []Node{ - { - ElType: "nethermind", - Replicas: 1, - KZGImpl: "crate-crypto/go-kzg-4844", - }, - { - ElType: "reth", - Replicas: 1, - KZGImpl: "crate-crypto/go-kzg-4844", - }, - { - ElType: "geth", - Replicas: 1, - KZGImpl: "crate-crypto/go-kzg-4844", - }, - { - ElType: "erigon", - Replicas: 1, - KZGImpl: "crate-crypto/go-kzg-4844", - }, - { - ElType: "besu", - Replicas: 1, - KZGImpl: "crate-crypto/go-kzg-4844", - }, - }, - } -} - -func defaultSeedNodes() NodeSet { - return NodeSet{ - Type: "seed", - Nodes: []Node{ - { - ElType: "geth", - Replicas: 1, - KZGImpl: "crate-crypto/go-kzg-4844", - }, - }, - } -} - -func defaultNodeSettings() NodeSettings { - return NodeSettings{ - ExecutionSettings: defaultExecutionSettings(), - ConsensusSettings: defaultConsensusSettings(), - } -} - -func defaultExecutionSettings() ExecutionSettings { - return ExecutionSettings{ - Specs: NodeSpecs{ - MinCPU: 0, - MaxCPU: 0, - MinMemory: 0, - MaxMemory: 2048, //nolint:mnd // 2 GB - }, - Images: map[string]string{ - "besu": "hyperledger/besu:24.5.4", - "erigon": "erigontech/erigon:v2.60.9", - "ethereumjs": "ethpandaops/ethereumjs:stable", - "geth": "ethereum/client-go:stable", - "nethermind": "nethermind/nethermind:latest", - "reth": "ghcr.io/paradigmxyz/reth:latest", - }, - } -} - -func defaultConsensusSettings() ConsensusSettings { - return ConsensusSettings{ - Specs: NodeSpecs{ - MinCPU: 0, - MaxCPU: 2000, //nolint:mnd // 2 vCPUs - MinMemory: 0, - MaxMemory: 2048, //nolint:mnd // 2 GB - }, - Images: map[string]string{ - "beaconkit": "beacond:kurtosis-local", - }, - Config: ConsensusConfig{ - TimeoutPropose: "3s", - TimeoutPrevote: "1s", - TimeoutPrecommit: "1s", - TimeoutCommit: "3s", - MaxNumInboundPeers: 40, //nolint:mnd // 40 inbound peers - MaxNumOutboundPeers: 10, //nolint:mnd // 10 outbound peers - }, - AppConfig: AppConfig{ - PayloadTimeout: "1.5s", - EnableOptimisticPayloadBuilds: false, - }, - } -} - -func defaultEthJSONRPCEndpoints() []EthJSONRPCEndpoint { - return []EthJSONRPCEndpoint{ - { - Type: "blutgang", - Clients: []string{ - // "el-full-nethermind-0", - // "el-full-reth-0", - "el-full-geth-2", - // "el-full-erigon-3", - // "el-full-erigon-3", - // Besu causing flakey tests. - // "el-full-besu-4", - }, - }, - } -} - -func defaultAdditionalServices() []AdditionalService { - return []AdditionalService{} -} - // MustMarshalJSON marshals the E2ETestConfig to JSON, panicking if an error. func (c *E2ETestConfig) MustMarshalJSON() []byte { jsonBytes, err := json.Marshal(c) diff --git a/testing/e2e/config/defaults.go b/testing/e2e/config/defaults.go index 5cb40423d0..28127fb7be 100644 --- a/testing/e2e/config/defaults.go +++ b/testing/e2e/config/defaults.go @@ -26,8 +26,176 @@ const ( AlternateClient = "cl-validator-beaconkit-1" ) -// Deposits. -const ( - DepositContractAddress = "0x4242424242424242424242424242424242424242" - NumDepositsLoad = 500 -) +// DefaultE2ETestConfig provides a default configuration for end-to-end tests, +// pre-populating with a standard set of validators and no additional +// services. +func DefaultE2ETestConfig() *E2ETestConfig { + return &E2ETestConfig{ + NetworkConfiguration: defaultNetworkConfiguration(), + NodeSettings: defaultNodeSettings(), + EthJSONRPCEndpoints: defaultEthJSONRPCEndpoints(), + AdditionalServices: defaultAdditionalServices(), + } +} + +func defaultNetworkConfiguration() NetworkConfiguration { + return NetworkConfiguration{ + Validators: defaultValidators(), + FullNodes: defaultFullNodes(), + SeedNodes: defaultSeedNodes(), + } +} + +func defaultValidators() NodeSet { + return NodeSet{ + Type: "validator", + Nodes: []Node{ + { + ElType: "nethermind", + // TODO: restore once we solve + // https://github.com/berachain/beacon-kit/issues/2177 + Replicas: 0, // nethermind cannot keep up with deposits checks + KZGImpl: "crate-crypto/go-kzg-4844", + }, + { + ElType: "geth", + Replicas: 2, //nolint:mnd // we want two replicas here + KZGImpl: "crate-crypto/go-kzg-4844", + }, + { + ElType: "reth", + Replicas: 2, //nolint:mnd // we want two replicas here + KZGImpl: "crate-crypto/go-kzg-4844", + }, + { + ElType: "erigon", + Replicas: 1, + KZGImpl: "crate-crypto/go-kzg-4844", + }, + { + ElType: "besu", + Replicas: 0, // Besu causing flakey tests. + KZGImpl: "crate-crypto/go-kzg-4844", + }, + }, + } +} + +func defaultFullNodes() NodeSet { + return NodeSet{ + Type: "full", + Nodes: []Node{ + { + ElType: "nethermind", + Replicas: 1, + KZGImpl: "crate-crypto/go-kzg-4844", + }, + { + ElType: "reth", + Replicas: 1, + KZGImpl: "crate-crypto/go-kzg-4844", + }, + { + ElType: "geth", + Replicas: 1, + KZGImpl: "crate-crypto/go-kzg-4844", + }, + { + ElType: "erigon", + Replicas: 1, + KZGImpl: "crate-crypto/go-kzg-4844", + }, + { + ElType: "besu", + Replicas: 1, + KZGImpl: "crate-crypto/go-kzg-4844", + }, + }, + } +} + +func defaultSeedNodes() NodeSet { + return NodeSet{ + Type: "seed", + Nodes: []Node{ + { + ElType: "geth", + Replicas: 1, + KZGImpl: "crate-crypto/go-kzg-4844", + }, + }, + } +} + +func defaultNodeSettings() NodeSettings { + return NodeSettings{ + ExecutionSettings: defaultExecutionSettings(), + ConsensusSettings: defaultConsensusSettings(), + } +} + +func defaultExecutionSettings() ExecutionSettings { + return ExecutionSettings{ + Specs: NodeSpecs{ + MinCPU: 0, + MaxCPU: 0, + MinMemory: 0, + MaxMemory: 2048, //nolint:mnd // 2 GB + }, + Images: map[string]string{ + "besu": "hyperledger/besu:24.5.4", + "erigon": "erigontech/erigon:v2.60.9", + "ethereumjs": "ethpandaops/ethereumjs:stable", + "geth": "ethereum/client-go:stable", + "nethermind": "nethermind/nethermind:latest", + "reth": "ghcr.io/paradigmxyz/reth:latest", + }, + } +} + +func defaultConsensusSettings() ConsensusSettings { + return ConsensusSettings{ + Specs: NodeSpecs{ + MinCPU: 0, + MaxCPU: 2000, //nolint:mnd // 2 vCPUs + MinMemory: 0, + MaxMemory: 2048, //nolint:mnd // 2 GB + }, + Images: map[string]string{ + "beaconkit": "beacond:kurtosis-local", + }, + Config: ConsensusConfig{ + TimeoutPropose: "3s", + TimeoutPrevote: "1s", + TimeoutPrecommit: "1s", + TimeoutCommit: "3s", + MaxNumInboundPeers: 40, //nolint:mnd // 40 inbound peers + MaxNumOutboundPeers: 10, //nolint:mnd // 10 outbound peers + }, + AppConfig: AppConfig{ + PayloadTimeout: "1.5s", + EnableOptimisticPayloadBuilds: false, + }, + } +} + +func defaultEthJSONRPCEndpoints() []EthJSONRPCEndpoint { + return []EthJSONRPCEndpoint{ + { + Type: "blutgang", + Clients: []string{ + // "el-full-nethermind-0", + // "el-full-reth-0", + "el-full-geth-2", + // "el-full-erigon-3", + // "el-full-erigon-3", + // Besu causing flakey tests. + // "el-full-besu-4", + }, + }, + } +} + +func defaultAdditionalServices() []AdditionalService { + return []AdditionalService{} +} diff --git a/testing/e2e/e2e_staking_test.go b/testing/e2e/e2e_staking_test.go index a897fa6cc7..8f67cc3a18 100644 --- a/testing/e2e/e2e_staking_test.go +++ b/testing/e2e/e2e_staking_test.go @@ -23,6 +23,7 @@ package e2e_test import ( "math/big" + "github.com/berachain/beacon-kit/mod/config/pkg/spec" "github.com/berachain/beacon-kit/mod/consensus-types/pkg/types" "github.com/berachain/beacon-kit/mod/geth-primitives/pkg/deposit" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" @@ -33,6 +34,12 @@ import ( coretypes "github.com/ethereum/go-ethereum/core/types" ) +const ( + // NumDepositsLoad is the number of deposits to load in the Deposit + // Robustness e2e test. + NumDepositsLoad = 500 +) + func (s *BeaconKitE2ESuite) TestDepositRobustness() { // Get the consensus client. client := s.ConsensusClients()[config.DefaultClient] @@ -73,7 +80,7 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() { // Bind the deposit contract. dc, err := deposit.NewBeaconDepositContract( - gethcommon.HexToAddress(config.DepositContractAddress), + gethcommon.HexToAddress(spec.DefaultDepositContractAddress), s.JSONRPCBalancer(), ) s.Require().NoError(err) @@ -81,7 +88,7 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() { tx, err := dc.AllowDeposit(&bind.TransactOpts{ From: genesisAccount.Address(), Signer: genesisAccount.SignerFunc(chainID), - }, sender.Address(), config.NumDepositsLoad) + }, sender.Address(), NumDepositsLoad) s.Require().NoError(err) // Wait for the transaction to be mined. @@ -97,7 +104,7 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() { ) s.Require().NoError(err) - for i := range config.NumDepositsLoad { + for i := range NumDepositsLoad { // Create a deposit transaction. tx, err = s.generateNewDepositTx( dc, @@ -108,7 +115,7 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() { s.Require().NoError(err) s.Logger(). Info("Deposit transaction created", "txHash", tx.Hash().Hex()) - if i == config.NumDepositsLoad-1 { + if i == NumDepositsLoad-1 { s.Logger().Info( "Waiting for deposit transaction to be mined", "txHash", tx.Hash().Hex(), @@ -141,7 +148,7 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() { // lower bound: 32ether * 500 oneEther := big.NewInt(1e18) totalAmt := new(big.Int).Mul( - oneEther, big.NewInt(config.NumDepositsLoad*32), + oneEther, big.NewInt(NumDepositsLoad*32), ) upperBound := new(big.Int).Add(totalAmt, oneEther) amtSpent := new(big.Int).Sub(balance, postDepositBalance) diff --git a/testing/e2e/e2e_startup_test.go b/testing/e2e/e2e_startup_test.go index 8ec0de8872..0c86afef96 100644 --- a/testing/e2e/e2e_startup_test.go +++ b/testing/e2e/e2e_startup_test.go @@ -21,7 +21,12 @@ package e2e_test import ( + "math/big" + + "github.com/berachain/beacon-kit/mod/config/pkg/spec" + "github.com/berachain/beacon-kit/mod/primitives/pkg/math" "github.com/berachain/beacon-kit/testing/e2e/suite" + gethcommon "github.com/ethereum/go-ethereum/common" ) // BeaconE2ESuite is a suite of tests simulating a fully function beacon-kit @@ -31,8 +36,33 @@ type BeaconKitE2ESuite struct { } // TestBasicStartup tests the basic startup of the beacon-kit network. -// TODO: Should check all clients, opposed to the load balancer. +// TODO: Should check all clients, opposed to just the load balancer. func (s *BeaconKitE2ESuite) TestBasicStartup() { err := s.WaitForFinalizedBlockNumber(10) s.Require().NoError(err) } + +// TestEVMInflation checks that the EVM inflation address receives the correct +// amount of EVM inflation per block. +func (s *BeaconKitE2ESuite) TestEVMInflation() { + evmInflationPerBlockWei, _ := big.NewFloat( + spec.DevnetEVMInflationPerBlock * math.GweiPerWei, + ).Int(nil) + + // Check over the next 10 EVM blocks, that after every block, the balance + // of the EVM inflation address increases by DevnetEVMInflationPerBlock. + for i := int64(0); i <= 10; i++ { + err := s.WaitForFinalizedBlockNumber(uint64(i)) + s.Require().NoError(err) + + balance, err := s.JSONRPCBalancer().BalanceAt( + s.Ctx(), + gethcommon.HexToAddress(spec.DevnetEVMInflationAddress), + big.NewInt(i), + ) + s.Require().NoError(err) + s.Require().Zero(balance.Cmp(new(big.Int).Mul( + evmInflationPerBlockWei, big.NewInt(i)), + )) + } +} diff --git a/testing/go.mod b/testing/go.mod index 68aa0081b8..e1c5a338e8 100644 --- a/testing/go.mod +++ b/testing/go.mod @@ -5,6 +5,7 @@ go 1.23.0 require ( cosmossdk.io/log v1.4.1 github.com/attestantio/go-eth2-client v0.21.10 + github.com/berachain/beacon-kit/mod/config v0.0.0-20240705193247-d464364483df github.com/berachain/beacon-kit/mod/consensus-types v0.0.0-20240806160829-cde2d1347e7e github.com/berachain/beacon-kit/mod/errors v0.0.0-20240705193247-d464364483df github.com/berachain/beacon-kit/mod/geth-primitives v0.0.0-20240806160829-cde2d1347e7e @@ -30,7 +31,6 @@ require ( github.com/adrg/xdg v0.4.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/berachain/beacon-kit/mod/chain-spec v0.0.0-20240705193247-d464364483df // indirect - github.com/berachain/beacon-kit/mod/config v0.0.0-20240705193247-d464364483df // indirect github.com/berachain/beacon-kit/mod/engine-primitives v0.0.0-20240808194557-e72e74f58197 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect