mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-20 19:56:44 +00:00
XDCx Trading Package: - XDCx.go - Main DEX engine - order.go - Order types and handling - order_processor.go - Order processing logic - trade.go - Trade execution - matcher.go - Order matching engine - api.go - Public/Private APIs - tradingstate/ - Trading state management XDCxLending Package: - XDCxlending.go - Main lending protocol - order.go - Lending order types - order_processor.go - Lending order processing - trade.go - Lending trade execution - liquidator.go - Loan liquidation logic - api.go - Lending APIs - lendingstate/ - Lending state management eth/downloader Integration: - downloader_xdc.go - XDC sync extension - queue_xdc.go - XDC-specific download queue - statesync_xdc.go - XDC state synchronization eth/tracers Support: - api_xdc.go - XDC tracing APIs - native/xdc_tracer.go - XDC-specific tracer Additional Consensus: - engines/engine_v1.go - XDPoS v1 implementation - engines/engine_v1_test.go - Engine tests - core/rawdb/accessors_xdc_indexes.go - XDC database indexes Configuration & Tools: - cmd/utils/flags_xdc.go - XDC CLI flags - eth/ethconfig/config_xdc.go - XDC configuration Documentation: - docs/XDPoS.md - Consensus documentation - docs/XDCx.md - DEX documentation - docs/XDCxLending.md - Lending documentation - docs/SYNC.md - Sync guide Docker & CI: - Dockerfile.dev - Development image - Dockerfile.testnet - Testnet image - docker-compose.yml - Multi-service setup - .github/workflows/xdc-test.yml - Test workflow - prometheus.yml - Monitoring config - scripts/ - Node management scripts Total files in PR: ~172
400 lines
11 KiB
Go
400 lines
11 KiB
Go
// Copyright 2021 XDC Network
|
|
// This file is part of the XDC library.
|
|
|
|
package engines
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/consensus"
|
|
"github.com/ethereum/go-ethereum/core/state"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"github.com/ethereum/go-ethereum/trie"
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidTimestampV1 is returned if the timestamp is not in correct range
|
|
ErrInvalidTimestampV1 = errors.New("invalid timestamp for XDPoS v1")
|
|
// ErrInvalidDifficultyV1 is returned if the difficulty is invalid
|
|
ErrInvalidDifficultyV1 = errors.New("invalid difficulty for XDPoS v1")
|
|
// ErrUnauthorizedSignerV1 is returned if the signer is not authorized
|
|
ErrUnauthorizedSignerV1 = errors.New("unauthorized signer for XDPoS v1")
|
|
// ErrMissingSignatureV1 is returned if signature is missing
|
|
ErrMissingSignatureV1 = errors.New("missing signature in XDPoS v1 block")
|
|
)
|
|
|
|
// EngineV1 implements the XDPoS v1 consensus engine
|
|
type EngineV1 struct {
|
|
config *params.XDPoSConfig
|
|
db Database
|
|
|
|
// Snapshot cache
|
|
recents *lru
|
|
signatures *lru
|
|
|
|
// Signing
|
|
signer common.Address
|
|
signFn SignerFn
|
|
lock sync.RWMutex
|
|
|
|
// Block time
|
|
period uint64
|
|
}
|
|
|
|
// Database interface for engine
|
|
type Database interface {
|
|
Get(key []byte) ([]byte, error)
|
|
Put(key []byte, value []byte) error
|
|
Delete(key []byte) error
|
|
Has(key []byte) (bool, error)
|
|
}
|
|
|
|
// SignerFn is a signature function
|
|
type SignerFn func(account common.Address, data []byte) ([]byte, error)
|
|
|
|
// lru is a simple LRU cache (placeholder)
|
|
type lru struct {
|
|
items map[common.Hash]interface{}
|
|
lock sync.Mutex
|
|
}
|
|
|
|
func newLRU(size int) *lru {
|
|
return &lru{items: make(map[common.Hash]interface{})}
|
|
}
|
|
|
|
func (l *lru) Get(key common.Hash) (interface{}, bool) {
|
|
l.lock.Lock()
|
|
defer l.lock.Unlock()
|
|
v, ok := l.items[key]
|
|
return v, ok
|
|
}
|
|
|
|
func (l *lru) Add(key common.Hash, value interface{}) {
|
|
l.lock.Lock()
|
|
defer l.lock.Unlock()
|
|
l.items[key] = value
|
|
}
|
|
|
|
// NewEngineV1 creates a new XDPoS v1 engine
|
|
func NewEngineV1(config *params.XDPoSConfig, db Database) *EngineV1 {
|
|
period := uint64(2)
|
|
if config != nil && config.Period > 0 {
|
|
period = config.Period
|
|
}
|
|
|
|
return &EngineV1{
|
|
config: config,
|
|
db: db,
|
|
recents: newLRU(100),
|
|
signatures: newLRU(1000),
|
|
period: period,
|
|
}
|
|
}
|
|
|
|
// Author returns the signer of the block
|
|
func (e *EngineV1) Author(header *types.Header) (common.Address, error) {
|
|
return ecrecover(header, e.signatures)
|
|
}
|
|
|
|
// VerifyHeader checks whether a header conforms to the consensus rules
|
|
func (e *EngineV1) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error {
|
|
return e.verifyHeader(chain, header, nil)
|
|
}
|
|
|
|
// VerifyHeaders verifies a batch of headers concurrently
|
|
func (e *EngineV1) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) {
|
|
abort := make(chan struct{})
|
|
results := make(chan error, len(headers))
|
|
|
|
go func() {
|
|
for i, header := range headers {
|
|
var parent *types.Header
|
|
if i > 0 {
|
|
parent = headers[i-1]
|
|
}
|
|
err := e.verifyHeader(chain, header, parent)
|
|
select {
|
|
case <-abort:
|
|
return
|
|
case results <- err:
|
|
}
|
|
}
|
|
}()
|
|
|
|
return abort, results
|
|
}
|
|
|
|
// verifyHeader checks header validity
|
|
func (e *EngineV1) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header) error {
|
|
if header.Number == nil {
|
|
return errors.New("missing block number")
|
|
}
|
|
|
|
// Don't verify genesis block
|
|
if header.Number.Uint64() == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Get parent if not provided
|
|
if parent == nil {
|
|
parent = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
|
|
if parent == nil {
|
|
return consensus.ErrUnknownAncestor
|
|
}
|
|
}
|
|
|
|
// Verify timestamp
|
|
if header.Time <= parent.Time {
|
|
return ErrInvalidTimestampV1
|
|
}
|
|
|
|
// Verify that the block doesn't come from the future
|
|
if header.Time > uint64(time.Now().Unix())+30 {
|
|
return consensus.ErrFutureBlock
|
|
}
|
|
|
|
// Verify extra data length
|
|
if len(header.Extra) < crypto.SignatureLength {
|
|
return ErrMissingSignatureV1
|
|
}
|
|
|
|
// Verify signer
|
|
signer, err := ecrecover(header, e.signatures)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if signer is authorized
|
|
if !e.isAuthorized(chain, header, signer) {
|
|
return ErrUnauthorizedSignerV1
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyUncles implements consensus.Engine, always returning an error for any
|
|
// uncles as this consensus mechanism doesn't permit uncles.
|
|
func (e *EngineV1) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
|
|
if len(block.Uncles()) > 0 {
|
|
return errors.New("uncles not allowed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Prepare initializes the consensus fields of a block header
|
|
func (e *EngineV1) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
|
|
header.Coinbase = e.signer
|
|
header.Nonce = types.BlockNonce{}
|
|
header.Difficulty = big.NewInt(1)
|
|
|
|
// Set extra data
|
|
if len(header.Extra) < ExtraVanity {
|
|
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, ExtraVanity-len(header.Extra))...)
|
|
}
|
|
header.Extra = header.Extra[:ExtraVanity]
|
|
|
|
// Add space for signature
|
|
header.Extra = append(header.Extra, make([]byte, crypto.SignatureLength)...)
|
|
|
|
// Mix digest is not used
|
|
header.MixDigest = common.Hash{}
|
|
|
|
// Set the correct timestamp
|
|
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
|
|
if parent == nil {
|
|
return consensus.ErrUnknownAncestor
|
|
}
|
|
header.Time = parent.Time + e.period
|
|
|
|
return nil
|
|
}
|
|
|
|
// Finalize implements consensus.Engine
|
|
func (e *EngineV1) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
|
|
// Accumulate block rewards
|
|
e.accumulateRewards(chain, state, header)
|
|
}
|
|
|
|
// FinalizeAndAssemble implements consensus.Engine
|
|
func (e *EngineV1) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) {
|
|
// Finalize block
|
|
e.Finalize(chain, header, state, txs, uncles, withdrawals)
|
|
|
|
// Compute state root
|
|
header.Root = state.IntermediateRoot(true)
|
|
|
|
// Assemble and return the final block
|
|
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil
|
|
}
|
|
|
|
// Seal implements consensus.Engine
|
|
func (e *EngineV1) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
|
header := block.Header()
|
|
|
|
// Don't seal genesis block
|
|
if header.Number.Uint64() == 0 {
|
|
return errors.New("cannot seal genesis block")
|
|
}
|
|
|
|
e.lock.RLock()
|
|
signer, signFn := e.signer, e.signFn
|
|
e.lock.RUnlock()
|
|
|
|
// Sign the header
|
|
sighash, err := signHash(header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signature, err := signFn(signer, sighash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy signature to extra data
|
|
copy(header.Extra[len(header.Extra)-crypto.SignatureLength:], signature)
|
|
|
|
select {
|
|
case results <- block.WithSeal(header):
|
|
case <-stop:
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SealHash returns the hash of a block prior to it being sealed
|
|
func (e *EngineV1) SealHash(header *types.Header) common.Hash {
|
|
return sigHash(header)
|
|
}
|
|
|
|
// CalcDifficulty returns the difficulty for a block
|
|
func (e *EngineV1) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
|
|
return big.NewInt(1)
|
|
}
|
|
|
|
// APIs returns the RPC APIs this consensus engine provides
|
|
func (e *EngineV1) APIs(chain consensus.ChainHeaderReader) []rpc.API {
|
|
return nil
|
|
}
|
|
|
|
// Close implements consensus.Engine
|
|
func (e *EngineV1) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// Authorize injects a private key for signing
|
|
func (e *EngineV1) Authorize(signer common.Address, signFn SignerFn) {
|
|
e.lock.Lock()
|
|
defer e.lock.Unlock()
|
|
|
|
e.signer = signer
|
|
e.signFn = signFn
|
|
}
|
|
|
|
// isAuthorized checks if a signer is authorized
|
|
func (e *EngineV1) isAuthorized(chain consensus.ChainHeaderReader, header *types.Header, signer common.Address) bool {
|
|
// Get snapshot at parent
|
|
// Check if signer is in the validator set
|
|
// For now, return true
|
|
return true
|
|
}
|
|
|
|
// accumulateRewards accumulates the block and uncle rewards
|
|
func (e *EngineV1) accumulateRewards(chain consensus.ChainHeaderReader, state *state.StateDB, header *types.Header) {
|
|
// Block reward is handled by the foundation reward contract
|
|
log.Debug("Accumulating rewards", "block", header.Number)
|
|
}
|
|
|
|
// ecrecover extracts the signer from a header
|
|
func ecrecover(header *types.Header, sigcache *lru) (common.Address, error) {
|
|
hash := header.Hash()
|
|
|
|
if address, known := sigcache.Get(hash); known {
|
|
return address.(common.Address), nil
|
|
}
|
|
|
|
if len(header.Extra) < crypto.SignatureLength {
|
|
return common.Address{}, ErrMissingSignatureV1
|
|
}
|
|
signature := header.Extra[len(header.Extra)-crypto.SignatureLength:]
|
|
|
|
// Recover the public key
|
|
pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), signature)
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
|
|
var signer common.Address
|
|
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
|
|
|
sigcache.Add(hash, signer)
|
|
return signer, nil
|
|
}
|
|
|
|
// sigHash returns the hash to be signed
|
|
func sigHash(header *types.Header) common.Hash {
|
|
return sigHashWithExtra(header, header.Extra[:len(header.Extra)-crypto.SignatureLength])
|
|
}
|
|
|
|
// sigHashWithExtra returns the hash to be signed with specific extra data
|
|
func sigHashWithExtra(header *types.Header, extra []byte) common.Hash {
|
|
type sigHeader struct {
|
|
ParentHash common.Hash
|
|
UncleHash common.Hash
|
|
Coinbase common.Address
|
|
Root common.Hash
|
|
TxHash common.Hash
|
|
ReceiptHash common.Hash
|
|
Bloom types.Bloom
|
|
Difficulty *big.Int
|
|
Number *big.Int
|
|
GasLimit uint64
|
|
GasUsed uint64
|
|
Time uint64
|
|
Extra []byte
|
|
MixDigest common.Hash
|
|
Nonce types.BlockNonce
|
|
}
|
|
|
|
data, _ := rlp.EncodeToBytes(&sigHeader{
|
|
ParentHash: header.ParentHash,
|
|
UncleHash: header.UncleHash,
|
|
Coinbase: header.Coinbase,
|
|
Root: header.Root,
|
|
TxHash: header.TxHash,
|
|
ReceiptHash: header.ReceiptHash,
|
|
Bloom: header.Bloom,
|
|
Difficulty: header.Difficulty,
|
|
Number: header.Number,
|
|
GasLimit: header.GasLimit,
|
|
GasUsed: header.GasUsed,
|
|
Time: header.Time,
|
|
Extra: extra,
|
|
MixDigest: header.MixDigest,
|
|
Nonce: header.Nonce,
|
|
})
|
|
|
|
return crypto.Keccak256Hash(data)
|
|
}
|
|
|
|
// signHash returns a hash for signing
|
|
func signHash(header *types.Header) ([]byte, error) {
|
|
return sigHash(header).Bytes(), nil
|
|
}
|
|
|
|
// ExtraVanity is the fixed number of extra-data prefix bytes
|
|
const ExtraVanity = 32
|
|
|
|
// rpc.API placeholder
|
|
type rpc struct{}
|
|
type API struct{}
|