diff --git a/common/constants.all.go b/common/constants.all.go index 68fe3995f8..92ae30f8c6 100644 --- a/common/constants.all.go +++ b/common/constants.all.go @@ -61,6 +61,7 @@ type constant struct { TIPV2SwitchBlock *big.Int tipXDCXMinerDisable *big.Int tipXDCXReceiverDisable *big.Int + tipUpgradeReward *big.Int eip1559Block *big.Int cancunBlock *big.Int @@ -102,6 +103,7 @@ var ( BlockNumberGas50x = MaintnetConstant.blockNumberGas50x TIPXDCXMinerDisable = MaintnetConstant.tipXDCXMinerDisable TIPXDCXReceiverDisable = MaintnetConstant.tipXDCXReceiverDisable + TIPUpgradeReward = MaintnetConstant.tipUpgradeReward Eip1559Block = MaintnetConstant.eip1559Block CancunBlock = MaintnetConstant.cancunBlock @@ -167,6 +169,7 @@ func CopyConstans(chainID uint64) { BlockNumberGas50x = c.blockNumberGas50x TIPXDCXMinerDisable = c.tipXDCXMinerDisable TIPXDCXReceiverDisable = c.tipXDCXReceiverDisable + TIPUpgradeReward = c.tipUpgradeReward Eip1559Block = c.eip1559Block CancunBlock = c.cancunBlock diff --git a/common/constants.devnet.go b/common/constants.devnet.go index c6e919f576..d26d7f0274 100644 --- a/common/constants.devnet.go +++ b/common/constants.devnet.go @@ -28,6 +28,7 @@ var DevnetConstant = constant{ TIPV2SwitchBlock: big.NewInt(1800), tipXDCXMinerDisable: big.NewInt(0), tipXDCXReceiverDisable: big.NewInt(0), + tipUpgradeReward: big.NewInt(1773000), eip1559Block: big.NewInt(0), cancunBlock: big.NewInt(1702800), diff --git a/common/constants.mainnet.go b/common/constants.mainnet.go index 3c15946435..5d46f7a12b 100644 --- a/common/constants.mainnet.go +++ b/common/constants.mainnet.go @@ -28,6 +28,7 @@ var MaintnetConstant = constant{ TIPV2SwitchBlock: big.NewInt(80370000), // Target 2nd Oct 2024 tipXDCXMinerDisable: big.NewInt(80370000), // Target 2nd Oct 2024 tipXDCXReceiverDisable: big.NewInt(80370900), // Target 2nd Oct 2024, safer to release after disable miner + tipUpgradeReward: big.NewInt(9999999999), eip1559Block: big.NewInt(9999999999), cancunBlock: big.NewInt(9999999999), diff --git a/common/constants.testnet.go b/common/constants.testnet.go index 4bf5fef8b0..4dc73524a5 100644 --- a/common/constants.testnet.go +++ b/common/constants.testnet.go @@ -28,6 +28,7 @@ var TestnetConstant = constant{ shanghaiBlock: big.NewInt(61290000), // Target 31st March 2024 tipXDCXMinerDisable: big.NewInt(61290000), // Target 31st March 2024 tipXDCXReceiverDisable: big.NewInt(66825000), // Target 26 Aug 2024 + tipUpgradeReward: big.NewInt(9999999999), eip1559Block: big.NewInt(71550000), // Target 14th Feb 2025 cancunBlock: big.NewInt(9999999999), diff --git a/consensus/tests/engine_v2_tests/helper.go b/consensus/tests/engine_v2_tests/helper.go index 89439f8119..1e099d397d 100644 --- a/consensus/tests/engine_v2_tests/helper.go +++ b/consensus/tests/engine_v2_tests/helper.go @@ -49,6 +49,15 @@ var ( acc3Addr = crypto.PubkeyToAddress(acc3Key.PublicKey) //xdc71562b71999873DB5b286dF957af199Ec94617F7 voterAddr = crypto.PubkeyToAddress(voterKey.PublicKey) //xdc5F74529C0338546f82389402a01c31fB52c6f434 chainID = int64(1337) + + protector1Key, _ = crypto.HexToECDSA("071c71a67e127fad4e901695e1b4b9ee04ae0e301d1e14474d32c45c72ce7b70") + protector1Addr = crypto.PubkeyToAddress(protector1Key.PublicKey) + protector2Key, _ = crypto.HexToECDSA("1d1e144127fad4e9016a977b97b0c89921839df052d7adc2f789034678902378") + protector2Addr = crypto.PubkeyToAddress(protector2Key.PublicKey) + observer1Key, _ = crypto.HexToECDSA("71a67e127fad4e9016a977b97b0c89921839df052d7adc2f7890346789023789") + observer1Addr = crypto.PubkeyToAddress(observer1Key.PublicKey) + observer2Key, _ = crypto.HexToECDSA("789034678902378971a67e127fad4e9016a977b97b0c89921839df052d7adc2f") + observer2Addr = crypto.PubkeyToAddress(observer2Key.PublicKey) ) func SignHashByPK(pk *ecdsa.PrivateKey, itemToSign []byte) []byte { @@ -279,6 +288,91 @@ func getMultiCandidatesBackend(t *testing.T, chainConfig *params.ChainConfig, n return contractBackend2 } +func getProtectorObserverBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend { + + // initial helper backend + contractBackendForSC := backends.NewXDCSimulatedBackend(types.GenesisAlloc{ + voterAddr: {Balance: new(big.Int).SetUint64(10000000000)}, + }, 10000000, chainConfig) + + transactOpts := bind.NewKeyedTransactor(voterKey) + + var candidates []common.Address + var caps []*big.Int + defalutCap := new(big.Int) + defalutCap.SetString("1000000000", 10) + + for i := 1; i <= 15; i++ { + addr := fmt.Sprintf("%02d", i) + candidates = append(candidates, common.StringToAddress(addr)) // StringToAddress does not exist + caps = append(caps, defalutCap) + } + candidates = append(candidates, protector1Addr, protector2Addr, observer1Addr, observer2Addr) + caps = append(caps, defalutCap, defalutCap, big.NewInt(999999), big.NewInt(999999)) // 99..9 is a small cap + + acc1Cap, acc2Cap, acc3Cap, voterCap := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + + acc1Cap.SetString("10000001", 10) + acc2Cap.SetString("10000002", 10) + acc3Cap.SetString("10000003", 10) + voterCap.SetString("1000000000", 10) + + caps = append(caps, voterCap, acc1Cap, acc2Cap, acc3Cap) + candidates = append(candidates, voterAddr, acc1Addr, acc2Addr, acc3Addr) + // create validator smart contract + validatorSCAddr, _, _, err := contractValidator.DeployXDCValidator( + transactOpts, + contractBackendForSC, + candidates, + caps, + voterAddr, // first owner, not used + big.NewInt(50000), + big.NewInt(1), + big.NewInt(99), + big.NewInt(100), + big.NewInt(100), + ) + if err != nil { + t.Fatalf("can't deploy root registry: %v", err) + } + + contractBackendForSC.Commit() // Write into database(state) + + // Prepare Code and Storage + d := time.Now().Add(1000 * time.Millisecond) + ctx, cancel := context.WithDeadline(context.Background(), d) + defer cancel() + + code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil) + storage := make(map[common.Hash]common.Hash) + f := func(key, val common.Hash) bool { + decode := []byte{} + trim := bytes.TrimLeft(val.Bytes(), "\x00") + err := rlp.DecodeBytes(trim, &decode) + if err != nil { + t.Fatalf("Failed while decode byte") + } + storage[key] = common.BytesToHash(decode) + log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String()) + return true + } + err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f) + if err != nil { + t.Fatalf("Failed while trying to read all keys from SC") + } + + // create test backend with smart contract in it + contractBackend2 := backends.NewXDCSimulatedBackend(types.GenesisAlloc{ + acc1Addr: {Balance: new(big.Int).SetUint64(10000000000)}, + acc2Addr: {Balance: new(big.Int).SetUint64(10000000000)}, + acc3Addr: {Balance: new(big.Int).SetUint64(10000000000)}, + voterAddr: {Balance: new(big.Int).SetUint64(10000000000)}, + common.MasternodeVotingSMCBinary: {Balance: new(big.Int).SetUint64(1), Code: code, Storage: storage}, // Binding the MasternodeVotingSMC with newly created 'code' for SC execution + }, 10000000, chainConfig) + + return contractBackend2 +} + func signingTxWithKey(header *types.Header, nonce uint64, privateKey *ecdsa.PrivateKey) (*types.Transaction, error) { tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.BlockSignersBinary) s := types.LatestSignerForChainID(big.NewInt(chainID)) @@ -483,7 +577,11 @@ func PrepareXDCTestBlockChainWithPenaltyForV2Engine(t *testing.T, numOfBlocks in } roundNumber := int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64() // use signer itself as penalty - block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, signer[:], nil, "") + penalty := signer[:] + if roundNumber%int64(chainConfig.XDPoS.Epoch) != 0 { + penalty = nil + } + block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, penalty, nil, "") err = blockchain.InsertBlock(block) if err != nil { @@ -564,6 +662,78 @@ func PrepareXDCTestBlockChainWith128Candidates(t *testing.T, numOfBlocks int, ch return blockchain, backend, currentBlock, signer, signFn } +// V2 concensus engine +func PrepareXDCTestBlockChainWithProtectorObserver(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*core.BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) { + // Preparation + var err error + signer, signFn, err := backends.SimulateWalletAddressAndSignFn() + if err != nil { + panic(fmt.Errorf("error while creating simulated wallet for generating singer address and signer fn: %v", err)) + } + backend := getProtectorObserverBackend(t, chainConfig) + blockchain := backend.BlockChain() + blockchain.Client = backend + + engine := blockchain.Engine().(*XDPoS.XDPoS) + + // Authorise + engine.Authorize(signer, signFn) + + currentBlock := blockchain.Genesis() + + go func() { + for range core.CheckpointCh { + checkpointChanMsg := <-core.CheckpointCh + log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg) + } + }() + + // Insert initial blocks + for i := 1; i <= numOfBlocks; i++ { + blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i) + // for v2 blocks, fill in correct coinbase + if int64(i) > chainConfig.XDPoS.V2.SwitchBlock.Int64() { + blockCoinBase = signer.Hex() + } + roundNumber := int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64() + // use observer2 as penalty and put in checkpoint block + penalty := observer2Addr[:] + if i != 900 { + penalty = nil + } + block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, penalty, nil, "f11ec19df702aa6bd9b3b2186edbc66d6b50b06334455a4a2ae8d166f28a14ff") + + if i == 900 { + fmt.Println(block.Penalties()) + } + err = blockchain.InsertBlock(block) + if err != nil { + t.Fatal(err) + } + + // First v2 block + if (int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()) == 1 { + lastv1BlockNumber := block.Header().Number.Uint64() - 1 + checkpointBlockNumber := lastv1BlockNumber - lastv1BlockNumber%chainConfig.XDPoS.Epoch + checkpointHeader := blockchain.GetHeaderByNumber(checkpointBlockNumber) + err := engine.EngineV2.Initial(blockchain, checkpointHeader) + if err != nil { + panic(err) + } + } + + currentBlock = block + } + + // Update Signer as there is no previous signer assigned + err = UpdateSigner(blockchain) + if err != nil { + t.Fatal(err) + } + + return blockchain, backend, currentBlock, signer, signFn +} + func CreateBlock(blockchain *core.BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), penalties []byte, signersKey []*ecdsa.PrivateKey, merkleRoot string) *types.Block { currentBlock := startingBlock if len(merkleRoot) == 0 { @@ -596,9 +766,6 @@ func CreateBlock(blockchain *core.BlockChain, chainConfig *params.ChainConfig, s for _, v := range masternodesFromV1LastEpoch { header.Validators = append(header.Validators, v[:]...) } - if penalties != nil { - header.Penalties = penalties - } } } else { // V1 block @@ -634,6 +801,9 @@ func CreateBlock(blockchain *core.BlockChain, chainConfig *params.ChainConfig, s copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash) } } + if penalties != nil { + header.Penalties = penalties + } block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig) if err != nil { panic(fmt.Errorf("fail to create block in test helper, %v", err)) diff --git a/consensus/tests/engine_v2_tests/reward_test.go b/consensus/tests/engine_v2_tests/reward_test.go index 55dc4104ca..6caed67cc0 100644 --- a/consensus/tests/engine_v2_tests/reward_test.go +++ b/consensus/tests/engine_v2_tests/reward_test.go @@ -59,7 +59,7 @@ func TestHookRewardV2(t *testing.T) { a, _ := big.NewInt(0).SetString("225000000000000000000", 10) assert.Zero(t, a.Cmp(r[owner])) b, _ := big.NewInt(0).SetString("25000000000000000000", 10) - assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")])) + assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr])) } header2685 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 885) header2716 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 + 16) @@ -146,18 +146,154 @@ func TestHookRewardV2SplitReward(t *testing.T) { for addr, x := range result { if addr == acc1Addr { r := x.(map[common.Address]*big.Int) - owner := state.GetCandidateOwner(parentState, acc1Addr) + owner := state.GetCandidateOwner(parentState, addr) a, _ := big.NewInt(0).SetString("149999999999999999999", 10) assert.Zero(t, a.Cmp(r[owner])) b, _ := big.NewInt(0).SetString("16666666666666666666", 10) - assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")])) + assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr])) } else if addr == signer { r := x.(map[common.Address]*big.Int) - owner := state.GetCandidateOwner(parentState, signer) + owner := state.GetCandidateOwner(parentState, addr) a, _ := big.NewInt(0).SetString("74999999999999999999", 10) assert.Zero(t, a.Cmp(r[owner])) b, _ := big.NewInt(0).SetString("8333333333333333333", 10) - assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")])) + assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr])) + } else { + assert.Fail(t, "wrong reward") } } } + +func TestHookRewardAfterUpgrade(t *testing.T) { + b, err := json.Marshal(params.TestXDPoSMockChainConfig) + assert.Nil(t, err) + configString := string(b) + + var config params.ChainConfig + err = json.Unmarshal([]byte(configString), &config) + assert.Nil(t, err) + // set switch to 1800, so that it covers 901-1799, 1800-2700 two epochs + config.XDPoS.V2.SwitchBlock.SetUint64(1800) + // set upgrade number to 0 + backup := common.TIPUpgradeReward + common.TIPUpgradeReward = big.NewInt(0) + + blockchain, _, _, signer, signFn := PrepareXDCTestBlockChainWithProtectorObserver(t, int(config.XDPoS.Epoch)*3+10, &config) + + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + hooks.AttachConsensusV2Hooks(adaptor, blockchain, &config) + assert.NotNil(t, adaptor.EngineV2.HookReward) + // forcely insert signing tx into cache, to give rewards. + header915 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 15) + header916 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 16) + header1785 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 15) + header1799 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 1) + header1801 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 1) + tx, err := signingTxWithSignerFn(header915, 0, signer, signFn) + assert.Nil(t, err) + adaptor.CacheSigningTxs(header916.Hash(), []*types.Transaction{tx}) + tx2, err := signingTxWithKey(header915, 0, acc1Key) + assert.Nil(t, err) + tx3, err := signingTxWithKey(header1785, 0, acc1Key) + assert.Nil(t, err) + tx4, err := signingTxWithKey(header1785, 0, protector1Key) + assert.Nil(t, err) + tx5, err := signingTxWithKey(header1785, 0, observer1Key) + assert.Nil(t, err) + tx6, err := signingTxWithKey(header915, 0, protector2Key) + assert.Nil(t, err) + tx7, err := signingTxWithKey(header1785, 0, protector2Key) + assert.Nil(t, err) + tx8, err := signingTxWithKey(header1785, 0, observer2Key) + assert.Nil(t, err) + adaptor.CacheSigningTxs(header1799.Hash(), []*types.Transaction{tx2, tx3, tx4, tx5, tx6, tx7, tx8}) + + statedb, err := blockchain.StateAt(header1799.Root) + assert.Nil(t, err) + parentState := statedb.Copy() + reward, err := adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header1801) + assert.Nil(t, err) + assert.Zero(t, len(reward)) + header2699 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - 1) + header2700 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3) + statedb, err = blockchain.StateAt(header2699.Root) + assert.Nil(t, err) + parentState = statedb.Copy() + reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header2700) + assert.Nil(t, err) + result := reward["rewards"].(map[common.Address]interface{}) + assert.Equal(t, 2, len(result)) + // two signing account, 3 txs, reward is split by 1:2 (total reward is 250...000) + for addr, x := range result { + if addr == acc1Addr { + r := x.(map[common.Address]*big.Int) + owner := state.GetCandidateOwner(parentState, addr) + a, _ := big.NewInt(0).SetString("299999999999999999998", 10) + assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner]) + b, _ := big.NewInt(0).SetString("33333333333333333333", 10) + assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr]) + } else if addr == signer { + r := x.(map[common.Address]*big.Int) + owner := state.GetCandidateOwner(parentState, addr) + a, _ := big.NewInt(0).SetString("149999999999999999999", 10) + assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner]) + b, _ := big.NewInt(0).SetString("16666666666666666666", 10) + assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr]) + } else { + assert.Fail(t, "wrong reward") + } + } + + // 5 master nodes inside header are: + //xdc703c4b2bD70c169f5717101CaeE543299Fc946C7 + //xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e + //xdc71562b71999873DB5b286dF957af199Ec94617F7 + //xdc5F74529C0338546f82389402a01c31fB52c6f434 + //signer + + // 20 master nodes candidate inside XDCValidator contract are: + //xdc703c4b2bD70c169f5717101CaeE543299Fc946C7 + //xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e + //xdc71562b71999873DB5b286dF957af199Ec94617F7 + //xdc5F74529C0338546f82389402a01c31fB52c6f434 + // and xdc00...01, xdc00...02, ..., protector1 protector2 observer1 observer2 + // so xdc00...01, xdc00...02, ..., protector1 protector2 are protectors + // only protector1 and 2 has signingtx. + + resultProtector := reward["rewardsProtector"].(map[common.Address]interface{}) + // 2 protector and split by 1:2 + assert.Equal(t, 2, len(resultProtector)) + for addr, x := range resultProtector { + if addr == protector1Addr { + r := x.(map[common.Address]*big.Int) + owner := state.GetCandidateOwner(parentState, addr) + a, _ := big.NewInt(0).SetString("119999999999999999999", 10) + assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner]) + b, _ := big.NewInt(0).SetString("13333333333333333333", 10) + assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr]) + } else if addr == protector2Addr { + r := x.(map[common.Address]*big.Int) + owner := state.GetCandidateOwner(parentState, addr) + a, _ := big.NewInt(0).SetString("239999999999999999999", 10) + assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner]) + b, _ := big.NewInt(0).SetString("26666666666666666666", 10) + assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr]) + } else { + assert.Fail(t, "wrong reward") + } + + } + resultObserver := reward["rewardsObserver"].(map[common.Address]interface{}) + // observer1 and it signs one tx, observer2 is inside penalty so no reward + assert.Equal(t, 1, len(resultObserver)) + for addr, x := range resultObserver { + assert.Equal(t, addr, observer1Addr) + r := x.(map[common.Address]*big.Int) + owner := state.GetCandidateOwner(parentState, addr) + a, _ := big.NewInt(0).SetString("270000000000000000000", 10) + assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner]) + b, _ := big.NewInt(0).SetString("30000000000000000000", 10) + assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr]) + } + common.TIPUpgradeReward = backup +} diff --git a/eth/hooks/engine_v2_hooks.go b/eth/hooks/engine_v2_hooks.go index e549802a10..17f71fd176 100644 --- a/eth/hooks/engine_v2_hooks.go +++ b/eth/hooks/engine_v2_hooks.go @@ -6,8 +6,10 @@ import ( "time" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/common/sort" "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/contracts" "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/state" @@ -17,6 +19,21 @@ import ( "github.com/XinFinOrg/XDPoSChain/params" ) +// Declaring an enum type Beneficiary of reward +type Beneficiary int + +// Enumerating reward beneficiary +const ( + MasterNodeBeneficiary Beneficiary = iota + ProtectorNodeBeneficiary + ObserverNodeBeneficiary +) + +type RewardLog struct { + Sign uint64 `json:"sign"` + Reward *big.Int `json:"reward"` +} + func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConfig *params.ChainConfig) { // Hook scans for bad masternodes and decide to penalty them adaptor.EngineV2.HookPenalty = func(chain consensus.ChainReader, number *big.Int, currentHash common.Hash, candidates []common.Address) ([]common.Address, error) { @@ -165,61 +182,138 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf log.Error("Foundation Wallet Address is empty", "error", foundationWalletAddr) return nil, errors.New("foundation wallet address is empty") } - rewards := make(map[string]interface{}) + rewardsMap := make(map[string]interface{}) // skip hook reward if this is the first v2 if number == chain.Config().XDPoS.V2.SwitchBlock.Uint64()+1 { - return rewards, nil + return rewardsMap, nil } start := time.Now() - // Get reward inflation. - chainReward := new(big.Int).Mul(new(big.Int).SetUint64(chain.Config().XDPoS.Reward), new(big.Int).SetUint64(params.Ether)) - chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear) + round, err := adaptor.EngineV2.GetRoundNumber(header) + if err != nil { + log.Error("[HookReward] Fail to get round", "error", err) + return nil, err + } + currentConfig := chain.Config().XDPoS.V2.Config(uint64(round)) // Get signers/signing tx count - totalSigner := new(uint64) - signers, err := GetSigningTxCount(adaptor, chain, header, totalSigner) + signers, err := GetSigningTxCount(adaptor, chain, header, parentState, currentConfig) log.Debug("Time Get Signers", "block", header.Number.Uint64(), "time", common.PrettyDuration(time.Since(start))) if err != nil { log.Error("[HookReward] Fail to get signers count for reward checkpoint", "error", err) return nil, err } - rewards["signers"] = signers - rewardSigners, err := contracts.CalculateRewardForSigner(chainReward, signers, *totalSigner) - if err != nil { - log.Error("[HookReward] Fail to calculate reward for signers", "error", err) - return nil, err + rewardsMap["signers"] = signers[MasterNodeBeneficiary] + + rewardSigners := make(map[common.Address]*big.Int) + rewardSignersProtector := make(map[common.Address]*big.Int) + rewardSignersObserver := make(map[common.Address]*big.Int) + if !chain.Config().IsTIPUpgradeReward(header.Number) { + // Get reward inflation. + chainReward := new(big.Int).Mul(new(big.Int).SetUint64(chain.Config().XDPoS.Reward), new(big.Int).SetUint64(params.Ether)) + chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear) + rewardSigners, err = CalculateRewardForSigner(chainReward, signers[MasterNodeBeneficiary]) + if err != nil { + log.Error("[HookReward] Fail to calculate reward for masternode", "error", err) + return nil, err + } + } else { + rewardsMap["signersProtector"] = signers[ProtectorNodeBeneficiary] + rewardsMap["signersObserver"] = signers[ObserverNodeBeneficiary] + // Masternode rewards + chainReward := new(big.Int).Mul(new(big.Int).SetUint64(currentConfig.MasternodeReward), new(big.Int).SetUint64(params.Ether)) + chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear) + rewardSigners, err = CalculateRewardForSigner(chainReward, signers[MasterNodeBeneficiary]) + if err != nil { + log.Error("[HookReward] Fail to calculate reward for masternode", "error", err) + return nil, err + } + + // Protector rewards + chainReward = new(big.Int).Mul(new(big.Int).SetUint64(currentConfig.ProtectorReward), new(big.Int).SetUint64(params.Ether)) + chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear) + rewardSignersProtector, err = CalculateRewardForSigner(chainReward, signers[ProtectorNodeBeneficiary]) + if err != nil { + log.Error("[HookReward] Fail to calculate reward for protector", "error", err) + return nil, err + } + + // Observer rewards + chainReward = new(big.Int).Mul(new(big.Int).SetUint64(currentConfig.ObserverReward), new(big.Int).SetUint64(params.Ether)) + chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear) + rewardSignersObserver, err = CalculateRewardForSigner(chainReward, signers[ObserverNodeBeneficiary]) + if err != nil { + log.Error("[HookReward] Fail to calculate reward for observer", "error", err) + return nil, err + } } // Add reward for coin holders. voterResults := make(map[common.Address]interface{}) - if len(signers) > 0 { - for signer, calcReward := range rewardSigners { - rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number) - if err != nil { - log.Error("[HookReward] Fail to calculate reward for holders.", "error", err) - return nil, err - } - if len(rewards) > 0 { - for holder, reward := range rewards { - stateBlock.AddBalance(holder, reward) - } - } - voterResults[signer] = rewards + for signer, calcReward := range rewardSigners { + rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number) + if err != nil { + log.Error("[HookReward] Fail to calculate reward for holders.", "error", err) + return nil, err } + if len(rewards) > 0 { + for holder, reward := range rewards { + stateBlock.AddBalance(holder, reward) + } + } + voterResults[signer] = rewards + } + rewardsMap["rewards"] = voterResults + + voterResultsProtector := make(map[common.Address]interface{}) + for signer, calcReward := range rewardSignersProtector { + rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number) + if err != nil { + log.Error("[HookReward] Fail to calculate reward for holders.", "error", err) + return nil, err + } + if len(rewards) > 0 { + for holder, reward := range rewards { + stateBlock.AddBalance(holder, reward) + } + } + voterResultsProtector[signer] = rewards + } + if len(voterResultsProtector) > 0 { + rewardsMap["rewardsProtector"] = voterResultsProtector + } + voterResultsObserver := make(map[common.Address]interface{}) + for signer, calcReward := range rewardSignersObserver { + rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number) + if err != nil { + log.Error("[HookReward] Fail to calculate reward for holders.", "error", err) + return nil, err + } + if len(rewards) > 0 { + for holder, reward := range rewards { + stateBlock.AddBalance(holder, reward) + } + } + voterResultsObserver[signer] = rewards + } + if len(voterResultsObserver) > 0 { + rewardsMap["rewardsObserver"] = voterResultsObserver } - rewards["rewards"] = voterResults log.Debug("Time Calculated HookReward ", "block", header.Number.Uint64(), "time", common.PrettyDuration(time.Since(start))) - return rewards, nil + return rewardsMap, nil } } // get signing transaction sender count -func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, totalSigner *uint64) (map[common.Address]*contracts.RewardLog, error) { +func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, parentState *state.StateDB, currentConfig *params.V2Config) (map[Beneficiary]map[common.Address]*RewardLog, error) { // header should be a new epoch switch block number := header.Number.Uint64() rewardEpochCount := 2 signEpochCount := 1 - signers := make(map[common.Address]*contracts.RewardLog) + signers := make(map[Beneficiary]map[common.Address]*RewardLog) + signers[MasterNodeBeneficiary] = make(map[common.Address]*RewardLog) + signers[ProtectorNodeBeneficiary] = make(map[common.Address]*RewardLog) + signers[ObserverNodeBeneficiary] = make(map[common.Address]*RewardLog) + mapBlkHash := map[uint64]common.Hash{} // prevent overflow @@ -229,32 +323,75 @@ func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *type data := make(map[common.Hash][]common.Address) epochCount := 0 - var masternodes []common.Address var startBlockNumber, endBlockNumber uint64 + + nodesToKeep := make(map[Beneficiary][]common.Address) + + h := header for i := number - 1; ; i-- { - header = chain.GetHeader(header.ParentHash, i) - isEpochSwitch, _, err := c.IsEpochSwitch(header) + h = chain.GetHeader(h.ParentHash, i) + isEpochSwitch, _, err := c.IsEpochSwitch(h) if err != nil { return nil, err } if isEpochSwitch && i != chain.Config().XDPoS.V2.SwitchBlock.Uint64()+1 { epochCount += 1 if epochCount == signEpochCount { - endBlockNumber = header.Number.Uint64() - 1 + endBlockNumber = h.Number.Uint64() - 1 } if epochCount == rewardEpochCount { - startBlockNumber = header.Number.Uint64() + 1 - masternodes = c.GetMasternodesFromCheckpointHeader(header) + startBlockNumber = h.Number.Uint64() + 1 + nodesToKeep[MasterNodeBeneficiary] = c.GetMasternodesFromCheckpointHeader(h) + // in reward upgrade, add protector and observer nodes + if chain.Config().IsTIPUpgradeReward(header.Number) { + candidates := state.GetCandidates(parentState) + var ms []utils.Masternode + for _, candidate := range candidates { + // ignore "0x0000000000000000000000000000000000000000" + if !candidate.IsZero() { + v := state.GetCandidateCap(parentState, candidate) + ms = append(ms, utils.Masternode{Address: candidate, Stake: v}) + } + } + sort.Slice(ms, func(i, j int) bool { + return ms[i].Stake.Cmp(ms[j].Stake) >= 0 + }) + // find penalty and filter them out + penalties := common.ExtractAddressFromBytes(h.Penalties) + filterMap := make(map[common.Address]struct{}) + for _, addr := range penalties { + filterMap[addr] = struct{}{} + } + for _, addr := range nodesToKeep[MasterNodeBeneficiary] { + filterMap[addr] = struct{}{} + } + // find top candidates + // maxMNP := currentConfig.MaxMasternodes + currentConfig.MaxProtectorNodes + protector := []common.Address{} + observer := []common.Address{} + for _, node := range ms { + if _, ok := filterMap[node.Address]; ok { + continue + } + if len(protector) < currentConfig.MaxProtectorNodes { + protector = append(protector, node.Address) + } else { + observer = append(observer, node.Address) + } + } + nodesToKeep[ProtectorNodeBeneficiary] = protector + nodesToKeep[ObserverNodeBeneficiary] = observer + } break } } - mapBlkHash[i] = header.Hash() - signingTxs, ok := c.GetCachedSigningTxs(header.Hash()) + mapBlkHash[i] = h.Hash() + signingTxs, ok := c.GetCachedSigningTxs(h.Hash()) if !ok { - log.Debug("Failed get from cached", "hash", header.Hash().String(), "number", i) - block := chain.GetBlock(header.Hash(), i) + log.Debug("Failed get from cached", "hash", h.Hash().String(), "number", i) + block := chain.GetBlock(h.Hash(), i) txs := block.Transactions() - signingTxs = c.CacheSigningTxs(header.Hash(), txs) + signingTxs = c.CacheSigningTxs(h.Hash(), txs) } for _, tx := range signingTxs { blkHash := common.BytesToHash(tx.Data()[len(tx.Data())-32:]) @@ -272,26 +409,35 @@ func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *type addrs := data[mapBlkHash[i]] // Filter duplicate address. if len(addrs) > 0 { - addrSigners := make(map[common.Address]bool) - for _, masternode := range masternodes { - for _, addr := range addrs { - if addr == masternode { - if _, ok := addrSigners[addr]; !ok { - addrSigners[addr] = true + addrSigners := make(map[Beneficiary]map[common.Address]bool) + addrSigners[MasterNodeBeneficiary] = make(map[common.Address]bool) + addrSigners[ProtectorNodeBeneficiary] = make(map[common.Address]bool) + addrSigners[ObserverNodeBeneficiary] = make(map[common.Address]bool) + + for _, addr := range addrs { + for _, beneficiary := range []Beneficiary{MasterNodeBeneficiary, ProtectorNodeBeneficiary, ObserverNodeBeneficiary} { + if _, ok := nodesToKeep[beneficiary]; ok { + for _, protector := range nodesToKeep[beneficiary] { + if addr == protector { + if _, ok := addrSigners[beneficiary][addr]; !ok { + addrSigners[beneficiary][addr] = true + } + break + } } - break } } } - for addr := range addrSigners { - _, exist := signers[addr] - if exist { - signers[addr].Sign++ - } else { - signers[addr] = &contracts.RewardLog{Sign: 1, Reward: new(big.Int)} + for _, beneficiary := range []Beneficiary{MasterNodeBeneficiary, ProtectorNodeBeneficiary, ObserverNodeBeneficiary} { + for addr := range addrSigners[beneficiary] { + _, exist := signers[beneficiary][addr] + if exist { + signers[beneficiary][addr].Sign++ + } else { + signers[beneficiary][addr] = &RewardLog{Sign: 1, Reward: new(big.Int)} + } } - *totalSigner++ } } } @@ -301,3 +447,31 @@ func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *type return signers, nil } + +// Calculate reward for signers. +func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*RewardLog) (map[common.Address]*big.Int, error) { + totalSignerCount := uint64(0) + for _, rLog := range signers { + totalSignerCount += rLog.Sign + } + resultSigners := make(map[common.Address]*big.Int) + // Add reward for signers. + if totalSignerCount > 0 { + for signer, rLog := range signers { + // Add reward for signer. + calcReward := new(big.Int) + calcReward.Div(chainReward, new(big.Int).SetUint64(totalSignerCount)) + calcReward.Mul(calcReward, new(big.Int).SetUint64(rLog.Sign)) + rLog.Reward = calcReward + + resultSigners[signer] = calcReward + } + } + + log.Info("Signers data", "totalSigner", totalSignerCount, "totalReward", chainReward) + for addr, signer := range signers { + log.Debug("Signer reward", "signer", addr, "sign", signer.Sign, "reward", signer.Reward) + } + + return resultSigners, nil +} diff --git a/params/config.go b/params/config.go index 67007c7871..a24d848865 100644 --- a/params/config.go +++ b/params/config.go @@ -118,6 +118,22 @@ var ( TimeoutPeriod: 5, MinePeriod: 2, ExpTimeoutConfig: ExpTimeoutConfig{Base: 2.0, MaxExponent: 5}, + MasternodeReward: 5000, + ProtectorReward: 4000, + ObserverReward: 1000, + }, + 9999999999: { + MaxMasternodes: 15, + MaxProtectorNodes: 2, + SwitchRound: 9999999999, + CertThreshold: 0.667, + TimeoutSyncThreshold: 3, + TimeoutPeriod: 5, + MinePeriod: 2, + ExpTimeoutConfig: ExpTimeoutConfig{Base: 2.0, MaxExponent: 5}, + MasternodeReward: 5000, + ProtectorReward: 4000, + ObserverReward: 1000, }, } @@ -142,12 +158,16 @@ var ( }, 900: { MaxMasternodes: 20, + MaxProtectorNodes: 17, SwitchRound: 900, CertThreshold: 0.667, TimeoutSyncThreshold: 4, TimeoutPeriod: 5, MinePeriod: 2, ExpTimeoutConfig: ExpTimeoutConfig{Base: 1.0, MaxExponent: 0}, + MasternodeReward: 500, // double as Reward + ProtectorReward: 400, + ObserverReward: 300, }, } @@ -421,12 +441,17 @@ type V2 struct { type V2Config struct { MaxMasternodes int `json:"maxMasternodes"` // v2 max masternodes + MaxProtectorNodes int `json:"maxProtectorNodes"` // v2 max ProtectorNodes SwitchRound uint64 `json:"switchRound"` // v1 to v2 switch block number MinePeriod int `json:"minePeriod"` // Miner mine period to mine a block TimeoutSyncThreshold int `json:"timeoutSyncThreshold"` // send syncInfo after number of timeout TimeoutPeriod int `json:"timeoutPeriod"` // Duration in ms CertThreshold float64 `json:"certificateThreshold"` // Necessary number of messages from master nodes to form a certificate + MasternodeReward uint64 `json:"masternodeReward"` // Block reward for master nodes (core validators) - unit Ether + ProtectorReward uint64 `json:"protectorReward"` // Block reward for protectors - unit Ether + ObserverReward uint64 `json:"observerReward"` // Block reward for observer - unit Ether + ExpTimeoutConfig ExpTimeoutConfig `json:"expTimeoutConfig"` } @@ -723,6 +748,10 @@ func (c *ChainConfig) IsTIPXDCXCancellationFee(num *big.Int) bool { return isForked(common.TIPXDCXCancellationFee, num) } +func (c *ChainConfig) IsTIPUpgradeReward(num *big.Int) bool { + return isForked(common.TIPUpgradeReward, num) +} + // GasTable returns the gas table corresponding to the current phase (homestead or homestead reprice). // // The returned GasTable's fields shouldn't, under any circumstances, be changed.