From 64defd91ef4a0e1c5088b1d5892972e8a23e2429 Mon Sep 17 00:00:00 2001 From: anilchinchawale Date: Wed, 28 Jan 2026 21:31:18 +0100 Subject: [PATCH] Add XDC APIs, contracts, and header fields - Add XDPoS header fields (Validators, Validator, Penalties) to block.go - Add XDC constants and contract addresses to common/types.go - Add consensus/XDPoS/utils package with types and utilities - Add contracts/randomize contract binding - Add contracts/utils.go for contract utilities - Add core/state/statedb_utils.go for XDPoS state access - Add core/state/state_reader.go for slot reading utilities - Add core/types/consensus_v2.go for XDPoS 2.0 types - Add internal/ethapi/api_xdc.go with XDC-specific RPC methods - GetBlockSignersByHash/Number - GetBlockFinalityByHash/Number - GetMasternodes - GetCandidateStatus - GetCandidates - Add BlockChain() method to ethapi.Backend interface --- common/types.go | 61 +++ consensus/XDPoS/utils/constants.go | 35 ++ consensus/XDPoS/utils/types.go | 49 +++ consensus/XDPoS/utils/utils.go | 104 ++++++ contracts/randomize/contract/randomize.go | 433 ++++++++++++++++++++++ contracts/randomize/randomize.go | 56 +++ contracts/utils.go | 279 ++++++++++++++ core/state/state_reader.go | 41 ++ core/state/statedb_utils.go | 184 +++++++++ core/types/block.go | 11 +- core/types/consensus_v2.go | 141 +++++++ eth/api_backend.go | 5 + internal/ethapi/api_xdc.go | 379 +++++++++++++++++++ internal/ethapi/backend.go | 3 + 14 files changed, 1780 insertions(+), 1 deletion(-) create mode 100644 consensus/XDPoS/utils/constants.go create mode 100644 consensus/XDPoS/utils/types.go create mode 100644 consensus/XDPoS/utils/utils.go create mode 100644 contracts/randomize/contract/randomize.go create mode 100644 contracts/randomize/randomize.go create mode 100644 contracts/utils.go create mode 100644 core/state/state_reader.go create mode 100644 core/state/statedb_utils.go create mode 100644 core/types/consensus_v2.go create mode 100644 internal/ethapi/api_xdc.go diff --git a/common/types.go b/common/types.go index a96d6c7c83..d02721e78f 100644 --- a/common/types.go +++ b/common/types.go @@ -41,6 +41,44 @@ const ( AddressLength = 20 ) +// XDC-specific constants +const ( + BlockSigners = "xdc0000000000000000000000000000000000000089" + MasternodeVotingSMC = "xdc0000000000000000000000000000000000000088" + RandomizeSMC = "xdc0000000000000000000000000000000000000090" + FoudationAddr = "xdc0000000000000000000000000000000000000068" + TeamAddr = "xdc0000000000000000000000000000000000000099" + XDCXAddr = "xdc0000000000000000000000000000000000000091" + TradingStateAddr = "xdc0000000000000000000000000000000000000092" + XDCXLendingAddress = "xdc0000000000000000000000000000000000000093" + XDCXLendingFinalizedTradeAddress = "xdc0000000000000000000000000000000000000094" + XDCNativeAddress = "xdc0000000000000000000000000000000000000001" + LendingLockAddress = "xdc0000000000000000000000000000000000000011" + + // Method signatures for XDPoS contracts + VoteMethod = "0x6dd7d8ea" + UnvoteMethod = "0x02aa9be2" + ProposeMethod = "0x01267951" + ResignMethod = "0xae6e43f5" + SignMethod = "0xe341eaa4" + XDCXApplyMethod = "0xc6b32f34" + XDCZApplyMethod = "0xc6b32f34" + HexSignMethod = "e341eaa4" + HexSetSecret = "34d38600" + HexSetOpening = "e11f5ba2" + + // Reward percentages + RewardMasterPercent = 40 + RewardVoterPercent = 50 + RewardFoundationPercent = 10 + + // Epoch block constants + MergeSignRange = 15 + EpocBlockSecret = 800 + EpocBlockOpening = 850 + EpocBlockRandomize = 900 +) + var ( hashT = reflect.TypeFor[Hash]() addressT = reflect.TypeFor[Address]() @@ -50,11 +88,34 @@ var ( // MaxHash represents the maximum possible hash value. MaxHash = HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + + // XDC contract addresses (binary form) + BlockSignersBinary = HexToAddress("0x0000000000000000000000000000000000000089") + MasternodeVotingSMCBinary = HexToAddress("0x0000000000000000000000000000000000000088") + RandomizeSMCBinary = HexToAddress("0x0000000000000000000000000000000000000090") + FoudationAddrBinary = HexToAddress("0x0000000000000000000000000000000000000068") + TeamAddrBinary = HexToAddress("0x0000000000000000000000000000000000000099") + XDCXAddrBinary = HexToAddress("0x0000000000000000000000000000000000000091") + TradingStateAddrBinary = HexToAddress("0x0000000000000000000000000000000000000092") + XDCXLendingAddressBinary = HexToAddress("0x0000000000000000000000000000000000000093") + XDCXLendingFinalizedTradeAddressBinary = HexToAddress("0x0000000000000000000000000000000000000094") + XDCNativeAddressBinary = HexToAddress("0x0000000000000000000000000000000000000001") + LendingLockAddressBinary = HexToAddress("0x0000000000000000000000000000000000000011") + MintedRecordAddressBinary = HexToAddress("0x000000000000000000000000000000000000009a") + + // TIP2019 block number for XDPoS reward updates + TIP2019Block = big.NewInt(0) ) // Hash represents the 32 byte Keccak256 hash of arbitrary data. type Hash [HashLength]byte +// Vote represents a masternode vote from a voter +type Vote struct { + Masternode Address + Voter Address +} + // BytesToHash sets b to hash. // If b is larger than len(h), b will be cropped from the left. func BytesToHash(b []byte) Hash { diff --git a/consensus/XDPoS/utils/constants.go b/consensus/XDPoS/utils/constants.go new file mode 100644 index 0000000000..5a9589c82c --- /dev/null +++ b/consensus/XDPoS/utils/constants.go @@ -0,0 +1,35 @@ +// Copyright (c) 2018 XDPoSChain +// XDPoS delegated-proof-of-stake protocol constants. + +package utils + +import ( + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + EpochLength = uint64(900) // Default number of blocks after which to checkpoint and reset the pending votes + + ExtraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity + ExtraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal + + NonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer + NonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer. + + UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. + InmemoryEpochs = 5 * EpochLength // Number of mapping from block to epoch switch infos to keep in memory + + InmemoryRound2Epochs = 65536 // Number of mapping of epoch switch blocks for quickly locating epoch switch block. +) + +const ( + InmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + BlockSignersCacheLimit = 9000 + M2ByteLength = 4 +) + +const ( + PeriodicJobPeriod = 60 + PoolHygieneRound = 10 +) diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go new file mode 100644 index 0000000000..b89001a0f1 --- /dev/null +++ b/consensus/XDPoS/utils/types.go @@ -0,0 +1,49 @@ +// Copyright (c) 2018 XDPoSChain +// XDPoS types and interfaces + +package utils + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core/types" +) + +type Masternode struct { + Address common.Address + Stake *big.Int +} + +type PublicApiSnapshot struct { + Number uint64 `json:"number"` // Block number where the snapshot was created + Hash common.Hash `json:"hash"` // Block hash where the snapshot was created + Signers map[common.Address]struct{} `json:"signers"` // Set of authorized signers at this moment + Recents map[uint64]common.Address `json:"recents"` // Set of recent signers for spam protections +} + +type MissedRoundInfo struct { + Round types.Round + Miner common.Address + CurrentBlockHash common.Hash + CurrentBlockNum *big.Int + ParentBlockHash common.Hash + ParentBlockNum *big.Int +} + +type PublicApiMissedRoundsMetadata struct { + EpochRound types.Round + EpochBlockNumber *big.Int + MissedRounds []MissedRoundInfo +} + +// Given an epoch number, this struct records the epoch switch block (first block in epoch) infos such as block number +type EpochNumInfo struct { + EpochBlockHash common.Hash `json:"hash"` + EpochRound types.Round `json:"round"` + EpochFirstBlockNumber *big.Int `json:"firstBlock"` + EpochLastBlockNumber *big.Int `json:"lastBlock"` +} + +type SigLRU = lru.Cache[common.Hash, common.Address] diff --git a/consensus/XDPoS/utils/utils.go b/consensus/XDPoS/utils/utils.go new file mode 100644 index 0000000000..d62d654b92 --- /dev/null +++ b/consensus/XDPoS/utils/utils.go @@ -0,0 +1,104 @@ +// Copyright (c) 2018 XDPoSChain +// XDPoS utility functions + +package utils + +import ( + "bytes" + "errors" + "fmt" + "reflect" + "sort" + "strconv" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +func Position(list []common.Address, x common.Address) int { + for i, item := range list { + if item == x { + return i + } + } + return -1 +} + +func Hop(len, pre, cur int) int { + switch { + case pre < cur: + return cur - (pre + 1) + case pre > cur: + return (len - pre) + (cur - 1) + default: + return len - 1 + } +} + +// Extract validators from byte array. +func ExtractValidatorsFromBytes(byteValidators []byte) []int64 { + lenValidator := len(byteValidators) / M2ByteLength + var validators []int64 + for i := 0; i < lenValidator; i++ { + trimByte := bytes.Trim(byteValidators[i*M2ByteLength:(i+1)*M2ByteLength], "\x00") + intNumber, err := strconv.Atoi(string(trimByte)) + if err != nil { + log.Error("Can not convert string to integer", "error", err) + return []int64{} + } + validators = append(validators, int64(intNumber)) + } + + return validators +} + +// compare 2 signers lists +// return true if they are same elements, otherwise return false +func CompareSignersLists(list1 []common.Address, list2 []common.Address) bool { + l1 := make([]common.Address, len(list1)) + l2 := make([]common.Address, len(list2)) + + copy(l1, list1) + copy(l2, list2) + + if len(l1) == 0 && len(l2) == 0 { + return true + } + + if len(l1) != len(l2) { + return false + } + + sort.Slice(l1, func(i, j int) bool { + return bytes.Compare(l1[i][:], l1[j][:]) == -1 + }) + sort.Slice(l2, func(i, j int) bool { + return bytes.Compare(l2[i][:], l2[j][:]) == -1 + }) + return reflect.DeepEqual(l1, l2) +} + +// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions) +func DecodeBytesExtraFields(b []byte, val interface{}) error { + if len(b) == 0 { + return errors.New("extra field is 0 length") + } + switch b[0] { + case 2: + return rlp.DecodeBytes(b[1:], val) + default: + return fmt.Errorf("consensus version %d is not defined, or this block is v1 block", b[0]) + } +} + +func RlpHash(x interface{}) (h common.Hash) { + hw := sha3.NewLegacyKeccak256() + err := rlp.Encode(hw, x) + if err != nil { + log.Error("[rlpHash] Fail to hash item", "Error", err) + } + hw.Sum(h[:0]) + return h +} diff --git a/contracts/randomize/contract/randomize.go b/contracts/randomize/contract/randomize.go new file mode 100644 index 0000000000..801c9f38bb --- /dev/null +++ b/contracts/randomize/contract/randomize.go @@ -0,0 +1,433 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package contract + +import ( + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// SafeMathABI is the input ABI used to generate the binding from. +const SafeMathABI = "[]" + +// SafeMathBin is the compiled bytecode used for deploying new contracts. +const SafeMathBin = `0x604c602c600b82828239805160001a60731460008114601c57601e565bfe5b5030600052607381538281f30073000000000000000000000000000000000000000030146060604052600080fd00a165627a7a72305820b9407d48ebc7efee5c9f08b3b3a957df2939281f5913225e8c1291f069b900490029` + +// DeploySafeMath deploys a new Ethereum contract, binding an instance of SafeMath to it. +func DeploySafeMath(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *SafeMath, error) { + parsed, err := abi.JSON(strings.NewReader(SafeMathABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(SafeMathBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &SafeMath{SafeMathCaller: SafeMathCaller{contract: contract}, SafeMathTransactor: SafeMathTransactor{contract: contract}, SafeMathFilterer: SafeMathFilterer{contract: contract}}, nil +} + +// SafeMath is an auto generated Go binding around an Ethereum contract. +type SafeMath struct { + SafeMathCaller // Read-only binding to the contract + SafeMathTransactor // Write-only binding to the contract + SafeMathFilterer // Log filterer for contract events +} + +// SafeMathCaller is an auto generated read-only Go binding around an Ethereum contract. +type SafeMathCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SafeMathTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SafeMathTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SafeMathFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SafeMathFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SafeMathSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SafeMathSession struct { + Contract *SafeMath // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SafeMathCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SafeMathCallerSession struct { + Contract *SafeMathCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SafeMathTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SafeMathTransactorSession struct { + Contract *SafeMathTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SafeMathRaw is an auto generated low-level Go binding around an Ethereum contract. +type SafeMathRaw struct { + Contract *SafeMath // Generic contract binding to access the raw methods on +} + +// SafeMathCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SafeMathCallerRaw struct { + Contract *SafeMathCaller // Generic read-only contract binding to access the raw methods on +} + +// SafeMathTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SafeMathTransactorRaw struct { + Contract *SafeMathTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSafeMath creates a new instance of SafeMath, bound to a specific deployed contract. +func NewSafeMath(address common.Address, backend bind.ContractBackend) (*SafeMath, error) { + contract, err := bindSafeMath(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SafeMath{SafeMathCaller: SafeMathCaller{contract: contract}, SafeMathTransactor: SafeMathTransactor{contract: contract}, SafeMathFilterer: SafeMathFilterer{contract: contract}}, nil +} + +// NewSafeMathCaller creates a new read-only instance of SafeMath, bound to a specific deployed contract. +func NewSafeMathCaller(address common.Address, caller bind.ContractCaller) (*SafeMathCaller, error) { + contract, err := bindSafeMath(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SafeMathCaller{contract: contract}, nil +} + +// NewSafeMathTransactor creates a new write-only instance of SafeMath, bound to a specific deployed contract. +func NewSafeMathTransactor(address common.Address, transactor bind.ContractTransactor) (*SafeMathTransactor, error) { + contract, err := bindSafeMath(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SafeMathTransactor{contract: contract}, nil +} + +// NewSafeMathFilterer creates a new log filterer instance of SafeMath, bound to a specific deployed contract. +func NewSafeMathFilterer(address common.Address, filterer bind.ContractFilterer) (*SafeMathFilterer, error) { + contract, err := bindSafeMath(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SafeMathFilterer{contract: contract}, nil +} + +// bindSafeMath binds a generic wrapper to an already deployed contract. +func bindSafeMath(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(SafeMathABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SafeMath *SafeMathRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SafeMath.Contract.SafeMathCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SafeMath *SafeMathRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SafeMath.Contract.SafeMathTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SafeMath *SafeMathRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SafeMath.Contract.SafeMathTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SafeMath *SafeMathCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SafeMath.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SafeMath *SafeMathTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SafeMath.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SafeMath *SafeMathTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SafeMath.Contract.contract.Transact(opts, method, params...) +} + +// XDCRandomizeABI is the input ABI used to generate the binding from. +const XDCRandomizeABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"_validator\",\"type\":\"address\"}],\"name\":\"getSecret\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_secret\",\"type\":\"bytes32[]\"}],\"name\":\"setSecret\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_validator\",\"type\":\"address\"}],\"name\":\"getOpening\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_opening\",\"type\":\"bytes32\"}],\"name\":\"setOpening\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]" + +// XDCRandomizeBin is the compiled bytecode used for deploying new contracts. +const XDCRandomizeBin = `0x6060604052341561000f57600080fd5b6103368061001e6000396000f3006060604052600436106100615763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663284180fc811461006657806334d38600146100d8578063d442d6cc14610129578063e11f5ba21461015a575b600080fd5b341561007157600080fd5b610085600160a060020a0360043516610170565b60405160208082528190810183818151815260200191508051906020019060200280838360005b838110156100c45780820151838201526020016100ac565b505050509050019250505060405180910390f35b34156100e357600080fd5b61012760046024813581810190830135806020818102016040519081016040528093929190818152602001838360200280828437509496506101f395505050505050565b005b341561013457600080fd5b610148600160a060020a0360043516610243565b60405190815260200160405180910390f35b341561016557600080fd5b61012760043561025e565b61017861028e565b60008083600160a060020a0316600160a060020a031681526020019081526020016000208054806020026020016040519081016040528092919081815260200182805480156101e757602002820191906000526020600020905b815481526001909101906020018083116101d2575b50505050509050919050565b610384430661032081101561020757600080fd5b610352811061021557600080fd5b600160a060020a033316600090815260208190526040902082805161023e9291602001906102a0565b505050565b600160a060020a031660009081526001602052604090205490565b610384430661035281101561027257600080fd5b50600160a060020a033316600090815260016020526040902055565b60206040519081016040526000815290565b8280548282559060005260206000209081019282156102dd579160200282015b828111156102dd57825182556020909201916001909101906102c0565b506102e99291506102ed565b5090565b61030791905b808211156102e957600081556001016102f3565b905600a165627a7a7230582034991c8dc4001fc254f3ba2811c05d2e7d29bee3908946ca56d1545b2c852de20029` + +// DeployXDCRandomize deploys a new Ethereum contract, binding an instance of XDCRandomize to it. +func DeployXDCRandomize(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *XDCRandomize, error) { + parsed, err := abi.JSON(strings.NewReader(XDCRandomizeABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(XDCRandomizeBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &XDCRandomize{XDCRandomizeCaller: XDCRandomizeCaller{contract: contract}, XDCRandomizeTransactor: XDCRandomizeTransactor{contract: contract}, XDCRandomizeFilterer: XDCRandomizeFilterer{contract: contract}}, nil +} + +// XDCRandomize is an auto generated Go binding around an Ethereum contract. +type XDCRandomize struct { + XDCRandomizeCaller // Read-only binding to the contract + XDCRandomizeTransactor // Write-only binding to the contract + XDCRandomizeFilterer // Log filterer for contract events +} + +// XDCRandomizeCaller is an auto generated read-only Go binding around an Ethereum contract. +type XDCRandomizeCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// XDCRandomizeTransactor is an auto generated write-only Go binding around an Ethereum contract. +type XDCRandomizeTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// XDCRandomizeFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type XDCRandomizeFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// XDCRandomizeSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type XDCRandomizeSession struct { + Contract *XDCRandomize // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// XDCRandomizeCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type XDCRandomizeCallerSession struct { + Contract *XDCRandomizeCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// XDCRandomizeTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type XDCRandomizeTransactorSession struct { + Contract *XDCRandomizeTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// XDCRandomizeRaw is an auto generated low-level Go binding around an Ethereum contract. +type XDCRandomizeRaw struct { + Contract *XDCRandomize // Generic contract binding to access the raw methods on +} + +// XDCRandomizeCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type XDCRandomizeCallerRaw struct { + Contract *XDCRandomizeCaller // Generic read-only contract binding to access the raw methods on +} + +// XDCRandomizeTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type XDCRandomizeTransactorRaw struct { + Contract *XDCRandomizeTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewXDCRandomize creates a new instance of XDCRandomize, bound to a specific deployed contract. +func NewXDCRandomize(address common.Address, backend bind.ContractBackend) (*XDCRandomize, error) { + contract, err := bindXDCRandomize(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &XDCRandomize{XDCRandomizeCaller: XDCRandomizeCaller{contract: contract}, XDCRandomizeTransactor: XDCRandomizeTransactor{contract: contract}, XDCRandomizeFilterer: XDCRandomizeFilterer{contract: contract}}, nil +} + +// NewXDCRandomizeCaller creates a new read-only instance of XDCRandomize, bound to a specific deployed contract. +func NewXDCRandomizeCaller(address common.Address, caller bind.ContractCaller) (*XDCRandomizeCaller, error) { + contract, err := bindXDCRandomize(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &XDCRandomizeCaller{contract: contract}, nil +} + +// NewXDCRandomizeTransactor creates a new write-only instance of XDCRandomize, bound to a specific deployed contract. +func NewXDCRandomizeTransactor(address common.Address, transactor bind.ContractTransactor) (*XDCRandomizeTransactor, error) { + contract, err := bindXDCRandomize(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &XDCRandomizeTransactor{contract: contract}, nil +} + +// NewXDCRandomizeFilterer creates a new log filterer instance of XDCRandomize, bound to a specific deployed contract. +func NewXDCRandomizeFilterer(address common.Address, filterer bind.ContractFilterer) (*XDCRandomizeFilterer, error) { + contract, err := bindXDCRandomize(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &XDCRandomizeFilterer{contract: contract}, nil +} + +// bindXDCRandomize binds a generic wrapper to an already deployed contract. +func bindXDCRandomize(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(XDCRandomizeABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_XDCRandomize *XDCRandomizeRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _XDCRandomize.Contract.XDCRandomizeCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_XDCRandomize *XDCRandomizeRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _XDCRandomize.Contract.XDCRandomizeTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_XDCRandomize *XDCRandomizeRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _XDCRandomize.Contract.XDCRandomizeTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_XDCRandomize *XDCRandomizeCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _XDCRandomize.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_XDCRandomize *XDCRandomizeTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _XDCRandomize.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_XDCRandomize *XDCRandomizeTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _XDCRandomize.Contract.contract.Transact(opts, method, params...) +} + +// GetOpening is a free data retrieval call binding the contract method 0xd442d6cc. +// +// Solidity: function getOpening(_validator address) constant returns(bytes32) +func (_XDCRandomize *XDCRandomizeCaller) GetOpening(opts *bind.CallOpts, _validator common.Address) ([32]byte, error) { + var ( + ret0 = new([32]byte) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCRandomize.contract.Call(opts, out, "getOpening", _validator) + return *ret0, err +} + +// GetOpening is a free data retrieval call binding the contract method 0xd442d6cc. +// +// Solidity: function getOpening(_validator address) constant returns(bytes32) +func (_XDCRandomize *XDCRandomizeSession) GetOpening(_validator common.Address) ([32]byte, error) { + return _XDCRandomize.Contract.GetOpening(&_XDCRandomize.CallOpts, _validator) +} + +// GetOpening is a free data retrieval call binding the contract method 0xd442d6cc. +// +// Solidity: function getOpening(_validator address) constant returns(bytes32) +func (_XDCRandomize *XDCRandomizeCallerSession) GetOpening(_validator common.Address) ([32]byte, error) { + return _XDCRandomize.Contract.GetOpening(&_XDCRandomize.CallOpts, _validator) +} + +// GetSecret is a free data retrieval call binding the contract method 0x284180fc. +// +// Solidity: function getSecret(_validator address) constant returns(bytes32[]) +func (_XDCRandomize *XDCRandomizeCaller) GetSecret(opts *bind.CallOpts, _validator common.Address) ([][32]byte, error) { + var ( + ret0 = new([][32]byte) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCRandomize.contract.Call(opts, out, "getSecret", _validator) + return *ret0, err +} + +// GetSecret is a free data retrieval call binding the contract method 0x284180fc. +// +// Solidity: function getSecret(_validator address) constant returns(bytes32[]) +func (_XDCRandomize *XDCRandomizeSession) GetSecret(_validator common.Address) ([][32]byte, error) { + return _XDCRandomize.Contract.GetSecret(&_XDCRandomize.CallOpts, _validator) +} + +// GetSecret is a free data retrieval call binding the contract method 0x284180fc. +// +// Solidity: function getSecret(_validator address) constant returns(bytes32[]) +func (_XDCRandomize *XDCRandomizeCallerSession) GetSecret(_validator common.Address) ([][32]byte, error) { + return _XDCRandomize.Contract.GetSecret(&_XDCRandomize.CallOpts, _validator) +} + +// SetOpening is a paid mutator transaction binding the contract method 0xe11f5ba2. +// +// Solidity: function setOpening(_opening bytes32) returns() +func (_XDCRandomize *XDCRandomizeTransactor) SetOpening(opts *bind.TransactOpts, _opening [32]byte) (*types.Transaction, error) { + return _XDCRandomize.contract.Transact(opts, "setOpening", _opening) +} + +// SetOpening is a paid mutator transaction binding the contract method 0xe11f5ba2. +// +// Solidity: function setOpening(_opening bytes32) returns() +func (_XDCRandomize *XDCRandomizeSession) SetOpening(_opening [32]byte) (*types.Transaction, error) { + return _XDCRandomize.Contract.SetOpening(&_XDCRandomize.TransactOpts, _opening) +} + +// SetOpening is a paid mutator transaction binding the contract method 0xe11f5ba2. +// +// Solidity: function setOpening(_opening bytes32) returns() +func (_XDCRandomize *XDCRandomizeTransactorSession) SetOpening(_opening [32]byte) (*types.Transaction, error) { + return _XDCRandomize.Contract.SetOpening(&_XDCRandomize.TransactOpts, _opening) +} + +// SetSecret is a paid mutator transaction binding the contract method 0x34d38600. +// +// Solidity: function setSecret(_secret bytes32[]) returns() +func (_XDCRandomize *XDCRandomizeTransactor) SetSecret(opts *bind.TransactOpts, _secret [][32]byte) (*types.Transaction, error) { + return _XDCRandomize.contract.Transact(opts, "setSecret", _secret) +} + +// SetSecret is a paid mutator transaction binding the contract method 0x34d38600. +// +// Solidity: function setSecret(_secret bytes32[]) returns() +func (_XDCRandomize *XDCRandomizeSession) SetSecret(_secret [][32]byte) (*types.Transaction, error) { + return _XDCRandomize.Contract.SetSecret(&_XDCRandomize.TransactOpts, _secret) +} + +// SetSecret is a paid mutator transaction binding the contract method 0x34d38600. +// +// Solidity: function setSecret(_secret bytes32[]) returns() +func (_XDCRandomize *XDCRandomizeTransactorSession) SetSecret(_secret [][32]byte) (*types.Transaction, error) { + return _XDCRandomize.Contract.SetSecret(&_XDCRandomize.TransactOpts, _secret) +} diff --git a/contracts/randomize/randomize.go b/contracts/randomize/randomize.go new file mode 100644 index 0000000000..ca4a0cb154 --- /dev/null +++ b/contracts/randomize/randomize.go @@ -0,0 +1,56 @@ +// Copyright (c) 2018 XDPoSChain +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +package randomize + +import ( + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/randomize/contract" +) + +type Randomize struct { + *contract.XDCRandomizeSession + contractBackend bind.ContractBackend +} + +func NewRandomize(transactOpts *bind.TransactOpts, contractAddr common.Address, contractBackend bind.ContractBackend) (*Randomize, error) { + randomize, err := contract.NewXDCRandomize(contractAddr, contractBackend) + if err != nil { + return nil, err + } + + return &Randomize{ + &contract.XDCRandomizeSession{ + Contract: randomize, + TransactOpts: *transactOpts, + }, + contractBackend, + }, nil +} + +func DeployRandomize(transactOpts *bind.TransactOpts, contractBackend bind.ContractBackend) (common.Address, *Randomize, error) { + randomizeAddr, _, _, err := contract.DeployXDCRandomize(transactOpts, contractBackend) + if err != nil { + return randomizeAddr, nil, err + } + + randomize, err := NewRandomize(transactOpts, randomizeAddr, contractBackend) + if err != nil { + return randomizeAddr, nil, err + } + + return randomizeAddr, randomize, nil +} diff --git a/contracts/utils.go b/contracts/utils.go new file mode 100644 index 0000000000..23c5bcd212 --- /dev/null +++ b/contracts/utils.go @@ -0,0 +1,279 @@ +// Copyright (c) 2018 XDPoSChain +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +package contracts + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "encoding/base64" + "fmt" + "io" + "math/big" + "math/rand" + "strconv" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/XDPoS/utils" + randomizeContract "github.com/ethereum/go-ethereum/contracts/randomize/contract" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +const ( + extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity + extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal +) + +type RewardLog struct { + Sign uint64 `json:"sign"` + Reward *big.Int `json:"reward"` +} + +// Create tx sign. +func CreateTxSign(blockNumber *big.Int, blockHash common.Hash, nonce uint64, blockSigner common.Address) *types.Transaction { + data := common.Hex2Bytes(common.HexSignMethod) + inputData := append(data, common.LeftPadBytes(blockNumber.Bytes(), 32)...) + inputData = append(inputData, common.LeftPadBytes(blockHash.Bytes(), 32)...) + tx := types.NewTransaction(nonce, blockSigner, big.NewInt(0), 200000, big.NewInt(0), inputData) + + return tx +} + +// Send secret key into randomize smartcontract. +func BuildTxSecretRandomize(nonce uint64, randomizeAddr common.Address, epocNumber uint64, randomizeKey []byte) (*types.Transaction, error) { + data := common.Hex2Bytes(common.HexSetSecret) + rand.Seed(time.Now().UnixNano()) + secretNumb := rand.Intn(int(epocNumber)) + + // Append randomize suffix in -1, 0, 1. + secrets := []int64{int64(secretNumb)} + sizeOfArray := int64(32) + + // Build extra data for tx with first position is size of array byte and second position are length of array byte. + arrSizeOfSecrets := common.LeftPadBytes(new(big.Int).SetInt64(sizeOfArray).Bytes(), 32) + arrLengthOfSecrets := common.LeftPadBytes(new(big.Int).SetInt64(int64(len(secrets))).Bytes(), 32) + inputData := append(data, arrSizeOfSecrets...) + inputData = append(inputData, arrLengthOfSecrets...) + for _, secret := range secrets { + encryptSecret := Encrypt(randomizeKey, new(big.Int).SetInt64(secret).String()) + inputData = append(inputData, common.LeftPadBytes([]byte(encryptSecret), int(sizeOfArray))...) + } + tx := types.NewTransaction(nonce, randomizeAddr, big.NewInt(0), 200000, big.NewInt(0), inputData) + + return tx, nil +} + +// Send opening to randomize SMC. +func BuildTxOpeningRandomize(nonce uint64, randomizeAddr common.Address, randomizeKey []byte) (*types.Transaction, error) { + data := common.Hex2Bytes(common.HexSetOpening) + inputData := append(data, randomizeKey...) + tx := types.NewTransaction(nonce, randomizeAddr, big.NewInt(0), 200000, big.NewInt(0), inputData) + + return tx, nil +} + +// Get random from randomize contract. +func GetRandomizeFromContract(client bind.ContractBackend, addrMasternode common.Address) (int64, error) { + randomize, err := randomizeContract.NewXDCRandomize(common.RandomizeSMCBinary, client) + if err != nil { + log.Error("Fail to get instance of randomize", "error", err) + } + opts := new(bind.CallOpts) + secrets, err := randomize.GetSecret(opts, addrMasternode) + if err != nil { + log.Error("Fail get secrets from randomize", "error", err) + } + opening, err := randomize.GetOpening(opts, addrMasternode) + if err != nil { + log.Error("Fail get opening from randomize", "error", err) + } + + return DecryptRandomizeFromSecretsAndOpening(secrets, opening) +} + +// Generate m2 listing from randomize array. +func GenM2FromRandomize(randomizes []int64, lenSigners int64) ([]int64, error) { + blockValidator := NewSlice(int64(0), lenSigners, 1) + randIndexs := make([]int64, lenSigners) + total := int64(0) + var temp int64 = 0 + for _, j := range randomizes { + total += j + } + rand.Seed(total) + for i := len(blockValidator) - 1; i >= 0; i-- { + blockLength := len(blockValidator) - 1 + if blockLength <= 1 { + blockLength = 1 + } + randomIndex := int64(rand.Intn(blockLength)) + temp = blockValidator[randomIndex] + blockValidator[randomIndex] = blockValidator[i] + blockValidator[i] = temp + blockValidator = append(blockValidator[:i], blockValidator[i+1:]...) + randIndexs[i] = temp + } + + return randIndexs, nil +} + +// Get validators from m2 array integer. +func BuildValidatorFromM2(listM2 []int64) []byte { + var validatorBytes []byte + for _, numberM2 := range listM2 { + // Convert number to byte. + m2Byte := common.LeftPadBytes([]byte(fmt.Sprintf("%d", numberM2)), utils.M2ByteLength) + validatorBytes = append(validatorBytes, m2Byte...) + } + + return validatorBytes +} + +// Decode validator hex string. +func DecodeValidatorsHexData(validatorsStr string) ([]int64, error) { + validatorsByte, err := hexutil.Decode(validatorsStr) + if err != nil { + return nil, err + } + + return utils.ExtractValidatorsFromBytes(validatorsByte), nil +} + +// Decrypt randomize from secrets and opening. +func DecryptRandomizeFromSecretsAndOpening(secrets [][32]byte, opening [32]byte) (int64, error) { + var random int64 + if len(secrets) > 0 { + for _, secret := range secrets { + trimSecret := bytes.TrimLeft(secret[:], "\x00") + decryptSecret := Decrypt(opening[:], string(trimSecret)) + if isInt(decryptSecret) { + intNumber, err := strconv.Atoi(decryptSecret) + if err != nil { + log.Error("Can not convert string to integer", "error", err) + return -1, err + } + random = int64(intNumber) + } + } + } + + return random, nil +} + +// Dynamic generate array sequence of numbers. +func NewSlice(start int64, end int64, step int64) []int64 { + s := make([]int64, end-start) + for i := range s { + s[i] = start + start += step + } + + return s +} + +// Shuffle array. +func Shuffle(slice []int64) []int64 { + newSlice := make([]int64, len(slice)) + copy(newSlice, slice) + + for i := 0; i < len(slice)-1; i++ { + rand.Seed(time.Now().UnixNano()) + randIndex := rand.Intn(len(newSlice)) + x := newSlice[i] + newSlice[i] = newSlice[randIndex] + newSlice[randIndex] = x + } + + return newSlice +} + +// encrypt string to base64 crypto using AES +func Encrypt(key []byte, text string) string { + plaintext := []byte(text) + + block, err := aes.NewCipher(key) + if err != nil { + log.Error("Fail to encrypt", "err", err) + return "" + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(cryptoRand.Reader, iv); err != nil { + log.Error("Fail to encrypt iv", "err", err) + return "" + } + + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) + + // convert to base64 + return base64.URLEncoding.EncodeToString(ciphertext) +} + +// decrypt from base64 to decrypted string +func Decrypt(key []byte, cryptoText string) string { + ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText) + + block, err := aes.NewCipher(key) + if err != nil { + log.Error("Fail to decrypt", "err", err) + return "" + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + if len(ciphertext) < aes.BlockSize { + log.Error("ciphertext too short") + return "" + } + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + stream := cipher.NewCFBDecrypter(block, iv) + + // XORKeyStream can work in-place if the two arguments are the same. + stream.XORKeyStream(ciphertext, ciphertext) + + return string(ciphertext[:]) +} + +// Generate random string. +func RandStringByte(n int) []byte { + letterBytes := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789" + b := make([]byte, n) + for i := range b { + rand.Seed(time.Now().UnixNano()) + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return b +} + +// Helper function check string is numeric. +func isInt(strNumber string) bool { + if _, err := strconv.Atoi(strNumber); err == nil { + return true + } else { + return false + } +} diff --git a/core/state/state_reader.go b/core/state/state_reader.go new file mode 100644 index 0000000000..c0735425bd --- /dev/null +++ b/core/state/state_reader.go @@ -0,0 +1,41 @@ +// Copyright (c) 2018 XDPoSChain +// This file provides utilities for reading smart contract state slots. + +package state + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func GetLocSimpleVariable(slot uint64) common.Hash { + slotHash := common.BigToHash(new(big.Int).SetUint64(slot)) + return slotHash +} + +func GetLocMappingAtKey(key common.Hash, slot uint64) *big.Int { + slotHash := common.BigToHash(new(big.Int).SetUint64(slot)) + retByte := crypto.Keccak256(key.Bytes(), slotHash.Bytes()) + ret := new(big.Int) + ret.SetBytes(retByte) + return ret +} + +func GetLocDynamicArrAtElement(slotHash common.Hash, index uint64, elementSize uint64) common.Hash { + slotKecBig := crypto.Keccak256Hash(slotHash.Bytes()).Big() + //arrBig = slotKecBig + index * elementSize + arrBig := slotKecBig.Add(slotKecBig, new(big.Int).SetUint64(index*elementSize)) + return common.BigToHash(arrBig) +} + +func GetLocFixedArrAtElement(slot uint64, index uint64, elementSize uint64) common.Hash { + slotBig := new(big.Int).SetUint64(slot) + arrBig := slotBig.Add(slotBig, new(big.Int).SetUint64(index*elementSize)) + return common.BigToHash(arrBig) +} + +func GetLocOfStructElement(locOfStruct *big.Int, elementSlotInstruct *big.Int) common.Hash { + return common.BigToHash(new(big.Int).Add(locOfStruct, elementSlotInstruct)) +} diff --git a/core/state/statedb_utils.go b/core/state/statedb_utils.go new file mode 100644 index 0000000000..72a03c292a --- /dev/null +++ b/core/state/statedb_utils.go @@ -0,0 +1,184 @@ +// Copyright (c) 2018 XDPoSChain +// This file provides utilities for accessing XDPoS state data. + +package state + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +var ( + slotBlockSignerMapping = map[string]uint64{ + "blockSigners": 0, + "blocks": 1, + } +) + +func GetSigners(statedb *StateDB, block *types.Block) []common.Address { + slot := slotBlockSignerMapping["blockSigners"] + keys := []common.Hash{} + keyArrSlot := GetLocMappingAtKey(block.Hash(), slot) + arrSlot := statedb.GetState(common.BlockSignersBinary, common.BigToHash(keyArrSlot)) + arrLength := arrSlot.Big().Uint64() + for i := uint64(0); i < arrLength; i++ { + key := GetLocDynamicArrAtElement(common.BigToHash(keyArrSlot), i, 1) + keys = append(keys, key) + } + rets := []common.Address{} + for _, key := range keys { + ret := statedb.GetState(common.BlockSignersBinary, key) + rets = append(rets, common.HexToAddress(ret.Hex())) + } + + return rets +} + +var ( + slotRandomizeMapping = map[string]uint64{ + "randomSecret": 0, + "randomOpening": 1, + } +) + +func GetSecret(statedb *StateDB, address common.Address) [][32]byte { + slot := slotRandomizeMapping["randomSecret"] + locSecret := GetLocMappingAtKey(common.BytesToHash(address.Bytes()), slot) + arrLength := statedb.GetState(common.RandomizeSMCBinary, common.BigToHash(locSecret)) + keys := []common.Hash{} + for i := uint64(0); i < arrLength.Big().Uint64(); i++ { + key := GetLocDynamicArrAtElement(common.BigToHash(locSecret), i, 1) + keys = append(keys, key) + } + rets := [][32]byte{} + for _, key := range keys { + ret := statedb.GetState(common.RandomizeSMCBinary, key) + rets = append(rets, ret) + } + return rets +} + +func GetOpening(statedb *StateDB, address common.Address) [32]byte { + slot := slotRandomizeMapping["randomOpening"] + locOpening := GetLocMappingAtKey(common.BytesToHash(address.Bytes()), slot) + ret := statedb.GetState(common.RandomizeSMCBinary, common.BigToHash(locOpening)) + return ret +} + +// The smart contract and the compiled byte code (in corresponding *.go file) is at commit "KYC Layer added." 7f856ffe672162dfa9c4006c89afb45a24fb7f9f +// Notice that if smart contract and the compiled byte code (in corresponding *.go file) changes, below also changes +var ( + slotValidatorMapping = map[string]uint64{ + "withdrawsState": 0, + "validatorsState": 1, + "voters": 2, + "KYCString": 3, + "invalidKYCCount": 4, + "hasVotedInvalid": 5, + "ownerToCandidate": 6, + "owners": 7, + "candidates": 8, + "candidateCount": 9, + "ownerCount": 10, + "minCandidateCap": 11, + "minVoterCap": 12, + "maxValidatorNumber": 13, + "candidateWithdrawDelay": 14, + "voterWithdrawDelay": 15, + } +) + +func GetCandidates(statedb *StateDB) []common.Address { + slot := slotValidatorMapping["candidates"] + slotHash := common.BigToHash(new(big.Int).SetUint64(slot)) + arrLength := statedb.GetState(common.MasternodeVotingSMCBinary, slotHash) + count := arrLength.Big().Uint64() + rets := make([]common.Address, 0, count) + + emptyHash := common.Hash{} + for i := uint64(0); i < count; i++ { + key := GetLocDynamicArrAtElement(slotHash, i, 1) + ret := statedb.GetState(common.MasternodeVotingSMCBinary, key) + if ret != emptyHash { + rets = append(rets, common.HexToAddress(ret.Hex())) + } + } + + return rets +} + +func GetCandidateOwner(statedb *StateDB, candidate common.Address) common.Address { + slot := slotValidatorMapping["validatorsState"] + // validatorsState[_candidate].owner; + locValidatorsState := GetLocMappingAtKey(common.BytesToHash(candidate.Bytes()), slot) + locCandidateOwner := locValidatorsState.Add(locValidatorsState, new(big.Int).SetUint64(uint64(0))) + ret := statedb.GetState(common.MasternodeVotingSMCBinary, common.BigToHash(locCandidateOwner)) + return common.HexToAddress(ret.Hex()) +} + +func GetCandidateCap(statedb *StateDB, candidate common.Address) *big.Int { + slot := slotValidatorMapping["validatorsState"] + // validatorsState[_candidate].cap; + locValidatorsState := GetLocMappingAtKey(common.BytesToHash(candidate.Bytes()), slot) + locCandidateCap := locValidatorsState.Add(locValidatorsState, new(big.Int).SetUint64(uint64(1))) + ret := statedb.GetState(common.MasternodeVotingSMCBinary, common.BigToHash(locCandidateCap)) + return ret.Big() +} + +func GetVoters(statedb *StateDB, candidate common.Address) []common.Address { + //mapping(address => address[]) voters; + slot := slotValidatorMapping["voters"] + locVoters := GetLocMappingAtKey(common.BytesToHash(candidate.Bytes()), slot) + arrLength := statedb.GetState(common.MasternodeVotingSMCBinary, common.BigToHash(locVoters)) + keys := []common.Hash{} + for i := uint64(0); i < arrLength.Big().Uint64(); i++ { + key := GetLocDynamicArrAtElement(common.BigToHash(locVoters), i, 1) + keys = append(keys, key) + } + rets := []common.Address{} + for _, key := range keys { + ret := statedb.GetState(common.MasternodeVotingSMCBinary, key) + rets = append(rets, common.HexToAddress(ret.Hex())) + } + + return rets +} + +func GetVoterCap(statedb *StateDB, candidate, voter common.Address) *big.Int { + slot := slotValidatorMapping["validatorsState"] + locValidatorsState := GetLocMappingAtKey(common.BytesToHash(candidate.Bytes()), slot) + locCandidateVoters := locValidatorsState.Add(locValidatorsState, new(big.Int).SetUint64(uint64(2))) + retByte := crypto.Keccak256(common.BytesToHash(voter.Bytes()).Bytes(), common.BigToHash(locCandidateVoters).Bytes()) + ret := statedb.GetState(common.MasternodeVotingSMCBinary, common.BytesToHash(retByte)) + return ret.Big() +} + +var ( + slotMintedRecordTotalMinted uint64 = 0 + slotMintedRecordLastEpochNum uint64 = 1 +) + +func GetTotalMinted(statedb *StateDB) common.Hash { + hash := GetLocSimpleVariable(slotMintedRecordTotalMinted) + totalMinted := statedb.GetState(common.MintedRecordAddressBinary, hash) + return totalMinted +} + +func PutTotalMinted(statedb *StateDB, value common.Hash) { + hash := GetLocSimpleVariable(slotMintedRecordTotalMinted) + statedb.SetState(common.MintedRecordAddressBinary, hash, value) +} + +func GetLastEpochNum(statedb *StateDB) common.Hash { + hash := GetLocSimpleVariable(slotMintedRecordLastEpochNum) + totalMinted := statedb.GetState(common.MintedRecordAddressBinary, hash) + return totalMinted +} + +func PutLastEpochNum(statedb *StateDB, value common.Hash) { + hash := GetLocSimpleVariable(slotMintedRecordLastEpochNum) + statedb.SetState(common.MintedRecordAddressBinary, hash, value) +} diff --git a/core/types/block.go b/core/types/block.go index c52c05a4c7..ba4682000a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -81,6 +81,11 @@ type Header struct { MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + // XDPoS fields for validator consensus + Validators []byte `json:"validators" rlp:"optional"` + Validator []byte `json:"validator" rlp:"optional"` + Penalties []byte `json:"penalties" rlp:"optional"` + // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` @@ -112,6 +117,10 @@ type headerMarshaling struct { Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON BlobGasUsed *hexutil.Uint64 ExcessBlobGas *hexutil.Uint64 + // XDPoS fields + Validators hexutil.Bytes + Validator hexutil.Bytes + Penalties hexutil.Bytes } // Hash returns the block hash of the header, which is simply the keccak256 hash of its @@ -129,7 +138,7 @@ func (h *Header) Size() common.StorageSize { if h.BaseFee != nil { baseFeeBits = h.BaseFee.BitLen() } - return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+baseFeeBits)/8) + return headerSize + common.StorageSize(len(h.Extra)+len(h.Validators)+len(h.Validator)+len(h.Penalties)+(h.Difficulty.BitLen()+h.Number.BitLen()+baseFeeBits)/8) } // SanityCheck checks a few basic things -- these checks are way beyond what diff --git a/core/types/consensus_v2.go b/core/types/consensus_v2.go new file mode 100644 index 0000000000..1b8cc67841 --- /dev/null +++ b/core/types/consensus_v2.go @@ -0,0 +1,141 @@ +// Copyright (c) 2018 XDPoSChain +// XDPoS 2.0 consensus types + +package types + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +// Round number type in XDPoS 2.0 +type Round uint64 +type Signature []byte + +// Block Info struct in XDPoS 2.0, used for vote message, etc. +type BlockInfo struct { + Hash common.Hash `json:"hash"` + Round Round `json:"round"` + Number *big.Int `json:"number"` +} + +// Vote message in XDPoS 2.0 +type VoteXDPoS struct { + signer common.Address //field not exported + ProposedBlockInfo *BlockInfo `json:"proposedBlockInfo"` + Signature Signature `json:"signature"` + GapNumber uint64 `json:"gapNumber"` +} + +func (v *VoteXDPoS) Hash() common.Hash { + return rlpHash(v) +} + +func (v *VoteXDPoS) PoolKey() string { + // return the voted block hash + return fmt.Sprint(v.ProposedBlockInfo.Round, ":", v.GapNumber, ":", v.ProposedBlockInfo.Number, ":", v.ProposedBlockInfo.Hash.Hex()) +} + +func (v *VoteXDPoS) GetSigner() common.Address { + return v.signer +} + +func (v *VoteXDPoS) SetSigner(signer common.Address) { + v.signer = signer +} + +// Timeout message in XDPoS 2.0 +type Timeout struct { + signer common.Address + Round Round + Signature Signature + GapNumber uint64 +} + +func (t *Timeout) Hash() common.Hash { + return rlpHash(t) +} + +func (t *Timeout) PoolKey() string { + // timeout pool key is round:gapNumber + return fmt.Sprint(t.Round, ":", t.GapNumber) +} + +func (t *Timeout) GetSigner() common.Address { + return t.signer +} + +func (t *Timeout) SetSigner(signer common.Address) { + t.signer = signer +} + +// BFT Sync Info message in XDPoS 2.0 +type SyncInfo struct { + HighestQuorumCert *QuorumCert + HighestTimeoutCert *TimeoutCert +} + +func (s *SyncInfo) Hash() common.Hash { + return rlpHash(s) +} + +// Quorum Certificate struct in XDPoS 2.0 +type QuorumCert struct { + ProposedBlockInfo *BlockInfo `json:"proposedBlockInfo"` + Signatures []Signature `json:"signatures"` + GapNumber uint64 `json:"gapNumber"` +} + +// Timeout Certificate struct in XDPoS 2.0 +type TimeoutCert struct { + Round Round + Signatures []Signature + GapNumber uint64 +} + +// The parsed extra fields in block header in XDPoS 2.0 (excluding the version byte) +// The version byte (consensus version) is the first byte in header's extra and it's only valid with value >= 2 +type ExtraFields_v2 struct { + Round Round + QuorumCert *QuorumCert +} + +// Encode XDPoS 2.0 extra fields into bytes +func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) { + bytes, err := rlp.EncodeToBytes(e) + if err != nil { + return nil, err + } + versionByte := []byte{2} + return append(versionByte, bytes...), nil +} + +type EpochSwitchInfo struct { + Penalties []common.Address + Standbynodes []common.Address + Masternodes []common.Address + MasternodesLen int + EpochSwitchBlockInfo *BlockInfo + EpochSwitchParentBlockInfo *BlockInfo +} + +type VoteForSign struct { + ProposedBlockInfo *BlockInfo + GapNumber uint64 +} + +func VoteSigHash(m *VoteForSign) common.Hash { + return rlpHash(m) +} + +type TimeoutForSign struct { + Round Round + GapNumber uint64 +} + +func TimeoutSigHash(m *TimeoutForSign) common.Hash { + return rlpHash(m) +} diff --git a/eth/api_backend.go b/eth/api_backend.go index 3f826b7861..5d44c6f3fa 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -57,6 +57,11 @@ func (b *EthAPIBackend) ChainConfig() *params.ChainConfig { return b.eth.blockchain.Config() } +// BlockChain returns the underlying blockchain for XDPoS operations. +func (b *EthAPIBackend) BlockChain() *core.BlockChain { + return b.eth.blockchain +} + func (b *EthAPIBackend) CurrentBlock() *types.Header { return b.eth.blockchain.CurrentBlock() } diff --git a/internal/ethapi/api_xdc.go b/internal/ethapi/api_xdc.go new file mode 100644 index 0000000000..d84bb4c876 --- /dev/null +++ b/internal/ethapi/api_xdc.go @@ -0,0 +1,379 @@ +// Copyright (c) 2018 XDPoSChain +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +package ethapi + +import ( + "context" + "errors" + "math/big" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/XDPoS" + "github.com/ethereum/go-ethereum/consensus/XDPoS/utils" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +// XDC status field names +const ( + fieldStatus = "status" + fieldCapacity = "capacity" + fieldSuccess = "success" + fieldEpoch = "epoch" + + statusMasternode = "MASTERNODE" + statusSlashed = "SLASHED" + statusProposed = "PROPOSED" + statusPenalty = "PENALTY" +) + +var ( + errEmptyHeader = errors.New("empty header") +) + +// GetBlockSignersByHash returns the signers of a block by hash +func (s *BlockChainAPI) GetBlockSignersByHash(ctx context.Context, blockHash common.Hash) ([]common.Address, error) { + block, err := s.b.BlockByHash(ctx, blockHash) + if err != nil || block == nil { + return []common.Address{}, err + } + masternodes, err := s.GetMasternodes(ctx, block) + if err != nil || len(masternodes) == 0 { + log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes)) + return []common.Address{}, err + } + return s.rpcOutputBlockSigners(block, ctx, masternodes) +} + +// GetBlockSignersByNumber returns the signers of a block by number +func (s *BlockChainAPI) GetBlockSignersByNumber(ctx context.Context, blockNumber rpc.BlockNumber) ([]common.Address, error) { + block, err := s.b.BlockByNumber(ctx, blockNumber) + if err != nil || block == nil { + return []common.Address{}, err + } + masternodes, err := s.GetMasternodes(ctx, block) + if err != nil || len(masternodes) == 0 { + log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes)) + return []common.Address{}, err + } + return s.rpcOutputBlockSigners(block, ctx, masternodes) +} + +// GetBlockFinalityByHash returns the finality of a block by hash +func (s *BlockChainAPI) GetBlockFinalityByHash(ctx context.Context, blockHash common.Hash) (uint, error) { + block, err := s.b.BlockByHash(ctx, blockHash) + if err != nil || block == nil { + return uint(0), err + } + masternodes, err := s.GetMasternodes(ctx, block) + if err != nil || len(masternodes) == 0 { + log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes)) + return uint(0), err + } + return s.findFinalityOfBlock(ctx, block, masternodes) +} + +// GetBlockFinalityByNumber returns the finality of a block by number +func (s *BlockChainAPI) GetBlockFinalityByNumber(ctx context.Context, blockNumber rpc.BlockNumber) (uint, error) { + block, err := s.b.BlockByNumber(ctx, blockNumber) + if err != nil || block == nil { + return uint(0), err + } + masternodes, err := s.GetMasternodes(ctx, block) + if err != nil || len(masternodes) == 0 { + log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes)) + return uint(0), err + } + return s.findFinalityOfBlock(ctx, block, masternodes) +} + +// GetMasternodes returns masternodes set at the starting block of epoch of the given block +func (s *BlockChainAPI) GetMasternodes(ctx context.Context, b *types.Block) ([]common.Address, error) { + var masternodes []common.Address + if b.Number().Int64() >= 0 { + if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok { + // Get block epoch masternodes + chain := s.b.BlockChain() + if chain != nil { + return engine.GetMasternodes(chain, b.Header()), nil + } + } else { + log.Error("Undefined XDPoS consensus engine") + } + } + return masternodes, nil +} + +// GetCandidateStatus returns status of the given candidate at a specified epochNumber +func (s *BlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAddress common.Address, epoch rpc.BlockNumber) (map[string]interface{}, error) { + result := map[string]interface{}{ + fieldStatus: "", + fieldCapacity: 0, + fieldSuccess: true, + } + + config := s.b.ChainConfig() + if config.XDPoS == nil { + return result, errors.New("XDPoS config not found") + } + epochConfig := config.XDPoS.Epoch + + // Calculate checkpoint block number + blockNum := uint64(epoch) + if epoch < 0 { + blockNum = s.b.CurrentBlock().Number.Uint64() + } + checkpointNumber := (blockNum / epochConfig) * epochConfig + if checkpointNumber == 0 { + checkpointNumber = epochConfig + } + + result[fieldEpoch] = checkpointNumber / epochConfig + + block, err := s.b.BlockByNumber(ctx, rpc.BlockNumber(checkpointNumber)) + if err != nil || block == nil { + result[fieldSuccess] = false + return result, err + } + + header := block.Header() + if header == nil { + log.Error("Empty header at checkpoint", "num", checkpointNumber) + return result, errEmptyHeader + } + + // Get candidates from state + statedb, _, err := s.b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(checkpointNumber)) + if err != nil { + result[fieldSuccess] = false + return result, err + } + if statedb == nil { + result[fieldSuccess] = false + return result, errors.New("nil statedb in GetCandidateStatus") + } + + candidatesAddresses := state.GetCandidates(statedb) + candidates := make([]utils.Masternode, 0, len(candidatesAddresses)) + for _, address := range candidatesAddresses { + v := state.GetCandidateCap(statedb, address) + candidates = append(candidates, utils.Masternode{Address: address, Stake: v}) + } + + if len(candidates) == 0 { + log.Debug("Candidates list cannot be found", "len(candidates)", len(candidates), "err", err) + result[fieldSuccess] = false + return result, err + } + + // Check if the address is a candidate + isCandidate := false + for i := 0; i < len(candidates); i++ { + if coinbaseAddress == candidates[i].Address { + isCandidate = true + result[fieldStatus] = statusProposed + result[fieldCapacity] = candidates[i].Stake + break + } + } + + // Get masternode list + var masternodes []common.Address + if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok { + masternodes = engine.GetMasternodesFromCheckpointHeader(header, header.Number.Uint64(), epochConfig) + if len(masternodes) == 0 { + log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64()) + result[fieldSuccess] = false + return result, err + } + } else { + log.Error("Undefined XDPoS consensus engine") + } + + // Set to statusMasternode if it is masternode + for _, masternode := range masternodes { + if coinbaseAddress == masternode { + result[fieldStatus] = statusMasternode + if !isCandidate { + result[fieldCapacity] = -1 + log.Warn("Find non-candidate masternode", "masternode", masternode.String(), "checkpointNumber", checkpointNumber, "epoch", epoch) + } + return result, nil + } + } + + return result, nil +} + +// GetCandidates returns status of all candidates at a specified epochNumber +func (s *BlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.BlockNumber) (map[string]interface{}, error) { + result := map[string]interface{}{ + "candidates": []map[string]interface{}{}, + "success": true, + } + + config := s.b.ChainConfig() + if config.XDPoS == nil { + result["success"] = false + return result, errors.New("XDPoS config not found") + } + epochConfig := config.XDPoS.Epoch + + // Calculate checkpoint block number + blockNum := uint64(epoch) + if epoch < 0 { + blockNum = s.b.CurrentBlock().Number.Uint64() + } + checkpointNumber := (blockNum / epochConfig) * epochConfig + if checkpointNumber == 0 { + checkpointNumber = epochConfig + } + + result["epoch"] = checkpointNumber / epochConfig + + // Get candidates from state + statedb, _, err := s.b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(checkpointNumber)) + if err != nil { + result["success"] = false + return result, err + } + if statedb == nil { + result["success"] = false + return result, errors.New("nil statedb in GetCandidates") + } + + candidatesAddresses := state.GetCandidates(statedb) + candidates := make([]map[string]interface{}, 0, len(candidatesAddresses)) + for _, address := range candidatesAddresses { + cap := state.GetCandidateCap(statedb, address) + owner := state.GetCandidateOwner(statedb, address) + candidates = append(candidates, map[string]interface{}{ + "address": address, + "capacity": cap, + "owner": owner, + }) + } + + // Sort by capacity descending + sort.Slice(candidates, func(i, j int) bool { + capI := candidates[i]["capacity"].(*big.Int) + capJ := candidates[j]["capacity"].(*big.Int) + return capI.Cmp(capJ) > 0 + }) + + result["candidates"] = candidates + return result, nil +} + +// rpcOutputBlockSigners returns block signers for RPC output +func (s *BlockChainAPI) rpcOutputBlockSigners(block *types.Block, ctx context.Context, masternodes []common.Address) ([]common.Address, error) { + if block == nil { + return nil, errors.New("block not found") + } + + header := block.Header() + if header == nil { + return nil, errors.New("header not found") + } + + // Extract signers from header's Validator field + signers := make([]common.Address, 0) + if len(header.Validator) > 0 { + // Decode validator bytes to addresses + for i := 0; i+common.AddressLength <= len(header.Validator); i += common.AddressLength { + var addr common.Address + copy(addr[:], header.Validator[i:i+common.AddressLength]) + signers = append(signers, addr) + } + } + + // If no signers found in Validator field, return the block miner + if len(signers) == 0 { + signers = append(signers, header.Coinbase) + } + + return signers, nil +} + +// findFinalityOfBlock calculates the finality percentage of a block +func (s *BlockChainAPI) findFinalityOfBlock(ctx context.Context, block *types.Block, masternodes []common.Address) (uint, error) { + if block == nil || len(masternodes) == 0 { + return 0, nil + } + + signers, err := s.rpcOutputBlockSigners(block, ctx, masternodes) + if err != nil { + return 0, err + } + + // Calculate finality percentage + signerCount := len(signers) + masternodeCount := len(masternodes) + + if masternodeCount == 0 { + return 0, nil + } + + finality := uint((signerCount * 100) / masternodeCount) + return finality, nil +} + +// RPCMarshalHeaderXDC converts a header to the RPC output with XDC fields +func RPCMarshalHeaderXDC(header *types.Header) map[string]interface{} { + result := map[string]interface{}{ + "number": (*hexutil.Big)(header.Number), + "hash": header.Hash(), + "parentHash": header.ParentHash, + "nonce": header.Nonce, + "mixHash": header.MixDigest, + "sha3Uncles": header.UncleHash, + "logsBloom": header.Bloom, + "stateRoot": header.Root, + "miner": header.Coinbase, + "difficulty": (*hexutil.Big)(header.Difficulty), + "extraData": hexutil.Bytes(header.Extra), + "size": hexutil.Uint64(header.Size()), + "gasLimit": hexutil.Uint64(header.GasLimit), + "gasUsed": hexutil.Uint64(header.GasUsed), + "timestamp": hexutil.Uint64(header.Time), + "transactionsRoot": header.TxHash, + "receiptsRoot": header.ReceiptHash, + } + + // Add XDPoS-specific fields + if header.Validators != nil { + result["validators"] = hexutil.Bytes(header.Validators) + } + if header.Validator != nil { + result["validator"] = hexutil.Bytes(header.Validator) + } + if header.Penalties != nil { + result["penalties"] = hexutil.Bytes(header.Penalties) + } + + if header.BaseFee != nil { + result["baseFeePerGas"] = (*hexutil.Big)(header.BaseFee) + } + if header.WithdrawalsHash != nil { + result["withdrawalsRoot"] = header.WithdrawalsHash + } + + return result +} diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index af3d592b82..e6ce63580b 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -91,6 +91,9 @@ type Backend interface { Engine() consensus.Engine HistoryPruningCutoff() uint64 + // XDPoS-specific method + BlockChain() *core.BlockChain + // This is copied from filters.Backend // eth/filters needs to be initialized from this backend type, so methods needed by // it must also be included here.