Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(e2e): Add verification during deposit e2e test #2398

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion node-api/backend/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (b Backend) ValidatorByID(
Balance: balance.Unwrap(),
},
Status: "active_ongoing", // TODO: fix
Validator: validator,
Validator: beacontypes.ValidatorFromConsensus(validator),
}, nil
}

Expand Down
13 changes: 13 additions & 0 deletions node-api/handlers/beacon/types/conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,16 @@ func SidecarFromConsensus(sc *datypes.BlobSidecar) *Sidecar {
KZGCommitmentInclusionProof: proofs,
}
}

func ValidatorFromConsensus(v *ctypes.Validator) *Validator {
return &Validator{
PublicKey: v.GetPubkey().String(),
WithdrawalCredentials: v.GetWithdrawalCredentials().String(),
EffectiveBalance: v.GetEffectiveBalance().Base10(),
Slashed: v.IsSlashed(),
ActivationEligibilityEpoch: v.GetActivationEligibilityEpoch().Base10(),
ActivationEpoch: v.GetActivationEpoch().Base10(),
ExitEpoch: v.GetExitEpoch().Base10(),
WithdrawableEpoch: v.GetWithdrawableEpoch().Base10(),
}
}
23 changes: 20 additions & 3 deletions node-api/handlers/beacon/types/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
package types

import (
ctypes "github.com/berachain/beacon-kit/consensus-types/types"
"github.com/berachain/beacon-kit/primitives/common"
)

type GenericResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data any `json:"data"`
}

type ValidatorResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Expand Down Expand Up @@ -74,15 +79,27 @@ type RootData struct {

type ValidatorData struct {
ValidatorBalanceData
Status string `json:"status"`
Validator *ctypes.Validator `json:"validator"`
Status string `json:"status"`
Validator *Validator `json:"validator"`
}

type ValidatorBalanceData struct {
Index uint64 `json:"index,string"`
Balance uint64 `json:"balance,string"`
}

// Validator is the spec representation of the struct.
type Validator struct {
PublicKey string `json:"pubkey"`
WithdrawalCredentials string `json:"withdrawal_credentials"`
EffectiveBalance string `json:"effective_balance"`
Slashed bool `json:"slashed"`
ActivationEligibilityEpoch string `json:"activation_eligibility_epoch"`
ActivationEpoch string `json:"activation_epoch"`
ExitEpoch string `json:"exit_epoch"`
WithdrawableEpoch string `json:"withdrawable_epoch"`
}

//nolint:staticcheck // todo: figure this out.
type CommitteeData struct {
Index uint64 `json:"index,string"`
Expand Down
8 changes: 7 additions & 1 deletion scripts/build/testing.mk
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,10 @@ test-e2e-4844: ## run e2e tests
@$(MAKE) build-docker VERSION=kurtosis-local test-e2e-4844-no-build

test-e2e-4844-no-build:
go test -timeout 0 -tags e2e,bls12381 ./testing/e2e/. -v -testify.m Test4844Live
go test -timeout 0 -tags e2e,bls12381 ./testing/e2e/. -v -testify.m Test4844Live

test-e2e-deposits: ## run e2e tests
@$(MAKE) build-docker VERSION=kurtosis-local test-e2e-deposits-no-build

test-e2e-deposits-no-build:
go test -timeout 0 -tags e2e,bls12381 ./testing/e2e/. -v -testify.m TestDepositRobustness
181 changes: 113 additions & 68 deletions testing/e2e/e2e_staking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ package e2e_test
import (
"math/big"

"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/berachain/beacon-kit/config/spec"
"github.com/berachain/beacon-kit/geth-primitives/deposit"
"github.com/berachain/beacon-kit/testing/e2e/config"
"github.com/berachain/beacon-kit/testing/e2e/suite/types"
"github.com/cometbft/cometbft/crypto/bls12381"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
gethcommon "github.com/ethereum/go-ethereum/common"
Expand All @@ -35,22 +38,38 @@ import (
const (
// NumDepositsLoad is the number of deposits to load in the Deposit Robustness e2e test.
NumDepositsLoad uint64 = 500

// DepositAmount is the amount of BERA to deposit.
DepositAmount = 32e18

// BlocksToWait is the number of blocks to wait for the nodes to catch up.
BlocksToWait = 10
)

// Contains pre-test state for validator info to facilitate validation of the post-state.
type ValidatorTestStruct struct {
Index uint64
Power *big.Int
WithdrawalBalance *big.Int
WithdrawalCredentials [32]byte
Name string
Client *types.ConsensusClient
}

// TestDepositRobustness tests sending a large number of deposits txs to the Deposit Contract.
// Then it checks whether all the validators' voting power have increased by the correct amount.
//
// TODO:
// 1) Add staking tests for exceeding the max stake.
// 2) Add staking tests for adding a new validator to the network.
// 3) Add staking tests for hitting the validator set cap and eviction.
// 1) Add staking tests for adding a new validator to the network.
// 2) Add staking tests for hitting the validator set cap and eviction.
func (s *BeaconKitE2ESuite) TestDepositRobustness() {
chainspec, err := spec.DevnetChainSpec()
s.Require().NoError(err)

weiPerGwei := big.NewInt(1e9)

// This value is determined by the MIN_DEPOSIT_AMOUNT_IN_GWEI variable from the deposit contract.
contractMinDepositAmountWei := big.NewInt(1e9 * 1e9)
depositAmountWei := new(big.Int).Mul(contractMinDepositAmountWei, big.NewInt(100))
depositAmountGwei := new(big.Int).Div(depositAmountWei, weiPerGwei)

// Our deposits should be greater than the min deposit amount.
s.Require().Equal(1, depositAmountWei.Cmp(contractMinDepositAmountWei))

s.Require().Equal(
0, int(NumDepositsLoad%config.NumValidators),
"every validator must get an equal amount of deposits",
Expand All @@ -62,7 +81,6 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() {

// Bind the deposit contract.
depositContractAddress := gethcommon.HexToAddress(spec.DefaultDepositContractAddress)

dc, err := deposit.NewDepositContract(depositContractAddress, s.JSONRPCBalancer())
s.Require().NoError(err)

Expand All @@ -71,7 +89,6 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() {
BlockNumber: big.NewInt(0),
})
s.Require().NoError(err)

s.Require().Equal(uint64(config.NumValidators), depositCount,
"initial deposit count should match number of validators")

Expand All @@ -84,17 +101,49 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() {
s.Require().NoError(err)
s.Require().False(genesisRoot == [32]byte{})

// Get the consensus clients.
client0 := s.ConsensusClients()[config.ClientValidator0]
s.Require().NotNil(client0)
client1 := s.ConsensusClients()[config.ClientValidator1]
s.Require().NotNil(client1)
client2 := s.ConsensusClients()[config.ClientValidator2]
s.Require().NotNil(client2)
client3 := s.ConsensusClients()[config.ClientValidator3]
s.Require().NotNil(client3)
client4 := s.ConsensusClients()[config.ClientValidator4]
s.Require().NotNil(client4)
apiClient := s.ConsensusClients()[config.ClientValidator0]
s.Require().NotNil(apiClient)

// Grab genesis validators to get withdrawal creds.
s.Require().NoError(apiClient.Connect(s.Ctx()))
s.Require().NotNil(apiClient.BeaconKitNodeClient)
response, err := apiClient.BeaconKitNodeClient.Validators(s.Ctx(), &api.ValidatorsOpts{
State: "genesis",
Indices: []phase0.ValidatorIndex{0, 1, 2, 3, 4},
})
s.Require().NoError(err)
vals := response.Data
s.Require().NotNil(vals)
s.Require().Len(vals, config.NumValidators)
s.Require().Len(s.ConsensusClients(), config.NumValidators)

// Grab pre-state data for each validator.
validators := make([]*ValidatorTestStruct, config.NumValidators)
var idx uint64
for name, client := range s.ConsensusClients() {
power, cErr := client.GetConsensusPower(s.Ctx())
s.Require().NoError(cErr)

s.Require().Contains(vals, phase0.ValidatorIndex(idx))
val := vals[phase0.ValidatorIndex(idx)]
s.Require().NotNil(val)
s.Require().NotNil(val.Validator)
creds := [32]byte(val.Validator.WithdrawalCredentials)
withdrawalAddress := gethcommon.Address(creds[12:])
withdrawalBalance, jErr := s.JSONRPCBalancer().BalanceAt(s.Ctx(), withdrawalAddress, nil)
s.Require().NoError(jErr)

// Populate the validators testing struct so we can keep track of the pre-state.
validators[idx] = &ValidatorTestStruct{
Index: idx,
Power: new(big.Int).SetUint64(power),
WithdrawalBalance: withdrawalBalance,
WithdrawalCredentials: creds,
Name: name,
Client: client,
}
idx++
}

// Sender account
sender := s.TestAccounts()[0]
Expand All @@ -108,8 +157,8 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() {
s.Ctx(), sender.Address(), new(big.Int).SetUint64(blkNum),
)
s.Require().NoError(err)
// Get the nonce.

// Get the nonce.
nonce, err := s.JSONRPCBalancer().NonceAt(
s.Ctx(), sender.Address(), new(big.Int).SetUint64(blkNum),
)
Expand All @@ -119,26 +168,15 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() {
tx *coretypes.Transaction
clientPubkey []byte
pk *bls12381.PubKey
credentials [32]byte
signature [96]byte
value, _ = big.NewFloat(DepositAmount).Int(nil)
value = depositAmountWei
signer = sender.SignerFunc(chainID)
from = sender.Address()
)
for i := range NumDepositsLoad {
// Create a deposit transaction using the default validators' pubkeys.
switch i % config.NumValidators {
case 0:
clientPubkey, err = client0.GetPubKey(s.Ctx())
case 1:
clientPubkey, err = client1.GetPubKey(s.Ctx())
case 2:
clientPubkey, err = client2.GetPubKey(s.Ctx())
case 3:
clientPubkey, err = client3.GetPubKey(s.Ctx())
case 4:
clientPubkey, err = client4.GetPubKey(s.Ctx())
}
curVal := validators[i%config.NumValidators]
clientPubkey, err = curVal.Client.GetPubKey(s.Ctx())
s.Require().NoError(err)
pk, err = bls12381.NewPublicKeyFromBytes(clientPubkey)
s.Require().NoError(err)
Expand All @@ -157,9 +195,9 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() {
Nonce: new(big.Int).SetUint64(nonce + i),
GasLimit: 1000000,
Context: s.Ctx(),
}, pubkey, credentials[:], signature[:], operator)
}, pubkey, curVal.WithdrawalCredentials[:], signature[:], operator)
s.Require().NoError(err)
s.Logger().Info("Deposit tx created", "num", i+1, "hash", tx.Hash().Hex())
s.Logger().Info("Deposit tx created", "num", i+1, "hash", tx.Hash().Hex(), "value", value)
}

// Wait for the final deposit tx to be mined.
Expand All @@ -173,13 +211,13 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() {
s.Logger().Info("Final deposit tx mined successfully", "hash", receipt.TxHash.Hex())

// Give time for the nodes to catch up.
err = s.WaitForNBlockNumbers(BlocksToWait)
err = s.WaitForNBlockNumbers(NumDepositsLoad / chainspec.MaxDepositsPerBlock())
s.Require().NoError(err)

// Compare height of nodes 0 and 1
height, err := client0.ABCIInfo(s.Ctx())
height, err := validators[0].Client.ABCIInfo(s.Ctx())
s.Require().NoError(err)
height2, err := client1.ABCIInfo(s.Ctx())
height2, err := validators[1].Client.ABCIInfo(s.Ctx())
s.Require().NoError(err)
s.Require().InDelta(height.Response.LastBlockHeight, height2.Response.LastBlockHeight, 1)

Expand All @@ -188,37 +226,44 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() {
s.Require().NoError(err)

// Check that the eth spent is somewhere~ (gas) between
// (DepositAmount * NumDepositsLoad, DepositAmount * NumDepositsLoad + 2ether)
// (depositAmountWei * NumDepositsLoad, depositAmountWei * NumDepositsLoad + 2ether)
lowerBound := new(big.Int).Mul(value, new(big.Int).SetUint64(NumDepositsLoad))
upperBound := new(big.Int).Add(lowerBound, big.NewInt(2e18))
amtSpent := new(big.Int).Sub(balance, postDepositBalance)

s.Require().Equal(1, amtSpent.Cmp(lowerBound), "amount spent is less than lower bound")
s.Require().Equal(-1, amtSpent.Cmp(upperBound), "amount spent is greater than upper bound")

// TODO: determine why voting power is not increasing above 32e9.
// // Check that all validators' voting power have increased by
// // (NumDepositsLoad / NumValidators) * DepositAmount
// // after the end of the epoch (next multiple of 32 after receipt.BlockNumber).
// nextEpochBlockNum := (receipt.BlockNumber.Uint64()/32 + 1) * 32
// err = s.WaitForFinalizedBlockNumber(nextEpochBlockNum + 1)
// s.Require().NoError(err)

// power0After, err := client0.GetConsensusPower(s.Ctx())
// s.Require().NoError(err)
// power1After, err := client1.GetConsensusPower(s.Ctx())
// s.Require().NoError(err)
// power2After, err := client2.GetConsensusPower(s.Ctx())
// s.Require().NoError(err)
// power3After, err := client3.GetConsensusPower(s.Ctx())
// s.Require().NoError(err)
// power4After, err := client4.GetConsensusPower(s.Ctx())
// s.Require().NoError(err)

// increaseAmt := NumDepositsLoad / config.NumValidators * uint64(DepositAmount/params.GWei)
// s.Require().Equal(power0+increaseAmt, power0After)
// s.Require().Equal(power1+increaseAmt, power1After)
// s.Require().Equal(power2+increaseAmt, power2After)
// s.Require().Equal(power3+increaseAmt, power3After)
// s.Require().Equal(power4+increaseAmt, power4After)
// Check that all validators' voting power have increased by
// (NumDepositsLoad / NumValidators) * depositAmountWei
// after the end of the epoch (next multiple of SlotsPerEpoch after receipt.BlockNumber).
blkNum, err = s.JSONRPCBalancer().BlockNumber(s.Ctx())
s.Require().NoError(err)
nextEpochBlockNum := (blkNum/chainspec.SlotsPerEpoch() + 1) * chainspec.SlotsPerEpoch()
err = s.WaitForFinalizedBlockNumber(nextEpochBlockNum + 1)
s.Require().NoError(err)

increaseAmt := new(big.Int).Mul(depositAmountGwei, big.NewInt(int64(NumDepositsLoad/config.NumValidators)))

for _, val := range validators {
// Consensus Power is in Gwei.
powerAfterRaw, cErr := val.Client.GetConsensusPower(s.Ctx())
s.Require().NoError(cErr)
powerAfter := new(big.Int).SetUint64(powerAfterRaw)
powerDiff := new(big.Int).Sub(powerAfter, val.Power)

// withdrawal balance is in Wei, so we'll convert it to Gwei.
withdrawalAddress := gethcommon.Address(val.WithdrawalCredentials[12:])
withdrawalBalanceAfter, jErr := s.JSONRPCBalancer().BalanceAt(s.Ctx(), withdrawalAddress, nil)
s.Require().NoError(jErr)
withdrawalDiff := new(big.Int).Sub(withdrawalBalanceAfter, val.WithdrawalBalance)
withdrawalDiff.Div(withdrawalDiff, weiPerGwei)

// TODO: currently the kurtosis devnet sets the withdrawal address the same for all validators.
// We simply validate that the balance is NumValidators times larger than we expect it to be.
withdrawalDiff.Div(withdrawalDiff, new(big.Int).SetUint64(config.NumValidators))

// Verify input balance is equal to the power + withdrawal balances.
s.Require().Equal(increaseAmt, new(big.Int).Add(powerDiff, withdrawalDiff))
}
}
2 changes: 1 addition & 1 deletion testing/e2e/suite/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func (s *KurtosisE2ESuite) FundAccounts() {
gasTipCap, big.NewInt(0).SetUint64(TenGwei))
nonceToSubmit := nonce.Add(1) - 1
//nolint:mnd // 20000 Ether
value := new(big.Int).Mul(big.NewInt(20000), big.NewInt(Ether))
value := new(big.Int).Mul(big.NewInt(200000), big.NewInt(Ether))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More funding to the test accounts. They did not have enough BERA to hit the devnet 4000e9 max effective balance for all 5 validators.

dest := account.Address()
var signedTx *ethtypes.Transaction
if signedTx, err = s.genesisAccount.SignTx(
Expand Down
Loading