diff --git a/blockchain/system/permissionless.go b/blockchain/system/permissionless.go index bcf430d18..f8b7ccc6a 100644 --- a/blockchain/system/permissionless.go +++ b/blockchain/system/permissionless.go @@ -43,6 +43,10 @@ import ( // DefaultEpochBlockInterval is the default number of blocks per epoch for ABv2. const DefaultEpochBlockInterval = int64(params.DefaultVRankEpoch) +// initialLockup mirrors CnStakingV4Factory.INITIAL_LOCKUP, which the factory +// stakes as dead shares when deploying with public delegation. +const initialLockup = int64(1e9) + // AllocPermissionlessConfig holds parameters for genesis permissionless allocation. type AllocPermissionlessConfig struct { Owner common.Address // Owner of beacons and Registry registrant. @@ -52,6 +56,7 @@ type AllocPermissionlessConfig struct { StakeAmts []*big.Int // Stake amounts per validator. DataConfig abv2data.IABv2DataContractInitData // ABv2DataContract constructor data. EpochBlockInterval int64 // Blocks per epoch baked into ABv2 bytecode. 0 uses DefaultEpochBlockInterval. + UsePD bool // Deploy every validator's CnStaking with a PublicDelegation. } // allocPermissionlessResult holds intermediate deployed addresses passed between internal steps. @@ -103,8 +108,12 @@ func allocPermissionless(config *AllocPermissionlessConfig, installABv2 bool) (m if common.EmptyAddress(deployer) { deployer = config.Owner } + lockup := new(big.Int) + if config.UsePD { + lockup = big.NewInt(initialLockup) + } for i, amt := range config.StakeAmts { - statedb.AddBalance(config.NodeInfos[i].Manager, amt) + statedb.AddBalance(config.NodeInfos[i].Manager, new(big.Int).Add(amt, lockup)) } cfg := &runtime.Config{ @@ -274,10 +283,40 @@ func deployCnStakingFactory(cfg *runtime.Config, owner common.Address, activatio func deployCnStakingPerValidator(cfg *runtime.Config, config *AllocPermissionlessConfig, result *allocPermissionlessResult) error { factoryABI, _ := cnstakingv4factory.CnStakingV4FactoryMetaData.GetAbi() cnStakingABI, _ := cnstakingv4.CnStakingV4MetaData.GetAbi() + pdABI, _ := pdcontract.PublicDelegationMetaData.GetAbi() for i := range config.NodeIds { - cfg.Origin = config.NodeInfos[i].Manager - retData, err := evmCallABIReturn(cfg, result.factory, factoryABI, "deployCnStaking", config.NodeInfos[i].Manager) + manager := config.NodeInfos[i].Manager + cfg.Origin = manager + + if config.UsePD { + cfg.Value = big.NewInt(initialLockup) + retData, err := evmCallABIReturn(cfg, result.factory, factoryABI, "deployCnStakingWithPD", manager, + cnstakingv4factory.IPublicDelegationPDConstructorArgs{ + Owner: manager, + CommissionTo: manager, + CommissionRate: new(big.Int), + GcName: fmt.Sprintf("GC%d", i+1), + }) + if err != nil { + return fmt.Errorf("deployCnStakingWithPD[%d]: %w", i, err) + } + proxyAddr := common.BytesToAddress(retData[12:32]) + pdAddr := common.BytesToAddress(retData[44:64]) + + cfg.Value = config.StakeAmts[i] + if err := evmCallABI(cfg, pdAddr, pdABI, "stake"); err != nil { + return fmt.Errorf("stake[%d]: %w", i, err) + } + cfg.Value = new(big.Int) + + config.NodeInfos[i].StakingContract = proxyAddr + // ABv2 requires a PD-backed node to pay its rewards to that PD. + config.NodeInfos[i].RewardAddress = pdAddr + continue + } + + retData, err := evmCallABIReturn(cfg, result.factory, factoryABI, "deployCnStaking", manager) if err != nil { return fmt.Errorf("deployCnStaking[%d]: %w", i, err) } diff --git a/blockchain/system/permissionless_test.go b/blockchain/system/permissionless_test.go index c73bcb0ac..9bcafb391 100644 --- a/blockchain/system/permissionless_test.go +++ b/blockchain/system/permissionless_test.go @@ -27,6 +27,7 @@ import ( "github.com/kaiachain/kaia/common" abv2data "github.com/kaiachain/kaia/contracts/bindings/abv2data" addressbookv2contract "github.com/kaiachain/kaia/contracts/bindings/addressbookv2" + cnstakingv4 "github.com/kaiachain/kaia/contracts/bindings/cnstakingv4" "github.com/kaiachain/kaia/crypto" "github.com/kaiachain/kaia/crypto/bls" "github.com/kaiachain/kaia/kaiax/valset" @@ -213,6 +214,34 @@ func TestAllocPermissionless_SingleValidator(t *testing.T) { verifyPermissionlessAlloc(t, config, alloc) } +func TestAllocPermissionless_UsePD(t *testing.T) { + log.EnableLogForTest(log.LvlCrit, log.LvlWarn) + config := makeTestPermissionlessConfig(4) + config.UsePD = true + + alloc, err := AllocPermissionless(config) + require.NoError(t, err) + + // One PD proxy per validator on top of what the non-PD alloc holds. + assert.Len(t, alloc, 10+2*len(config.NodeIds)) + + backend := backends.NewSimulatedBackend(blockchain.GenesisAlloc(alloc)) + for i, info := range config.NodeInfos { + caller, err := cnstakingv4.NewCnStakingV4Caller(info.StakingContract, backend) + require.NoError(t, err) + pd, err := caller.PublicDelegation(nil) + require.NoError(t, err) + + assert.NotEqual(t, common.Address{}, pd, "node[%d] must be deployed with a PD", i) + assert.Equal(t, pd, info.RewardAddress, "node[%d] must pay its rewards to its own PD", i) + require.Contains(t, alloc, pd) + assert.NotEmpty(t, alloc[pd].Code) + + want := new(big.Int).Add(config.StakeAmts[i], big.NewInt(initialLockup)) + assert.Equal(t, want, alloc[info.StakingContract].Balance, "node[%d] stake plus the factory lockup", i) + } +} + func TestAllocPermissionlessPrerequisites(t *testing.T) { log.EnableLogForTest(log.LvlCrit, log.LvlWarn) config := makeTestPermissionlessConfig(4) diff --git a/cmd/homi/setup/cmd.go b/cmd/homi/setup/cmd.go index f74d5e699..98312ae03 100644 --- a/cmd/homi/setup/cmd.go +++ b/cmd/homi/setup/cmd.go @@ -150,6 +150,7 @@ var HomiFlags = []cli.Flag{ altsrc.NewInt64Flag(osakaCompatibleBlockNumberFlag), altsrc.NewInt64Flag(permissionlessCompatibleBlockNumberFlag), altsrc.NewUint64Flag(vrankEpochFlag), + altsrc.NewBoolFlag(publicDelegationFlag), altsrc.NewInt64Flag(pfsThresholdFlag), altsrc.NewInt64Flag(cfsThresholdFlag), altsrc.NewStringFlag(permissionlessGenesisDeployerFlag), @@ -624,6 +625,7 @@ func makePermissionlessConfig(ctx *cli.Context, validatorAddrs []common.Address, NodeInfos: nodeInfos, StakeAmts: stakeAmts, EpochBlockInterval: int64(ctx.Uint64(vrankEpochFlag.Name)), + UsePD: ctx.Bool(publicDelegationFlag.Name), DataConfig: abv2data.IABv2DataContractInitData{ InitialOwner: owner, InitialSuspender: owner, diff --git a/cmd/homi/setup/flags.go b/cmd/homi/setup/flags.go index 83d119964..a19820ee7 100644 --- a/cmd/homi/setup/flags.go +++ b/cmd/homi/setup/flags.go @@ -563,6 +563,12 @@ var ( Aliases: []string{"genesis.vrank-epoch"}, } + publicDelegationFlag = &cli.BoolFlag{ + Name: "public-delegation", + Usage: "deploy each validator's CnStaking with a PublicDelegation in permissionless mode", + Aliases: []string{"genesis.public-delegation"}, + } + pfsThresholdFlag = &cli.Int64Flag{ Name: "pfs-threshold", Usage: "PFS threshold for severe violation in permissionless mode (default: 2)",