diff --git a/app/test_common.go b/app/test_common.go index 5c46c1ac..5962a72e 100644 --- a/app/test_common.go +++ b/app/test_common.go @@ -9,6 +9,7 @@ import ( "time" sdkmath "cosmossdk.io/math" + dasignerskeeper "github.com/0glabs/0g-chain/x/dasigners/v1/keeper" tmdb "github.com/cometbft/cometbft-db" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/libs/log" @@ -54,7 +55,7 @@ var ( defaultInitialHeight int64 = 1 ) -const TestChainId = "kavatest_2221-1" +const TestChainId = "zgchain_8888-1" // TestApp is a simple wrapper around an App. It exposes internal keepers for use in integration tests. // This file also contains test helpers. Ideally they would be in separate package. @@ -114,6 +115,7 @@ func (tApp TestApp) GetCommitteeKeeper() committeekeeper.Keeper { return tApp.co func (tApp TestApp) GetEvmutilKeeper() evmutilkeeper.Keeper { return tApp.evmutilKeeper } func (tApp TestApp) GetEvmKeeper() *evmkeeper.Keeper { return tApp.evmKeeper } func (tApp TestApp) GetFeeMarketKeeper() feemarketkeeper.Keeper { return tApp.feeMarketKeeper } +func (tApp TestApp) GetDASignersKeeper() dasignerskeeper.Keeper { return tApp.dasignersKeeper } func (tApp TestApp) GetKVStoreKey(key string) *storetypes.KVStoreKey { return tApp.keys[key] diff --git a/precompiles/dasigners/dasigners_test.go b/precompiles/dasigners/dasigners_test.go new file mode 100644 index 00000000..4ed52dd8 --- /dev/null +++ b/precompiles/dasigners/dasigners_test.go @@ -0,0 +1,375 @@ +package dasigners_test + +import ( + "math/big" + "strings" + "testing" + + "github.com/0glabs/0g-chain/crypto/bn254util" + dasignersprecompile "github.com/0glabs/0g-chain/precompiles/dasigners" + "github.com/0glabs/0g-chain/precompiles/testutil" + "github.com/0glabs/0g-chain/x/dasigners/v1" + "github.com/0glabs/0g-chain/x/dasigners/v1/keeper" + dasignerskeeper "github.com/0glabs/0g-chain/x/dasigners/v1/keeper" + "github.com/0glabs/0g-chain/x/dasigners/v1/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/consensys/gnark-crypto/ecc/bn254" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "github.com/stretchr/testify/suite" + + "cosmossdk.io/math" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/ethermint/crypto/ethsecp256k1" +) + +type DASignersTestSuite struct { + testutil.PrecompileTestSuite + + abi abi.ABI + addr common.Address + dasigners *dasignersprecompile.DASignersPrecompile + dasignerskeeper dasignerskeeper.Keeper + signerOne *testutil.TestSigner + signerTwo *testutil.TestSigner +} + +func (suite *DASignersTestSuite) AddDelegation(from string, to string, amount math.Int) { + accAddr, err := sdk.AccAddressFromHexUnsafe(from) + suite.Require().NoError(err) + valAddr, err := sdk.ValAddressFromHex(to) + suite.Require().NoError(err) + validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr) + if !found { + consPriv, err := ethsecp256k1.GenerateKey() + suite.Require().NoError(err) + newValidator, err := stakingtypes.NewValidator(valAddr, consPriv.PubKey(), stakingtypes.Description{}) + suite.Require().NoError(err) + validator = newValidator + } + validator.Tokens = validator.Tokens.Add(amount) + validator.DelegatorShares = validator.DelegatorShares.Add(amount.ToLegacyDec()) + suite.StakingKeeper.SetValidator(suite.Ctx, validator) + bonded := suite.dasignerskeeper.GetDelegatorBonded(suite.Ctx, accAddr) + suite.StakingKeeper.SetDelegation(suite.Ctx, stakingtypes.Delegation{ + DelegatorAddress: accAddr.String(), + ValidatorAddress: valAddr.String(), + Shares: bonded.Add(amount).ToLegacyDec(), + }) +} + +func (suite *DASignersTestSuite) SetupTest() { + suite.PrecompileTestSuite.SetupTest() + + suite.dasignerskeeper = suite.App.GetDASignersKeeper() + + suite.addr = common.HexToAddress(dasignersprecompile.PrecompileAddress) + + precompiles := suite.EvmKeeper.GetPrecompiles() + precompile, ok := precompiles[suite.addr] + suite.Assert().EqualValues(ok, true) + suite.dasigners = precompile.(*dasignersprecompile.DASignersPrecompile) + + suite.signerOne = testutil.GenSigner() + suite.signerTwo = testutil.GenSigner() + abi, err := abi.JSON(strings.NewReader(dasignersprecompile.DASignersABI)) + suite.Assert().NoError(err) + suite.abi = abi +} + +func (suite *DASignersTestSuite) runTx(input []byte, signer *testutil.TestSigner, gas uint64) ([]byte, error) { + contract := vm.NewPrecompile(vm.AccountRef(signer.Addr), vm.AccountRef(suite.addr), big.NewInt(0), gas) + contract.Input = input + + msgEthereumTx := evmtypes.NewTx(suite.EvmKeeper.ChainID(), 0, &suite.addr, big.NewInt(0), gas, big.NewInt(0), big.NewInt(0), big.NewInt(0), input, nil) + msgEthereumTx.From = signer.HexAddr + err := msgEthereumTx.Sign(suite.EthSigner, signer.Signer) + suite.Assert().NoError(err, "failed to sign Ethereum message") + + proposerAddress := suite.Ctx.BlockHeader().ProposerAddress + cfg, err := suite.EvmKeeper.EVMConfig(suite.Ctx, proposerAddress, suite.EvmKeeper.ChainID()) + suite.Assert().NoError(err, "failed to instantiate EVM config") + + msg, err := msgEthereumTx.AsMessage(suite.EthSigner, big.NewInt(0)) + suite.Assert().NoError(err, "failed to instantiate Ethereum message") + + evm := suite.EvmKeeper.NewEVM(suite.Ctx, msg, cfg, nil, suite.Statedb) + precompiles := suite.EvmKeeper.GetPrecompiles() + evm.WithPrecompiles(precompiles, []common.Address{suite.addr}) + + return suite.dasigners.Run(evm, contract, false) +} + +func (suite *DASignersTestSuite) registerSigner(testSigner *testutil.TestSigner, sk *big.Int) *types.Signer { + pkG1 := new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), sk) + pkG2 := new(bn254.G2Affine).ScalarMultiplication(bn254util.GetG2Generator(), sk) + hash := types.PubkeyRegistrationHash(testSigner.Addr, big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, sk) + signer := &types.Signer{ + Account: testSigner.HexAddr, + Socket: "0.0.0.0:1234", + PubkeyG1: bn254util.SerializeG1(pkG1), + PubkeyG2: bn254util.SerializeG2(pkG2), + } + + input, err := suite.abi.Pack( + "registerSigner", + dasignersprecompile.NewIDASignersSignerDetail(signer), + dasignersprecompile.NewBN254G1Point(bn254util.SerializeG1(signature)), + ) + suite.Assert().NoError(err) + + oldLogs := suite.Statedb.Logs() + _, err = suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + logs := suite.Statedb.Logs() + suite.Assert().EqualValues(len(logs), len(oldLogs)+2) + + _, err = suite.abi.Unpack("SocketUpdated", logs[len(logs)-1].Data) + suite.Assert().NoError(err) + _, err = suite.abi.Unpack("NewSigner", logs[len(logs)-2].Data) + suite.Assert().NoError(err) + return signer +} + +func (suite *DASignersTestSuite) updateSocket(testSigner *testutil.TestSigner, signer *types.Signer) { + input, err := suite.abi.Pack( + "updateSocket", + "0.0.0.0:2345", + ) + suite.Assert().NoError(err) + + oldLogs := suite.Statedb.Logs() + _, err = suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + logs := suite.Statedb.Logs() + suite.Assert().EqualValues(len(logs), len(oldLogs)+1) + + _, err = suite.abi.Unpack("SocketUpdated", logs[len(logs)-1].Data) + suite.Assert().NoError(err) + + signer.Socket = "0.0.0.0:2345" +} + +func (suite *DASignersTestSuite) registerEpoch(testSigner *testutil.TestSigner, sk *big.Int) { + hash := types.EpochRegistrationHash(common.HexToAddress(testSigner.HexAddr), 1, big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, sk) + + input, err := suite.abi.Pack( + "registerNextEpoch", + dasignersprecompile.NewBN254G1Point(bn254util.SerializeG1(signature)), + ) + suite.Assert().NoError(err) + + _, err = suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) +} + +func (suite *DASignersTestSuite) queryEpochNumber(testSigner *testutil.TestSigner) { + input, err := suite.abi.Pack( + "epochNumber", + ) + suite.Assert().NoError(err) + + bz, err := suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + out, err := suite.abi.Methods["epochNumber"].Outputs.Unpack(bz) + suite.Assert().NoError(err) + suite.Assert().EqualValues(out[0], big.NewInt(1)) +} + +func (suite *DASignersTestSuite) queryQuorumCount(testSigner *testutil.TestSigner) { + input, err := suite.abi.Pack( + "quorumCount", + big.NewInt(1), + ) + suite.Assert().NoError(err) + + bz, err := suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + out, err := suite.abi.Methods["quorumCount"].Outputs.Unpack(bz) + suite.Assert().NoError(err) + suite.Assert().EqualValues(out[0], big.NewInt(1)) +} + +func (suite *DASignersTestSuite) queryGetSigner(testSigner *testutil.TestSigner, answer []*types.Signer) { + input, err := suite.abi.Pack( + "getSigner", + []common.Address{suite.signerOne.Addr, suite.signerTwo.Addr}, + ) + suite.Assert().NoError(err) + + bz, err := suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + out, err := suite.abi.Methods["getSigner"].Outputs.Unpack(bz) + suite.Assert().NoError(err) + res := make([]dasignersprecompile.IDASignersSignerDetail, 0) + for _, s := range answer { + res = append(res, dasignersprecompile.NewIDASignersSignerDetail(s)) + } + suite.Assert().EqualValues(out[0], res) +} + +func (suite *DASignersTestSuite) queryIsSigner(testSigner *testutil.TestSigner) { + input, err := suite.abi.Pack( + "isSigner", + suite.signerOne.Addr, + ) + suite.Assert().NoError(err) + + bz, err := suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + out, err := suite.abi.Methods["isSigner"].Outputs.Unpack(bz) + suite.Assert().NoError(err) + suite.Assert().EqualValues(out[0], true) +} + +func (suite *DASignersTestSuite) queryRegisteredEpoch(testSigner *testutil.TestSigner, account common.Address, epoch *big.Int) bool { + input, err := suite.abi.Pack( + "registeredEpoch", + account, + epoch, + ) + suite.Assert().NoError(err) + + bz, err := suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + out, err := suite.abi.Methods["registeredEpoch"].Outputs.Unpack(bz) + suite.Assert().NoError(err) + return out[0].(bool) +} + +func (suite *DASignersTestSuite) queryGetQuorum(testSigner *testutil.TestSigner) []common.Address { + input, err := suite.abi.Pack( + "getQuorum", + big.NewInt(1), + big.NewInt(0), + ) + suite.Assert().NoError(err) + + bz, err := suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + out, err := suite.abi.Methods["getQuorum"].Outputs.Unpack(bz) + suite.Assert().NoError(err) + return out[0].([]common.Address) +} + +func (suite *DASignersTestSuite) queryGetQuorumRow(testSigner *testutil.TestSigner, row uint32) common.Address { + input, err := suite.abi.Pack( + "getQuorumRow", + big.NewInt(1), + big.NewInt(0), + row, + ) + suite.Assert().NoError(err) + + bz, err := suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + out, err := suite.abi.Methods["getQuorumRow"].Outputs.Unpack(bz) + suite.Assert().NoError(err) + return out[0].(common.Address) +} + +func (suite *DASignersTestSuite) queryGetAggPkG1(testSigner *testutil.TestSigner, bitmap []byte) struct { + AggPkG1 dasignersprecompile.BN254G1Point + Total *big.Int + Hit *big.Int +} { + input, err := suite.abi.Pack( + "getAggPkG1", + big.NewInt(1), + big.NewInt(0), + bitmap, + ) + suite.Assert().NoError(err) + + bz, err := suite.runTx(input, testSigner, 10000000) + suite.Assert().NoError(err) + out, err := suite.abi.Methods["getAggPkG1"].Outputs.Unpack(bz) + suite.Assert().NoError(err) + return struct { + AggPkG1 dasignersprecompile.BN254G1Point + Total *big.Int + Hit *big.Int + }{ + AggPkG1: out[0].(dasignersprecompile.BN254G1Point), + Total: out[1].(*big.Int), + Hit: out[2].(*big.Int), + } +} + +func (suite *DASignersTestSuite) Test_DASigners() { + // suite.App.InitializeFromGenesisStates() + dasigners.InitGenesis(suite.Ctx, suite.dasignerskeeper, *types.DefaultGenesisState()) + // add delegation + params := suite.dasignerskeeper.GetParams(suite.Ctx) + suite.AddDelegation(suite.signerOne.HexAddr, suite.signerOne.HexAddr, keeper.BondedConversionRate.Mul(sdk.NewIntFromUint64(params.TokensPerVote))) + suite.AddDelegation(suite.signerTwo.HexAddr, suite.signerOne.HexAddr, keeper.BondedConversionRate.Mul(sdk.NewIntFromUint64(params.TokensPerVote)).Mul(sdk.NewIntFromUint64(2))) + // tx test + signer1 := suite.registerSigner(suite.signerOne, big.NewInt(1)) + signer2 := suite.registerSigner(suite.signerTwo, big.NewInt(11)) + suite.updateSocket(suite.signerOne, signer1) + suite.updateSocket(suite.signerTwo, signer2) + suite.registerEpoch(suite.signerOne, big.NewInt(1)) + suite.registerEpoch(suite.signerTwo, big.NewInt(11)) + // move to next epoch + suite.Ctx = suite.Ctx.WithBlockHeight(int64(params.EpochBlocks) * 1) + suite.dasignerskeeper.BeginBlock(suite.Ctx, abci.RequestBeginBlock{}) + // query test + suite.queryEpochNumber(suite.signerOne) + suite.queryQuorumCount(suite.signerOne) + suite.queryGetSigner(suite.signerOne, []*types.Signer{signer1, signer2}) + suite.queryIsSigner(suite.signerOne) + suite.Assert().EqualValues(suite.queryRegisteredEpoch(suite.signerOne, suite.signerOne.Addr, big.NewInt(1)), true) + suite.Assert().EqualValues(suite.queryRegisteredEpoch(suite.signerOne, suite.signerTwo.Addr, big.NewInt(1)), true) + suite.Assert().EqualValues(suite.queryRegisteredEpoch(suite.signerOne, suite.signerOne.Addr, big.NewInt(2)), false) + suite.Assert().EqualValues(suite.queryRegisteredEpoch(suite.signerOne, suite.signerTwo.Addr, big.NewInt(0)), false) + + quorum := suite.queryGetQuorum(suite.signerOne) + suite.Assert().EqualValues(len(quorum), params.EncodedSlices) + cnt := map[common.Address]int{suite.signerOne.Addr: 0, suite.signerTwo.Addr: 0} + onePos := len(quorum) + twoPos := len(quorum) + for i, v := range quorum { + suite.Assert().EqualValues(suite.queryGetQuorumRow(suite.signerOne, uint32(i)), v) + cnt[v] += 1 + if v == suite.signerOne.Addr { + onePos = min(onePos, i) + } else { + twoPos = min(twoPos, i) + } + } + suite.Assert().EqualValues(cnt[suite.signerOne.Addr], len(quorum)/3) + suite.Assert().EqualValues(cnt[suite.signerTwo.Addr], len(quorum)*2/3) + + bitMap := make([]byte, len(quorum)/8) + bitMap[onePos/8] |= 1 << (onePos % 8) + suite.Assert().EqualValues(suite.queryGetAggPkG1(suite.signerOne, bitMap), struct { + AggPkG1 dasignersprecompile.BN254G1Point + Total *big.Int + Hit *big.Int + }{ + AggPkG1: dasignersprecompile.NewBN254G1Point(bn254util.SerializeG1(new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), big.NewInt(1)))), + Total: big.NewInt(int64(len(quorum))), + Hit: big.NewInt(int64(len(quorum) / 3)), + }) + + bitMap[twoPos/8] |= 1 << (twoPos % 8) + suite.Assert().EqualValues(suite.queryGetAggPkG1(suite.signerOne, bitMap), struct { + AggPkG1 dasignersprecompile.BN254G1Point + Total *big.Int + Hit *big.Int + }{ + AggPkG1: dasignersprecompile.NewBN254G1Point(bn254util.SerializeG1(new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), big.NewInt(1+11)))), + Total: big.NewInt(int64(len(quorum))), + Hit: big.NewInt(int64(len(quorum))), + }) + +} + +func TestKeeperSuite(t *testing.T) { + suite.Run(t, new(DASignersTestSuite)) +} diff --git a/precompiles/testutil/suite.go b/precompiles/testutil/suite.go new file mode 100644 index 00000000..1b6a9ce3 --- /dev/null +++ b/precompiles/testutil/suite.go @@ -0,0 +1,88 @@ +package testutil + +import ( + "strings" + + "github.com/0glabs/0g-chain/app" + "github.com/0glabs/0g-chain/chaincfg" + dasignersprecompile "github.com/0glabs/0g-chain/precompiles/dasigners" + "github.com/0glabs/0g-chain/x/bep3/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + emtests "github.com/evmos/ethermint/tests" + evmkeeper "github.com/evmos/ethermint/x/evm/keeper" + "github.com/evmos/ethermint/x/evm/statedb" + "github.com/stretchr/testify/suite" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +type PrecompileTestSuite struct { + suite.Suite + + StakingKeeper *stakingkeeper.Keeper + App app.TestApp + Ctx sdk.Context + QueryClient types.QueryClient + Addresses []sdk.AccAddress + + EvmKeeper *evmkeeper.Keeper + EthSigner ethtypes.Signer + Statedb *statedb.StateDB +} + +type TestSigner struct { + Addr common.Address + HexAddr string + PrivKey cryptotypes.PrivKey + Signer keyring.Signer +} + +func GenSigner() *TestSigner { + var s TestSigner + addr, priv := emtests.NewAddrKey() + s.PrivKey = priv + s.Addr = addr + s.HexAddr = dasignersprecompile.ToLowerHexWithoutPrefix(s.Addr) + s.Signer = emtests.NewSigner(priv) + return &s +} + +func (suite *PrecompileTestSuite) SetupTest() { + chaincfg.SetSDKConfig() + suite.App = app.NewTestApp() + suite.App.InitializeFromGenesisStates() + suite.StakingKeeper = suite.App.GetStakingKeeper() + + // make block header + privkey, _ := ethsecp256k1.GenerateKey() + consAddress := sdk.ConsAddress(privkey.PubKey().Address()) + key, err := privkey.ToECDSA() + suite.Assert().NoError(err) + hexAddr := strings.ToLower(crypto.PubkeyToAddress(key.PublicKey).Hex()[2:]) + valAddr, err := sdk.ValAddressFromHex(hexAddr) + suite.Assert().NoError(err) + suite.Ctx = suite.App.NewContext(true, tmproto.Header{Height: 1, ChainID: app.TestChainId, ProposerAddress: consAddress}) + newValidator, err := stakingtypes.NewValidator(valAddr, privkey.PubKey(), stakingtypes.Description{}) + suite.Assert().NoError(err) + err = suite.StakingKeeper.SetValidatorByConsAddr(suite.Ctx, newValidator) + suite.Assert().NoError(err) + suite.StakingKeeper.SetValidator(suite.Ctx, newValidator) + + _, accAddresses := app.GeneratePrivKeyAddressPairs(10) + suite.Addresses = accAddresses + + suite.EvmKeeper = suite.App.GetEvmKeeper() + + suite.EthSigner = ethtypes.LatestSignerForChainID(suite.EvmKeeper.ChainID()) + + suite.Statedb = statedb.New(suite.Ctx, suite.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.Ctx.HeaderHash().Bytes()))) +} diff --git a/x/dasigners/v1/genesis.go b/x/dasigners/v1/genesis.go index deb04433..bab56a65 100644 --- a/x/dasigners/v1/genesis.go +++ b/x/dasigners/v1/genesis.go @@ -39,7 +39,7 @@ func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState { return false }) epochQuorums := make([]*types.Quorums, 0) - for i := 0; i < int(epochNumber); i += 1 { + for i := 0; i <= int(epochNumber); i += 1 { quorumCnt, err := keeper.GetQuorumCount(ctx, uint64(i)) if err != nil { panic("historical quorums not found") diff --git a/x/dasigners/v1/genesis_test.go b/x/dasigners/v1/genesis_test.go new file mode 100644 index 00000000..7ee4e555 --- /dev/null +++ b/x/dasigners/v1/genesis_test.go @@ -0,0 +1,155 @@ +package dasigners_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/0glabs/0g-chain/app" + "github.com/0glabs/0g-chain/x/dasigners/v1" + "github.com/0glabs/0g-chain/x/dasigners/v1/testutil" + "github.com/0glabs/0g-chain/x/dasigners/v1/types" +) + +type GenesisTestSuite struct { + testutil.Suite +} + +func (suite *GenesisTestSuite) TestInitGenesis() { + // Most genesis validation tests are located in the types directory. The 'invalid' test cases are + // randomly selected subset of those tests. + testCases := []struct { + name string + genState *types.GenesisState + expectPass bool + }{ + { + name: "normal", + genState: types.DefaultGenesisState(), + expectPass: true, + }, + { + name: "normal-more-epochs", + genState: types.NewGenesisState(types.Params{ + TokensPerVote: 10, + MaxVotesPerSigner: 1024, + MaxQuorums: 10, + EpochBlocks: 5760, + EncodedSlices: 1, + }, 0, []*types.Signer{{ + Account: "0000000000000000000000000000000000000001", + Socket: "0.0.0.0:1234", + PubkeyG1: make([]byte, 64), + PubkeyG2: make([]byte, 128), + }}, []*types.Quorums{{ + Quorums: []*types.Quorum{{Signers: []string{"0000000000000000000000000000000000000001"}}}, + }}), + expectPass: true, + }, + { + name: "invalid account format", + genState: types.NewGenesisState(types.Params{ + TokensPerVote: 10, + MaxVotesPerSigner: 1024, + MaxQuorums: 10, + EpochBlocks: 5760, + EncodedSlices: 1, + }, 0, []*types.Signer{{ + Account: "0x0000000000000000000000000000000000000001", + Socket: "0.0.0.0:1234", + PubkeyG1: make([]byte, 64), + PubkeyG2: make([]byte, 128), + }}, []*types.Quorums{{ + Quorums: []*types.Quorum{{Signers: []string{"0x0000000000000000000000000000000000000001"}}}, + }}), + expectPass: false, + }, + { + name: "invalid pubkeyG1 format", + genState: types.NewGenesisState(types.Params{ + TokensPerVote: 10, + MaxVotesPerSigner: 1024, + MaxQuorums: 10, + EpochBlocks: 5760, + EncodedSlices: 1, + }, 0, []*types.Signer{{ + Account: "0000000000000000000000000000000000000001", + Socket: "0.0.0.0:1234", + PubkeyG1: make([]byte, 63), + PubkeyG2: make([]byte, 128), + }}, []*types.Quorums{{ + Quorums: []*types.Quorum{{Signers: []string{"0000000000000000000000000000000000000001"}}}, + }}), + expectPass: false, + }, + { + name: "invalid pubkeyG2 format", + genState: types.NewGenesisState(types.Params{ + TokensPerVote: 10, + MaxVotesPerSigner: 1024, + MaxQuorums: 10, + EpochBlocks: 5760, + EncodedSlices: 1, + }, 0, []*types.Signer{{ + Account: "0000000000000000000000000000000000000001", + Socket: "0.0.0.0:1234", + PubkeyG1: make([]byte, 64), + PubkeyG2: make([]byte, 129), + }}, []*types.Quorums{{ + Quorums: []*types.Quorum{{Signers: []string{"0000000000000000000000000000000000000001"}}}, + }}), + expectPass: false, + }, + { + name: "history missing", + genState: types.NewGenesisState(types.Params{ + TokensPerVote: 10, + MaxVotesPerSigner: 1024, + MaxQuorums: 10, + EpochBlocks: 5760, + EncodedSlices: 1, + }, 0, []*types.Signer{{ + Account: "0000000000000000000000000000000000000001", + Socket: "0.0.0.0:1234", + PubkeyG1: make([]byte, 64), + PubkeyG2: make([]byte, 128), + }}, []*types.Quorums{}), + expectPass: false, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + // Setup (note: suite.SetupTest is not run before every suite.Run) + suite.App = app.NewTestApp() + suite.Keeper = suite.App.GetDASignersKeeper() + suite.Ctx = suite.App.NewContext(true, tmproto.Header{}) + + // Run + var exportedGenState *types.GenesisState + run := func() { + dasigners.InitGenesis(suite.Ctx, suite.Keeper, *tc.genState) + exportedGenState = dasigners.ExportGenesis(suite.Ctx, suite.Keeper) + } + if tc.expectPass { + suite.Require().NotPanics(run) + } else { + suite.Require().Panics(run) + } + + // Check + if tc.expectPass { + expectedJson, err := suite.App.AppCodec().MarshalJSON(tc.genState) + suite.Require().NoError(err) + actualJson, err := suite.App.AppCodec().MarshalJSON(exportedGenState) + suite.Require().NoError(err) + suite.Equal(expectedJson, actualJson) + } + }) + } +} + +func TestGenesisTestSuite(t *testing.T) { + suite.Run(t, new(GenesisTestSuite)) +} diff --git a/x/dasigners/v1/keeper/abci.go b/x/dasigners/v1/keeper/abci.go index 20c30ab9..cb1bde33 100644 --- a/x/dasigners/v1/keeper/abci.go +++ b/x/dasigners/v1/keeper/abci.go @@ -72,7 +72,7 @@ func (k Keeper) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { } if len(ballots) >= int(params.EncodedSlices) { for i := 0; i+int(params.EncodedSlices) <= len(ballots); i += int(params.EncodedSlices) { - if int(params.MaxQuorums) < len(quorums.Quorums) { + if int(params.MaxQuorums) <= len(quorums.Quorums) { break } quorum := types.Quorum{ @@ -101,10 +101,6 @@ func (k Keeper) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { quorum.Signers[i] = ballots[i%n].account } quorums.Quorums = append(quorums.Quorums, &quorum) - } else { - quorums.Quorums = append(quorums.Quorums, &types.Quorum{ - Signers: make([]string, 0), - }) } // save to store diff --git a/x/dasigners/v1/keeper/abci_test.go b/x/dasigners/v1/keeper/abci_test.go new file mode 100644 index 00000000..701eb687 --- /dev/null +++ b/x/dasigners/v1/keeper/abci_test.go @@ -0,0 +1,138 @@ +package keeper_test + +import ( + "testing" + + "github.com/0glabs/0g-chain/x/dasigners/v1" + "github.com/0glabs/0g-chain/x/dasigners/v1/keeper" + "github.com/0glabs/0g-chain/x/dasigners/v1/testutil" + "github.com/0glabs/0g-chain/x/dasigners/v1/types" + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/suite" +) + +type AbciTestSuite struct { + testutil.Suite +} + +func (suite *AbciTestSuite) TestBeginBlock_NotContinuous() { + // suite.App.InitializeFromGenesisStates() + dasigners.InitGenesis(suite.Ctx, suite.Keeper, *types.DefaultGenesisState()) + params := suite.Keeper.GetParams(suite.Ctx) + suite.Require().Panics(func() { + suite.Keeper.BeginBlock(suite.Ctx.WithBlockHeight(int64(params.EpochBlocks*2)), abci.RequestBeginBlock{}) + }, "block height is not continuous") +} + +func (suite *AbciTestSuite) TestBeginBlock_Success() { + // suite.App.InitializeFromGenesisStates() + dasigners.InitGenesis(suite.Ctx, suite.Keeper, *types.DefaultGenesisState()) + suite.Keeper.SetParams(suite.Ctx, types.Params{ + TokensPerVote: 10, + MaxVotesPerSigner: 200, + MaxQuorums: 10, + EpochBlocks: 5760, + EncodedSlices: 10, + }) + params := suite.Keeper.GetParams(suite.Ctx) + epoch, err := suite.Keeper.GetEpochNumber(suite.Ctx) + suite.Require().NoError(err) + cnt, err := suite.Keeper.GetQuorumCount(suite.Ctx, epoch) + suite.Require().NoError(err) + suite.Assert().EqualValues(cnt, 0) + // set signer + suite.Keeper.SetSigner(suite.Ctx, types.Signer{ + Account: "0000000000000000000000000000000000000001", + Socket: "0.0.0.0:1234", + PubkeyG1: common.LeftPadBytes([]byte{1}, 32), + PubkeyG2: common.LeftPadBytes([]byte{2}, 64), + }) + suite.Keeper.SetRegistration(suite.Ctx, epoch+1, "0000000000000000000000000000000000000001", common.LeftPadBytes([]byte{1}, 32)) + suite.Ctx = suite.Ctx.WithBlockHeight(int64(params.EpochBlocks) * int64(epoch+1)) + suite.Keeper.BeginBlock(suite.Ctx, abci.RequestBeginBlock{}) + // check quorums + lastEpoch := epoch + epoch, err = suite.Keeper.GetEpochNumber(suite.Ctx) + suite.Require().NoError(err) + suite.Assert().EqualValues(epoch, lastEpoch+1) + cnt, err = suite.Keeper.GetQuorumCount(suite.Ctx, epoch) + suite.Require().NoError(err) + suite.Assert().EqualValues(cnt, 0) + // set delegation, 1 ballot + suite.AddDelegation("0000000000000000000000000000000000000001", "0000000000000000000000000000000000000001", keeper.BondedConversionRate.Mul(sdk.NewIntFromUint64(params.TokensPerVote))) + // set signer + suite.Keeper.SetSigner(suite.Ctx, types.Signer{ + Account: "0000000000000000000000000000000000000001", + Socket: "0.0.0.0:1234", + PubkeyG1: common.LeftPadBytes([]byte{1}, 32), + PubkeyG2: common.LeftPadBytes([]byte{2}, 64), + }) + suite.Keeper.SetRegistration(suite.Ctx, epoch+1, "0000000000000000000000000000000000000001", common.LeftPadBytes([]byte{1}, 32)) + suite.Ctx = suite.Ctx.WithBlockHeight(int64(params.EpochBlocks) * int64(epoch+1)) + suite.Keeper.BeginBlock(suite.Ctx, abci.RequestBeginBlock{}) + // check quorums + lastEpoch = epoch + epoch, err = suite.Keeper.GetEpochNumber(suite.Ctx) + suite.Require().NoError(err) + suite.Assert().EqualValues(epoch, lastEpoch+1) + cnt, err = suite.Keeper.GetQuorumCount(suite.Ctx, epoch) + suite.Require().NoError(err) + suite.Assert().EqualValues(cnt, 1) + // set delegation, 10 ballot + suite.AddDelegation( + "0000000000000000000000000000000000000001", + "0000000000000000000000000000000000000001", + keeper.BondedConversionRate.Mul(sdk.NewIntFromUint64(params.TokensPerVote)).Mul(sdk.NewIntFromUint64(9)), + ) + suite.Keeper.SetRegistration(suite.Ctx, epoch+1, "0000000000000000000000000000000000000001", common.LeftPadBytes([]byte{1}, 32)) + suite.Ctx = suite.Ctx.WithBlockHeight(int64(params.EpochBlocks) * int64(epoch+1)) + suite.Keeper.BeginBlock(suite.Ctx, abci.RequestBeginBlock{}) + // check quorums + lastEpoch = epoch + epoch, err = suite.Keeper.GetEpochNumber(suite.Ctx) + suite.Require().NoError(err) + suite.Assert().EqualValues(epoch, lastEpoch+1) + cnt, err = suite.Keeper.GetQuorumCount(suite.Ctx, epoch) + suite.Require().NoError(err) + suite.Assert().EqualValues(cnt, 1) + // set delegation, 11 ballot + suite.AddDelegation( + "0000000000000000000000000000000000000001", + "0000000000000000000000000000000000000001", + keeper.BondedConversionRate.Mul(sdk.NewIntFromUint64(params.TokensPerVote)).Mul(sdk.NewIntFromUint64(1)), + ) + suite.Keeper.SetRegistration(suite.Ctx, epoch+1, "0000000000000000000000000000000000000001", common.LeftPadBytes([]byte{1}, 32)) + suite.Ctx = suite.Ctx.WithBlockHeight(int64(params.EpochBlocks) * int64(epoch+1)) + suite.Keeper.BeginBlock(suite.Ctx, abci.RequestBeginBlock{}) + // check quorums + lastEpoch = epoch + epoch, err = suite.Keeper.GetEpochNumber(suite.Ctx) + suite.Require().NoError(err) + suite.Assert().EqualValues(epoch, lastEpoch+1) + cnt, err = suite.Keeper.GetQuorumCount(suite.Ctx, epoch) + suite.Require().NoError(err) + suite.Assert().EqualValues(cnt, 2) + // set delegation, 200 ballot + suite.AddDelegation( + "0000000000000000000000000000000000000001", + "0000000000000000000000000000000000000001", + keeper.BondedConversionRate.Mul(sdk.NewIntFromUint64(params.TokensPerVote)).Mul(sdk.NewIntFromUint64(200)), + ) + suite.Keeper.SetRegistration(suite.Ctx, epoch+1, "0000000000000000000000000000000000000001", common.LeftPadBytes([]byte{1}, 32)) + suite.Ctx = suite.Ctx.WithBlockHeight(int64(params.EpochBlocks) * int64(epoch+1)) + suite.Keeper.BeginBlock(suite.Ctx, abci.RequestBeginBlock{}) + // check quorums + lastEpoch = epoch + epoch, err = suite.Keeper.GetEpochNumber(suite.Ctx) + suite.Require().NoError(err) + suite.Assert().EqualValues(epoch, lastEpoch+1) + cnt, err = suite.Keeper.GetQuorumCount(suite.Ctx, epoch) + suite.Require().NoError(err) + suite.Assert().EqualValues(cnt, 10) +} + +func TestAbciSuite(t *testing.T) { + suite.Run(t, new(AbciTestSuite)) +} diff --git a/x/dasigners/v1/keeper/grpc_query.go b/x/dasigners/v1/keeper/grpc_query.go index 36208f44..d76d0ae9 100644 --- a/x/dasigners/v1/keeper/grpc_query.go +++ b/x/dasigners/v1/keeper/grpc_query.go @@ -24,7 +24,7 @@ func (k Keeper) Signer( return nil, err } if !found { - return nil, nil + return nil, types.ErrSignerNotFound } response.Signer[i] = &signer } diff --git a/x/dasigners/v1/keeper/keeper_test.go b/x/dasigners/v1/keeper/keeper_test.go new file mode 100644 index 00000000..fda2c698 --- /dev/null +++ b/x/dasigners/v1/keeper/keeper_test.go @@ -0,0 +1,338 @@ +package keeper_test + +import ( + "encoding/hex" + "math/big" + "strings" + "testing" + + "github.com/0glabs/0g-chain/crypto/bn254util" + "github.com/0glabs/0g-chain/x/dasigners/v1" + "github.com/0glabs/0g-chain/x/dasigners/v1/keeper" + "github.com/0glabs/0g-chain/x/dasigners/v1/testutil" + "github.com/0glabs/0g-chain/x/dasigners/v1/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/consensys/gnark-crypto/ecc/bn254" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/suite" +) + +const ( + signer1 = "9685C4EB29309820CDC62663CC6CC82F3D42E964" + signer2 = "9685C4EB29309820CDC62663CC6CC82F3D42E965" +) + +type KeeperTestSuite struct { + testutil.Suite +} + +func (suite *KeeperTestSuite) testRegisterSignerInvalidSignature() { + sk := big.NewInt(1) + pkG1 := new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), sk) + pkG2 := new(bn254.G2Affine).ScalarMultiplication(bn254util.GetG2Generator(), sk) + hash := types.PubkeyRegistrationHash(common.HexToAddress(signer1), big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, big.NewInt(2)) + msg := &types.MsgRegisterSigner{ + Signer: &types.Signer{ + Account: signer1, + Socket: "0.0.0.0:1234", + PubkeyG1: bn254util.SerializeG1(pkG1), + PubkeyG2: bn254util.SerializeG2(pkG2), + }, + Signature: bn254util.SerializeG1(signature), + } + _, err := suite.Keeper.RegisterSigner(sdk.WrapSDKContext(suite.Ctx), msg) + suite.Assert().ErrorIs(err, types.ErrInvalidSignature) +} + +func (suite *KeeperTestSuite) testRegisterSignerSuccess() *types.Signer { // resgister signer + sk := big.NewInt(1) + pkG1 := new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), sk) + pkG2 := new(bn254.G2Affine).ScalarMultiplication(bn254util.GetG2Generator(), sk) + hash := types.PubkeyRegistrationHash(common.HexToAddress(signer1), big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, sk) + signer := &types.Signer{ + Account: signer1, + Socket: "0.0.0.0:1234", + PubkeyG1: bn254util.SerializeG1(pkG1), + PubkeyG2: bn254util.SerializeG2(pkG2), + } + msg := &types.MsgRegisterSigner{ + Signer: signer, + Signature: bn254util.SerializeG1(signature), + } + oldEventNum := len(suite.Ctx.EventManager().Events()) + _, err := suite.Keeper.RegisterSigner(sdk.WrapSDKContext(suite.Ctx), msg) + suite.Assert().NoError(err) + events := suite.Ctx.EventManager().Events() + suite.Assert().EqualValues(len(events), oldEventNum+1) + suite.Assert().EqualValues(events[len(events)-1], sdk.NewEvent( + types.EventTypeUpdateSigner, + sdk.NewAttribute(types.AttributeKeySigner, signer.Account), + sdk.NewAttribute(types.AttributeKeySocket, signer.Socket), + sdk.NewAttribute(types.AttributeKeyPublicKeyG1, hex.EncodeToString(signer.PubkeyG1)), + sdk.NewAttribute(types.AttributeKeyPublicKeyG2, hex.EncodeToString(signer.PubkeyG2)), + )) + return signer +} + +func (suite *KeeperTestSuite) testQuerySigner(signer *types.Signer) { + response, err := suite.Keeper.Signer(sdk.WrapSDKContext(suite.Ctx), &types.QuerySignerRequest{ + Accounts: []string{signer1}, + }) + suite.Assert().NoError(err) + suite.Assert().EqualValues(len(response.Signer), 1) + suite.Assert().EqualValues(response.Signer[0], signer) + _, err = suite.Keeper.Signer(sdk.WrapSDKContext(suite.Ctx), &types.QuerySignerRequest{ + Accounts: []string{signer1, signer2}, + }) + suite.Assert().ErrorIs(err, types.ErrSignerNotFound) +} + +func (suite *KeeperTestSuite) testUpdateSocket(signer *types.Signer) { + signer.Socket = "0.0.0.0:2345" + msg := &types.MsgUpdateSocket{ + Account: signer2, + Socket: signer.Socket, + } + _, err := suite.Keeper.UpdateSocket(sdk.WrapSDKContext(suite.Ctx), msg) + suite.Assert().ErrorIs(err, types.ErrSignerNotFound) + msg.Account = signer.Account + oldEventNum := len(suite.Ctx.EventManager().Events()) + _, err = suite.Keeper.UpdateSocket(sdk.WrapSDKContext(suite.Ctx), msg) + suite.Assert().NoError(err, types.ErrSignerNotFound) + events := suite.Ctx.EventManager().Events() + suite.Assert().EqualValues(len(events), oldEventNum+1) + suite.Assert().EqualValues(events[len(events)-1], sdk.NewEvent( + types.EventTypeUpdateSigner, + sdk.NewAttribute(types.AttributeKeySigner, signer.Account), + sdk.NewAttribute(types.AttributeKeySocket, signer.Socket), + sdk.NewAttribute(types.AttributeKeyPublicKeyG1, hex.EncodeToString(signer.PubkeyG1)), + sdk.NewAttribute(types.AttributeKeyPublicKeyG2, hex.EncodeToString(signer.PubkeyG2)), + )) +} + +func (suite *KeeperTestSuite) testRegisterEpochInvalidSignature() { + sk := big.NewInt(2) + hash := types.EpochRegistrationHash(common.HexToAddress(signer1), 1, big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, sk) + msg := &types.MsgRegisterNextEpoch{ + Account: signer1, + Signature: bn254util.SerializeG1(signature), + } + _, err := suite.Keeper.RegisterNextEpoch(sdk.WrapSDKContext(suite.Ctx), msg) + suite.Assert().ErrorIs(err, types.ErrInvalidSignature) +} + +func (suite *KeeperTestSuite) secondSigner() *types.Signer { + sk := big.NewInt(11) + pkG1 := new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), sk) + pkG2 := new(bn254.G2Affine).ScalarMultiplication(bn254util.GetG2Generator(), sk) + hash := types.PubkeyRegistrationHash(common.HexToAddress(signer2), big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, sk) + signer := &types.Signer{ + Account: signer2, + Socket: "0.0.0.0:1234", + PubkeyG1: bn254util.SerializeG1(pkG1), + PubkeyG2: bn254util.SerializeG2(pkG2), + } + msg := &types.MsgRegisterSigner{ + Signer: signer, + Signature: bn254util.SerializeG1(signature), + } + oldEventNum := len(suite.Ctx.EventManager().Events()) + _, err := suite.Keeper.RegisterSigner(sdk.WrapSDKContext(suite.Ctx), msg) + suite.Assert().NoError(err) + events := suite.Ctx.EventManager().Events() + suite.Assert().EqualValues(len(events), oldEventNum+1) + suite.Assert().EqualValues(events[len(events)-1], sdk.NewEvent( + types.EventTypeUpdateSigner, + sdk.NewAttribute(types.AttributeKeySigner, signer.Account), + sdk.NewAttribute(types.AttributeKeySocket, signer.Socket), + sdk.NewAttribute(types.AttributeKeyPublicKeyG1, hex.EncodeToString(signer.PubkeyG1)), + sdk.NewAttribute(types.AttributeKeyPublicKeyG2, hex.EncodeToString(signer.PubkeyG2)), + )) + // register epoch + hash = types.EpochRegistrationHash(common.HexToAddress(signer2), 1, big.NewInt(8888)) + signature = new(bn254.G1Affine).ScalarMultiplication(hash, sk) + msg2 := &types.MsgRegisterNextEpoch{ + Account: signer2, + Signature: bn254util.SerializeG1(signature), + } + _, err = suite.Keeper.RegisterNextEpoch(sdk.WrapSDKContext(suite.Ctx), msg2) + suite.Assert().NoError(err, types.ErrSignerNotFound) + return signer +} + +func (suite *KeeperTestSuite) testRegisterEpochSuccess() { + sk := big.NewInt(1) + hash := types.EpochRegistrationHash(common.HexToAddress(signer1), 1, big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, sk) + msg := &types.MsgRegisterNextEpoch{ + Account: signer1, + Signature: bn254util.SerializeG1(signature), + } + _, err := suite.Keeper.RegisterNextEpoch(sdk.WrapSDKContext(suite.Ctx), msg) + suite.Assert().NoError(err, types.ErrSignerNotFound) + +} + +func (suite *KeeperTestSuite) newEpoch(params types.Params) { + // 1st ballot of signer1: 1d5df5684184f84a8dbd20b158b6478a6e8eb021b1cf81ac281dd4c7af4370ed30231e1a6a1d76bac5f464f10c7e99afa8df3c4643ca447bfc80f248764ab2ac + // 1st ballot of signer2: 103d29532b47eb7df57049180475d72737f7ab2be4a0f3614aedbb61c8a844a32c76fcbb29b937c56c577121dfd4be8041e2b4acfe2523ae54f0d6f604745b06 + // 2nd ballot of signer2: 93a5bb4c22640a155b18e24c0c584f2bc4bdd94ddb786d86ff3c3816d741e67f + // sorted ballots: 2-1, 1-1, 2-2 + suite.Ctx = suite.Ctx.WithBlockHeight(int64(params.EpochBlocks) * 1) + suite.Keeper.BeginBlock(suite.Ctx, abci.RequestBeginBlock{}) +} + +func (suite *KeeperTestSuite) queryEpochNumber() { + response, err := suite.Keeper.EpochNumber(sdk.WrapSDKContext(suite.Ctx), &types.QueryEpochNumberRequest{}) + suite.Assert().NoError(err) + suite.Assert().EqualValues(response.EpochNumber, 1) +} + +func (suite *KeeperTestSuite) queryQuorumCount() { + response, err := suite.Keeper.QuorumCount(sdk.WrapSDKContext(suite.Ctx), &types.QueryQuorumCountRequest{ + EpochNumber: 1, + }) + suite.Assert().NoError(err) + suite.Assert().EqualValues(response.QuorumCount, 1) +} + +func (suite *KeeperTestSuite) queryEpochQuorum(params types.Params) { + _, err := suite.Keeper.EpochQuorum(sdk.WrapSDKContext(suite.Ctx), &types.QueryEpochQuorumRequest{ + EpochNumber: 1, + QuorumId: 1, + }) + suite.Assert().ErrorIs(err, types.ErrQuorumIdOutOfBound) + response, err := suite.Keeper.EpochQuorum(sdk.WrapSDKContext(suite.Ctx), &types.QueryEpochQuorumRequest{ + EpochNumber: 1, + QuorumId: 0, + }) + suite.Assert().NoError(err) + quorum := make([]string, 0) + for i := 0; i < int(params.EncodedSlices); i += 1 { + if i%3 == 1 { + quorum = append(quorum, strings.ToLower(signer1)) + } else { + quorum = append(quorum, strings.ToLower(signer2)) + } + } + suite.Assert().EqualValues(response.Quorum.Signers, quorum) +} + +func (suite *KeeperTestSuite) queryEpochQuorumRow(params types.Params) { + _, err := suite.Keeper.EpochQuorumRow(sdk.WrapSDKContext(suite.Ctx), &types.QueryEpochQuorumRowRequest{ + EpochNumber: 1, + QuorumId: 0, + RowIndex: uint32(params.EncodedSlices), + }) + suite.Assert().ErrorIs(err, types.ErrRowIndexOutOfBound) + response, err := suite.Keeper.EpochQuorumRow(sdk.WrapSDKContext(suite.Ctx), &types.QueryEpochQuorumRowRequest{ + EpochNumber: 1, + QuorumId: 0, + RowIndex: 0, + }) + suite.Assert().NoError(err) + suite.Assert().EqualValues(response.Signer, strings.ToLower(signer2)) + response, err = suite.Keeper.EpochQuorumRow(sdk.WrapSDKContext(suite.Ctx), &types.QueryEpochQuorumRowRequest{ + EpochNumber: 1, + QuorumId: 0, + RowIndex: 1, + }) + suite.Assert().NoError(err) + suite.Assert().EqualValues(response.Signer, strings.ToLower(signer1)) + response, err = suite.Keeper.EpochQuorumRow(sdk.WrapSDKContext(suite.Ctx), &types.QueryEpochQuorumRowRequest{ + EpochNumber: 1, + QuorumId: 0, + RowIndex: 2, + }) + suite.Assert().NoError(err) + suite.Assert().EqualValues(response.Signer, strings.ToLower(signer2)) +} + +func (suite *KeeperTestSuite) queryAggregatePubkeyG1(params types.Params) { + quorumBitMap := make([]byte, params.EncodedSlices/8-1) + _, err := suite.Keeper.AggregatePubkeyG1(sdk.WrapSDKContext(suite.Ctx), &types.QueryAggregatePubkeyG1Request{ + EpochNumber: 1, + QuorumId: 0, + QuorumBitmap: quorumBitMap, + }) + suite.Assert().ErrorIs(err, types.ErrQuorumBitmapLengthMismatch) + quorumBitMap = append(quorumBitMap, byte(0)) + response, err := suite.Keeper.AggregatePubkeyG1(sdk.WrapSDKContext(suite.Ctx), &types.QueryAggregatePubkeyG1Request{ + EpochNumber: 1, + QuorumId: 0, + QuorumBitmap: quorumBitMap, + }) + suite.Assert().NoError(err) + pkG1 := new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), big.NewInt(0)) + suite.Assert().EqualValues(response.AggregatePubkeyG1, bn254util.SerializeG1(pkG1)) + suite.Assert().EqualValues(response.Total, params.EncodedSlices) + suite.Assert().EqualValues(response.Hit, 0) + + quorumBitMap[0] = byte(1) + response, err = suite.Keeper.AggregatePubkeyG1(sdk.WrapSDKContext(suite.Ctx), &types.QueryAggregatePubkeyG1Request{ + EpochNumber: 1, + QuorumId: 0, + QuorumBitmap: quorumBitMap, + }) + suite.Assert().NoError(err) + pkG1 = new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), big.NewInt(11)) + suite.Assert().EqualValues(response.AggregatePubkeyG1, bn254util.SerializeG1(pkG1)) + suite.Assert().EqualValues(response.Total, params.EncodedSlices) + suite.Assert().EqualValues(response.Hit, params.EncodedSlices*2/3) + + quorumBitMap[0] = byte(2) + response, err = suite.Keeper.AggregatePubkeyG1(sdk.WrapSDKContext(suite.Ctx), &types.QueryAggregatePubkeyG1Request{ + EpochNumber: 1, + QuorumId: 0, + QuorumBitmap: quorumBitMap, + }) + suite.Assert().NoError(err) + pkG1 = new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), big.NewInt(1)) + suite.Assert().EqualValues(response.AggregatePubkeyG1, bn254util.SerializeG1(pkG1)) + suite.Assert().EqualValues(response.Total, params.EncodedSlices) + suite.Assert().EqualValues(response.Hit, params.EncodedSlices/3) + + quorumBitMap[0] = byte(3) + response, err = suite.Keeper.AggregatePubkeyG1(sdk.WrapSDKContext(suite.Ctx), &types.QueryAggregatePubkeyG1Request{ + EpochNumber: 1, + QuorumId: 0, + QuorumBitmap: quorumBitMap, + }) + suite.Assert().NoError(err) + pkG1 = new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), big.NewInt(1+11)) + suite.Assert().EqualValues(response.AggregatePubkeyG1, bn254util.SerializeG1(pkG1)) + suite.Assert().EqualValues(response.Total, params.EncodedSlices) + suite.Assert().EqualValues(response.Hit, params.EncodedSlices) +} + +func (suite *KeeperTestSuite) Test_Keeper() { + // suite.App.InitializeFromGenesisStates() + dasigners.InitGenesis(suite.Ctx, suite.Keeper, *types.DefaultGenesisState()) + // add delegation + params := suite.Keeper.GetParams(suite.Ctx) + suite.AddDelegation(signer1, signer1, keeper.BondedConversionRate.Mul(sdk.NewIntFromUint64(params.TokensPerVote))) + suite.AddDelegation(signer2, signer1, keeper.BondedConversionRate.Mul(sdk.NewIntFromUint64(params.TokensPerVote)).Mul(sdk.NewIntFromUint64(2))) + // test + suite.testRegisterSignerInvalidSignature() + signerOne := suite.testRegisterSignerSuccess() + suite.testQuerySigner(signerOne) + suite.testUpdateSocket(signerOne) + suite.testRegisterEpochInvalidSignature() + suite.testRegisterEpochSuccess() + suite.secondSigner() + suite.newEpoch(params) + suite.queryEpochNumber() + suite.queryQuorumCount() + suite.queryEpochQuorum(params) + suite.queryEpochQuorumRow(params) + suite.queryAggregatePubkeyG1(params) +} + +func TestKeeperSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} diff --git a/x/dasigners/v1/testutil/suite.go b/x/dasigners/v1/testutil/suite.go new file mode 100644 index 00000000..1191cba7 --- /dev/null +++ b/x/dasigners/v1/testutil/suite.go @@ -0,0 +1,88 @@ +package testutil + +import ( + "strings" + + "cosmossdk.io/math" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/suite" + + "github.com/0glabs/0g-chain/app" + "github.com/0glabs/0g-chain/chaincfg" + "github.com/0glabs/0g-chain/x/dasigners/v1/keeper" + "github.com/0glabs/0g-chain/x/dasigners/v1/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/evmos/ethermint/crypto/ethsecp256k1" +) + +// Suite implements a test suite for the module integration tests +type Suite struct { + suite.Suite + + Keeper keeper.Keeper + StakingKeeper *stakingkeeper.Keeper + App app.TestApp + Ctx sdk.Context + QueryClient types.QueryClient + Addresses []sdk.AccAddress +} + +// SetupTest instantiates a new app, keepers, and sets suite state +func (suite *Suite) SetupTest() { + chaincfg.SetSDKConfig() + suite.App = app.NewTestApp() + suite.App.InitializeFromGenesisStates() + suite.Keeper = suite.App.GetDASignersKeeper() + suite.StakingKeeper = suite.App.GetStakingKeeper() + + // make block header + privkey, _ := ethsecp256k1.GenerateKey() + consAddress := sdk.ConsAddress(privkey.PubKey().Address()) + key, err := privkey.ToECDSA() + suite.Assert().NoError(err) + hexAddr := strings.ToLower(crypto.PubkeyToAddress(key.PublicKey).Hex()[2:]) + valAddr, err := sdk.ValAddressFromHex(hexAddr) + suite.Assert().NoError(err) + suite.Ctx = suite.App.NewContext(true, tmproto.Header{Height: 1, ChainID: app.TestChainId, ProposerAddress: consAddress}) + newValidator, err := stakingtypes.NewValidator(valAddr, privkey.PubKey(), stakingtypes.Description{}) + suite.Assert().NoError(err) + err = suite.StakingKeeper.SetValidatorByConsAddr(suite.Ctx, newValidator) + suite.Assert().NoError(err) + suite.StakingKeeper.SetValidator(suite.Ctx, newValidator) + + _, accAddresses := app.GeneratePrivKeyAddressPairs(10) + suite.Addresses = accAddresses + + // Set query client + queryHelper := suite.App.NewQueryServerTestHelper(suite.Ctx) + queryHandler := suite.Keeper + types.RegisterQueryServer(queryHelper, queryHandler) + suite.QueryClient = types.NewQueryClient(queryHelper) +} + +func (suite *Suite) AddDelegation(from string, to string, amount math.Int) { + accAddr, err := sdk.AccAddressFromHexUnsafe(from) + suite.Require().NoError(err) + valAddr, err := sdk.ValAddressFromHex(to) + suite.Require().NoError(err) + validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr) + if !found { + consPriv, err := ethsecp256k1.GenerateKey() + suite.Require().NoError(err) + newValidator, err := stakingtypes.NewValidator(valAddr, consPriv.PubKey(), stakingtypes.Description{}) + suite.Require().NoError(err) + validator = newValidator + } + validator.Tokens = validator.Tokens.Add(amount) + validator.DelegatorShares = validator.DelegatorShares.Add(amount.ToLegacyDec()) + suite.StakingKeeper.SetValidator(suite.Ctx, validator) + bonded := suite.Keeper.GetDelegatorBonded(suite.Ctx, accAddr) + suite.StakingKeeper.SetDelegation(suite.Ctx, stakingtypes.Delegation{ + DelegatorAddress: accAddr.String(), + ValidatorAddress: valAddr.String(), + Shares: bonded.Add(amount).ToLegacyDec(), + }) +} diff --git a/x/dasigners/v1/testutil/types.go b/x/dasigners/v1/testutil/types.go new file mode 100644 index 00000000..1ae3828c --- /dev/null +++ b/x/dasigners/v1/testutil/types.go @@ -0,0 +1,25 @@ +package testutil + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/assert" +) + +// Avoid cluttering test cases with long function names +func I(in int64) sdkmath.Int { return sdkmath.NewInt(in) } +func D(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } +func C(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func Cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } + +func AssertProtoMessageJSON(t *testing.T, cdc codec.Codec, expected proto.Message, actual proto.Message) { + expectedJson, err := cdc.MarshalJSON(expected) + assert.NoError(t, err) + actualJson, err := cdc.MarshalJSON(actual) + assert.NoError(t, err) + assert.Equal(t, string(expectedJson), string(actualJson)) +} diff --git a/x/dasigners/v1/types/hash_test.go b/x/dasigners/v1/types/hash_test.go new file mode 100644 index 00000000..ba731007 --- /dev/null +++ b/x/dasigners/v1/types/hash_test.go @@ -0,0 +1,22 @@ +package types_test + +import ( + "math/big" + "testing" + + "github.com/0glabs/0g-chain/x/dasigners/v1/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func Test_PubkeyRegistrationHash(t *testing.T) { + hash := types.PubkeyRegistrationHash(common.HexToAddress("0x9685C4EB29309820CDC62663CC6CC82F3D42E964"), big.NewInt(8888)) + assert.Equal(t, hash.X.String(), "17347288745752564851578145205408924577042674846071448492673629564958667746090") + assert.Equal(t, hash.Y.String(), "21456041422468658262738002909407073439935597271458862589356790821116767485654") +} + +func Test_EpochRegistrationHash(t *testing.T) { + hash := types.EpochRegistrationHash(common.HexToAddress("0x9685C4EB29309820CDC62663CC6CC82F3D42E964"), 1, big.NewInt(8888)) + assert.Equal(t, hash.X.String(), "13283083124528531674735853832182424672122091139683454761857829308708073730285") + assert.Equal(t, hash.Y.String(), "21773064143788270772276852950775943855438706734263253481317981346601766662828") +} diff --git a/x/dasigners/v1/types/msg_test.go b/x/dasigners/v1/types/msg_test.go new file mode 100644 index 00000000..d75529f3 --- /dev/null +++ b/x/dasigners/v1/types/msg_test.go @@ -0,0 +1,63 @@ +package types_test + +import ( + "math/big" + "testing" + + "github.com/0glabs/0g-chain/crypto/bn254util" + "github.com/0glabs/0g-chain/x/dasigners/v1/testutil" + "github.com/0glabs/0g-chain/x/dasigners/v1/types" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/suite" +) + +type MsgTestSuite struct { + testutil.Suite +} + +func (suite *MsgTestSuite) Test_MsgRegisterSigner() { + sk := big.NewInt(1) + pkG1 := new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), sk) + pkG2 := new(bn254.G2Affine).ScalarMultiplication(bn254util.GetG2Generator(), sk) + hash := types.PubkeyRegistrationHash(common.HexToAddress("0x9685C4EB29309820CDC62663CC6CC82F3D42E964"), big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, sk) + msg := &types.MsgRegisterSigner{ + Signer: &types.Signer{ + Account: "9685C4EB29309820CDC62663CC6CC82F3D42E964", + Socket: "0.0.0.0:1234", + PubkeyG1: bn254util.SerializeG1(pkG1), + PubkeyG2: bn254util.SerializeG2(pkG2), + }, + Signature: bn254util.SerializeG1(signature), + } + suite.Assert().EqualValues(len(msg.GetSigners()), 1) + suite.Assert().EqualValues(msg.GetSigners()[0].String(), "0g1j6zuf6efxzvzpnwxye3ucmxg9u7596ty686hna") + suite.Assert().NoError(msg.ValidateBasic()) +} + +func (suite *MsgTestSuite) Test_MsgUpdateSocket() { + msg := &types.MsgUpdateSocket{ + Account: "9685C4EB29309820CDC62663CC6CC82F3D42E964", + Socket: "0.0.0.0:1234", + } + suite.Assert().EqualValues(len(msg.GetSigners()), 1) + suite.Assert().EqualValues(msg.GetSigners()[0].String(), "0g1j6zuf6efxzvzpnwxye3ucmxg9u7596ty686hna") + suite.Assert().NoError(msg.ValidateBasic()) +} + +func (suite *MsgTestSuite) Test_MsgRegisterNextEpoch() { + hash := types.EpochRegistrationHash(common.HexToAddress("0x9685C4EB29309820CDC62663CC6CC82F3D42E964"), 1, big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, big.NewInt(1)) + msg := &types.MsgRegisterNextEpoch{ + Account: "9685C4EB29309820CDC62663CC6CC82F3D42E964", + Signature: bn254util.SerializeG1(signature), + } + suite.Assert().EqualValues(len(msg.GetSigners()), 1) + suite.Assert().EqualValues(msg.GetSigners()[0].String(), "0g1j6zuf6efxzvzpnwxye3ucmxg9u7596ty686hna") + suite.Assert().NoError(msg.ValidateBasic()) +} + +func TestSuite(t *testing.T) { + suite.Run(t, new(MsgTestSuite)) +} diff --git a/x/dasigners/v1/types/signer_test.go b/x/dasigners/v1/types/signer_test.go new file mode 100644 index 00000000..f16d3b2d --- /dev/null +++ b/x/dasigners/v1/types/signer_test.go @@ -0,0 +1,28 @@ +package types_test + +import ( + "math/big" + "testing" + + "github.com/0glabs/0g-chain/crypto/bn254util" + "github.com/0glabs/0g-chain/x/dasigners/v1/types" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func Test_ValidateSignature(t *testing.T) { + sk := big.NewInt(1) + pkG1 := new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), sk) + pkG2 := new(bn254.G2Affine).ScalarMultiplication(bn254util.GetG2Generator(), sk) + signer := types.Signer{ + Account: "9685C4EB29309820CDC62663CC6CC82F3D42E964", + Socket: "0.0.0.0:1234", + PubkeyG1: bn254util.SerializeG1(pkG1), + PubkeyG2: bn254util.SerializeG2(pkG2), + } + assert.NoError(t, signer.Validate()) + hash := types.PubkeyRegistrationHash(common.HexToAddress("0x9685C4EB29309820CDC62663CC6CC82F3D42E964"), big.NewInt(8888)) + signature := new(bn254.G1Affine).ScalarMultiplication(hash, big.NewInt(1)) + assert.Equal(t, signer.ValidateSignature(hash, signature), true) +}