diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index 66043a7f82..1235c23895 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -122,6 +122,7 @@ var ( //utils.ExtraDataFlag, configFileFlag, utils.AnnounceTxsFlag, + utils.StoreRewardFlag, } rpcFlags = []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 6051d1c179..1a6c1210e0 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -115,6 +115,10 @@ var ( Name: "announce-txs", Usage: "Always commit transactions", } + StoreRewardFlag = cli.BoolFlag{ + Name: "store-reward", + Usage: "Store reward to file", + } DataDirFlag = DirectoryFlag{ Name: "datadir", Usage: "Data directory for the databases and keystore", @@ -1081,6 +1085,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name) } + if ctx.GlobalIsSet(StoreRewardFlag.Name) { + cfg.StoreRewardFolder = filepath.Join(stack.DataDir(), "XDC", "rewards") + if _, err := os.Stat(cfg.StoreRewardFolder); os.IsNotExist(err) { + os.Mkdir(cfg.StoreRewardFolder, os.ModePerm) + } + } // Override any default configs for hard coded networks. switch { case ctx.GlobalBool(TestnetFlag.Name): diff --git a/contracts/utils.go b/contracts/utils.go index c9933b3fa0..8d916278c3 100644 --- a/contracts/utils.go +++ b/contracts/utils.go @@ -372,17 +372,17 @@ func GetCandidatesOwnerBySigner(validator *contractValidator.XDCValidator, signe } // Calculate reward for holders. -func CalculateRewardForHolders(foudationWalletAddr common.Address, validator *contractValidator.XDCValidator, state *state.StateDB, signer common.Address, calcReward *big.Int) error { +func CalculateRewardForHolders(foudationWalletAddr common.Address, validator *contractValidator.XDCValidator, state *state.StateDB, signer common.Address, calcReward *big.Int) (error, map[common.Address]*big.Int) { rewards, err := GetRewardBalancesRate(foudationWalletAddr, signer, calcReward, validator) if err != nil { - return err + return err, nil } if len(rewards) > 0 { for holder, reward := range rewards { state.AddBalance(holder, reward) } } - return nil + return nil, rewards } // Get reward balance rates for master node, founder and holders. diff --git a/core/blockchain.go b/core/blockchain.go index 3e2d74e586..ce64949008 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -141,9 +141,10 @@ type BlockChain struct { validator Validator // block and state validator interface vmConfig vm.Config - badBlocks *lru.Cache // Bad block cache - IPCEndpoint string - Client *ethclient.Client // Global ipc client instance. + badBlocks *lru.Cache // Bad block cache + IPCEndpoint string + Client *ethclient.Client // Global ipc client instance. + HookWriteRewards func(header *types.Header) } // NewBlockChain returns a fully initialised block chain using information @@ -1223,6 +1224,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty } } } + if bc.HookWriteRewards != nil { + bc.HookWriteRewards(block.Header()) + } } // Append a single chain head event if we've progressed the chain if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { @@ -1429,6 +1433,9 @@ func (bc *BlockChain) insertBlock(block *types.Block) ([]interface{}, []*types.L events = append(events, ChainHeadEvent{block}) log.Debug("New ChainHeadEvent from fetcher ", "number", block.NumberU64(), "hash", block.Hash()) } + if bc.HookWriteRewards != nil { + bc.HookWriteRewards(block.Header()) + } return events, coalescedLogs, nil } diff --git a/eth/api_backend.go b/eth/api_backend.go index 178828ec64..499653d1c6 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -18,6 +18,7 @@ package eth import ( "context" + "github.com/ethereum/go-ethereum/consensus/XDPoS" "math/big" "github.com/ethereum/go-ethereum/accounts" @@ -232,4 +233,14 @@ func (b *EthApiBackend) GetIPCClient() (*ethclient.Client, error) { func (b *EthApiBackend) GetEngine() consensus.Engine { return b.eth.engine +} + +func (s *EthApiBackend) GetRewardByHash(hash common.Hash) map[string]interface{} { + if c, ok := s.eth.Engine().(*XDPoS.XDPoS); ok { + rewards := c.GetRewards(hash) + if rewards != nil { + return rewards + } + } + return make(map[string]interface{}) } \ No newline at end of file diff --git a/eth/backend.go b/eth/backend.go index ca0f665362..1e8353635f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -19,9 +19,12 @@ package eth import ( + "encoding/json" "errors" "fmt" + "io/ioutil" "math/big" + "path/filepath" "runtime" "sync" "sync/atomic" @@ -287,10 +290,11 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } // Hook calculates reward for masternodes - c.HookReward = func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) error { + c.HookReward = func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) (error, map[string]interface{}) { client, err := eth.blockchain.GetClient() if err != nil { log.Error("Fail to connect IPC client for blockSigner", "error", err) + return err, nil } number := header.Number.Uint64() rCheckpoint := chain.Config().XDPoS.RewardCheckpoint @@ -298,6 +302,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if foudationWalletAddr == (common.Address{}) { log.Error("Foundation Wallet Address is empty", "error", foudationWalletAddr) } + rewards := make(map[string]interface{}) if number > 0 && number-rCheckpoint > 0 && foudationWalletAddr != (common.Address{}) { start := time.Now() // Get signers in blockSigner smartcontract. @@ -310,30 +315,36 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { signers, err := contracts.GetRewardForCheckpoint(chain, addr, number, rCheckpoint, client, totalSigner) if err != nil { log.Error("Fail to get signers for reward checkpoint", "error", err) + return err, nil } + rewards["signers"] = signers rewardSigners, err := contracts.CalculateRewardForSigner(chainReward, signers, *totalSigner) if err != nil { log.Error("Fail to calculate reward for signers", "error", err) + return err, nil } // Get validator. validator, err := contract.NewXDCValidator(common.HexToAddress(common.MasternodeVotingSMC), client) if err != nil { log.Error("Fail get instance of XDC Validator", "error", err) - - return err + return err, nil } // Add reward for coin holders. + voterResults := make(map[common.Address]interface{}) if len(signers) > 0 { for signer, calcReward := range rewardSigners { - err := contracts.CalculateRewardForHolders(foudationWalletAddr, validator, state, signer, calcReward) + err, rewards := contracts.CalculateRewardForHolders(foudationWalletAddr, validator, state, signer, calcReward) if err != nil { log.Error("Fail to calculate reward for holders.", "error", err) + return err, nil } + voterResults[signer] = rewards } } + rewards["rewards"] = voterResults log.Debug("Time Calculated HookReward ", "block", header.Number.Uint64(), "time", common.PrettyDuration(time.Since(start))) } - return nil + return nil, rewards } // Hook verifies masternodes set @@ -364,8 +375,28 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } return false } + eth.blockchain.HookWriteRewards = func(header *types.Header) { + if len(config.StoreRewardFolder) > 0 { + rewards := c.GetRewards(header.Hash()) + if rewards == nil { + rewards = c.GetRewards(header.HashNoValidator()) + if rewards != nil { + c.InsertRewards(header.Hash(), rewards) + } + } + if rewards == nil { + return + } + data, err := json.Marshal(rewards) + if err == nil { + err = ioutil.WriteFile(filepath.Join(config.StoreRewardFolder, header.Number.String()+"."+header.Hash().Hex()), data, 0644) + } + if err != nil { + log.Error("Error when save reward info ", "number", header.Number, "hash", header.Hash().Hex(), "err", err) + } + } + } } - return eth, nil } diff --git a/eth/config.go b/eth/config.go index 52b5c71ad7..ba6969a54b 100644 --- a/eth/config.go +++ b/eth/config.go @@ -112,6 +112,8 @@ type Config struct { // Miscellaneous options DocRoot string `toml:"-"` + + StoreRewardFolder string } type configMarshaling struct { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ad8b98805f..a3ebdec45c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -492,6 +492,17 @@ func (s *PublicBlockChainAPI) BlockNumber() *big.Int { return header.Number } +// BlockNumber returns the block number of the chain head. +func (s *PublicBlockChainAPI) GetRewardByHash(hash common.Hash) map[string]interface{} { + if c, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok { + rewards := c.GetRewards(hash) + if rewards != nil { + return rewards + } + } + return make(map[string]interface{}) +} + // GetBalance returns the amount of wei for the given address in the state of the // given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta // block numbers are also allowed. diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 9cf80d750f..3aee184e54 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -73,6 +73,7 @@ type Backend interface { CurrentBlock() *types.Block GetIPCClient() (*ethclient.Client, error) GetEngine() consensus.Engine + GetRewardByHash(hash common.Hash) map[string]interface{} } func GetAPIs(apiBackend Backend) []rpc.API { diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index e3dcf7071e..7970df2fdb 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -461,6 +461,11 @@ web3._extend({ call: 'eth_getRawTransactionByHash', params: 1 }), + new web3._extend.Method({ + name: 'getRewardByHash', + call: 'eth_getRewardByHash', + params: 1 + }), new web3._extend.Method({ name: 'getRawTransactionFromBlock', call: function(args) { diff --git a/les/api_backend.go b/les/api_backend.go index ff562b047e..7bbe47abfa 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -18,6 +18,7 @@ package les import ( "context" + "github.com/ethereum/go-ethereum/consensus/XDPoS" "math/big" "github.com/ethereum/go-ethereum/accounts" @@ -199,4 +200,13 @@ func (b *LesApiBackend) GetIPCClient() (*ethclient.Client, error) { func (b *LesApiBackend) GetEngine() consensus.Engine { return b.eth.engine -} \ No newline at end of file +} +func (s *LesApiBackend) GetRewardByHash(hash common.Hash) map[string]interface{} { + if c, ok := s.eth.Engine().(*XDPoS.XDPoS); ok { + rewards := c.GetRewards(hash) + if rewards != nil { + return rewards + } + } + return make(map[string]interface{}) +} \ No newline at end of file