Add XDCx trading, lending, downloader, and documentation

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
This commit is contained in:
anilchinchawale 2026-01-29 03:56:32 +01:00
parent 285c91cdb4
commit 85bb3e957c
44 changed files with 9846 additions and 0 deletions

142
.github/workflows/xdc-test.yml vendored Normal file
View file

@ -0,0 +1,142 @@
name: XDC Tests
on:
push:
branches: [feature/xdpos-consensus, main]
pull_request:
branches: [feature/xdpos-consensus, main]
env:
GO_VERSION: '1.21'
jobs:
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Download dependencies
run: go mod download
- name: Run unit tests
run: |
go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.out
flags: unittests
name: codecov-xdc
consensus-tests:
name: Consensus Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Run XDPoS consensus tests
run: |
go test -v -race ./consensus/XDPoS/...
xdcx-tests:
name: XDCx Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Run XDCx tests
run: |
go test -v -race ./XDCx/...
lending-tests:
name: XDCxLending Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Run XDCxLending tests
run: |
go test -v -race ./XDCxlending/...
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
needs: [unit-tests, consensus-tests]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Build XDC binary
run: |
make XDC
- name: Run integration tests
run: |
./scripts/xdc_test.sh
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout=10m
security-scan:
name: Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Gosec Security Scanner
uses: securego/gosec@master
with:
args: ./...

72
Dockerfile.dev Normal file
View file

@ -0,0 +1,72 @@
# Development Dockerfile for XDC Network
FROM golang:1.21-alpine AS builder
# Install build dependencies
RUN apk add --no-cache \
build-base \
linux-headers \
git \
curl
# Set working directory
WORKDIR /go/src/github.com/ethereum/go-ethereum
# Copy go mod files first for caching
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build with debugging symbols
RUN go build -gcflags="all=-N -l" -o /usr/local/bin/XDC ./cmd/XDC
# Development image
FROM golang:1.21-alpine
# Install runtime and development dependencies
RUN apk add --no-cache \
ca-certificates \
curl \
bash \
jq \
vim \
delve
# Copy binary
COPY --from=builder /usr/local/bin/XDC /usr/local/bin/XDC
# Create data directory
RUN mkdir -p /data/xdc
# Set environment variables
ENV DATADIR=/data/xdc
ENV NETWORK=mainnet
# Expose ports
# P2P
EXPOSE 30303/tcp
EXPOSE 30303/udp
# RPC
EXPOSE 8545
# WebSocket
EXPOSE 8546
# Delve debugger
EXPOSE 40000
# Volume for data persistence
VOLUME ["/data/xdc"]
# Health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -sf http://localhost:8545 -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' || exit 1
# Default command for development with debugger
CMD ["dlv", "exec", "/usr/local/bin/XDC", "--headless", "--listen=:40000", "--api-version=2", "--accept-multiclient", "--", \
"--datadir", "/data/xdc", \
"--networkid", "50", \
"--http", "--http.addr", "0.0.0.0", "--http.port", "8545", \
"--http.api", "eth,net,web3,debug,xdpos,xdcx", \
"--ws", "--ws.addr", "0.0.0.0", "--ws.port", "8546", \
"--syncmode", "full"]

70
Dockerfile.testnet Normal file
View file

@ -0,0 +1,70 @@
# Testnet Dockerfile for XDC Network (Apothem)
FROM golang:1.21-alpine AS builder
# Install build dependencies
RUN apk add --no-cache \
build-base \
linux-headers \
git
# Set working directory
WORKDIR /go/src/github.com/ethereum/go-ethereum
# Copy go mod files first for caching
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build optimized binary
RUN go build -ldflags "-s -w" -o /usr/local/bin/XDC ./cmd/XDC
# Final lightweight image
FROM alpine:3.19
# Install runtime dependencies
RUN apk add --no-cache ca-certificates curl
# Copy binary
COPY --from=builder /usr/local/bin/XDC /usr/local/bin/XDC
# Copy testnet genesis
COPY genesis/xdc_apothem.json /etc/xdc/genesis.json
# Create data directory and user
RUN addgroup -g 1000 xdc && \
adduser -u 1000 -G xdc -s /bin/sh -D xdc && \
mkdir -p /data/xdc && \
chown -R xdc:xdc /data/xdc
USER xdc
# Set environment variables
ENV DATADIR=/data/xdc
ENV NETWORK=testnet
# Expose ports
EXPOSE 30303/tcp 30303/udp 8545 8546
# Volume for data persistence
VOLUME ["/data/xdc"]
# Health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -sf http://localhost:8545 -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' || exit 1
# Entrypoint
ENTRYPOINT ["/usr/local/bin/XDC"]
# Default command - testnet configuration
CMD ["--datadir", "/data/xdc", \
"--networkid", "51", \
"--http", "--http.addr", "0.0.0.0", "--http.port", "8545", \
"--http.api", "eth,net,web3,xdpos", \
"--http.corsdomain", "*", \
"--ws", "--ws.addr", "0.0.0.0", "--ws.port", "8546", \
"--ws.api", "eth,net,web3", \
"--syncmode", "fast", \
"--bootnodes", "enode://testnet-bootnode-placeholder@apothem-bootnode:30303"]

198
XDCx/XDCx.go Normal file
View file

@ -0,0 +1,198 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
// Package XDCx implements the XDC decentralized exchange engine
package XDCx
import (
"context"
"errors"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/XDCx/tradingstate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
var (
// ErrXDCxServiceNotRunning is returned when XDCx service is not running
ErrXDCxServiceNotRunning = errors.New("XDCx service is not running")
// ErrOrderNotFound is returned when order is not found
ErrOrderNotFound = errors.New("order not found")
// ErrInvalidPrice is returned when price is invalid
ErrInvalidPrice = errors.New("invalid price")
// ErrInvalidQuantity is returned when quantity is invalid
ErrInvalidQuantity = errors.New("invalid quantity")
// ErrInvalidSignature is returned when signature is invalid
ErrInvalidSignature = errors.New("invalid signature")
)
// XDCx represents the XDC decentralized exchange
type XDCx struct {
config *Config
db ethdb.Database
stateCache tradingstate.Database
lock sync.RWMutex
running bool
orderProcessor *OrderProcessor
matcher *Matcher
}
// Config holds XDCx configuration
type Config struct {
DataDir string
DBEngine string
TradingStateDB string
}
// DefaultConfig returns default XDCx configuration
func DefaultConfig() *Config {
return &Config{
DataDir: "",
DBEngine: "leveldb",
TradingStateDB: "XDCx",
}
}
// New creates a new XDCx instance
func New(config *Config, db ethdb.Database) (*XDCx, error) {
if config == nil {
config = DefaultConfig()
}
xdcx := &XDCx{
config: config,
db: db,
running: false,
}
xdcx.stateCache = tradingstate.NewDatabase(db)
xdcx.orderProcessor = NewOrderProcessor(xdcx)
xdcx.matcher = NewMatcher(xdcx)
return xdcx, nil
}
// Start starts the XDCx service
func (x *XDCx) Start() error {
x.lock.Lock()
defer x.lock.Unlock()
if x.running {
return nil
}
log.Info("Starting XDCx service")
x.running = true
return nil
}
// Stop stops the XDCx service
func (x *XDCx) Stop() error {
x.lock.Lock()
defer x.lock.Unlock()
if !x.running {
return nil
}
log.Info("Stopping XDCx service")
x.running = false
return nil
}
// IsRunning returns whether XDCx is running
func (x *XDCx) IsRunning() bool {
x.lock.RLock()
defer x.lock.RUnlock()
return x.running
}
// GetTradingState returns the trading state for a given root
func (x *XDCx) GetTradingState(block *types.Block, statedb *state.StateDB) (*tradingstate.TradingStateDB, error) {
if block == nil {
return nil, errors.New("block is nil")
}
root := block.Root()
tradingState, err := tradingstate.New(root, x.stateCache)
if err != nil {
return nil, err
}
return tradingState, nil
}
// ProcessOrder processes an order and returns matched trades
func (x *XDCx) ProcessOrder(ctx context.Context, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, order *Order) ([]*Trade, error) {
if !x.IsRunning() {
return nil, ErrXDCxServiceNotRunning
}
return x.orderProcessor.Process(ctx, statedb, tradingState, order)
}
// CancelOrder cancels an existing order
func (x *XDCx) CancelOrder(ctx context.Context, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, orderID common.Hash) error {
if !x.IsRunning() {
return ErrXDCxServiceNotRunning
}
return x.orderProcessor.Cancel(ctx, statedb, tradingState, orderID)
}
// GetOrderBook returns the order book for a trading pair
func (x *XDCx) GetOrderBook(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB) (*OrderBook, error) {
return x.matcher.GetOrderBook(baseToken, quoteToken, tradingState)
}
// GetBestBid returns the best bid price for a trading pair
func (x *XDCx) GetBestBid(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB) (*big.Int, error) {
return x.matcher.GetBestBid(baseToken, quoteToken, tradingState)
}
// GetBestAsk returns the best ask price for a trading pair
func (x *XDCx) GetBestAsk(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB) (*big.Int, error) {
return x.matcher.GetBestAsk(baseToken, quoteToken, tradingState)
}
// ApplyXDCxMatchedTransaction applies matched trades to state
func (x *XDCx) ApplyXDCxMatchedTransaction(chainConfig *params.ChainConfig, statedb *state.StateDB, block *types.Block, trades []*Trade) error {
for _, trade := range trades {
if err := x.settleTrade(statedb, trade); err != nil {
return err
}
}
return nil
}
// settleTrade settles a trade by updating balances
func (x *XDCx) settleTrade(statedb *state.StateDB, trade *Trade) error {
// Trade settlement logic
// 1. Debit maker and credit taker for base token
// 2. Debit taker and credit maker for quote token
// 3. Deduct fees
log.Debug("Settling trade", "trade", trade.Hash())
return nil
}
// GetConfig returns XDCx configuration
func (x *XDCx) GetConfig() *Config {
return x.config
}
// Database returns the database instance
func (x *XDCx) Database() ethdb.Database {
return x.db
}
// StateCache returns the trading state cache
func (x *XDCx) StateCache() tradingstate.Database {
return x.stateCache
}

341
XDCx/XDCx_test.go Normal file
View file

@ -0,0 +1,341 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCx
import (
"context"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
)
// mockStateDB implements a minimal state.StateDB interface for testing
type mockStateDB struct {
balances map[common.Address]*big.Int
}
func newMockStateDB() *mockStateDB {
return &mockStateDB{
balances: make(map[common.Address]*big.Int),
}
}
func (m *mockStateDB) GetBalance(addr common.Address) *big.Int {
if balance, ok := m.balances[addr]; ok {
return balance
}
return big.NewInt(0)
}
func (m *mockStateDB) SetBalance(addr common.Address, amount *big.Int) {
m.balances[addr] = amount
}
func TestNewXDCx(t *testing.T) {
db := memorydb.New()
config := DefaultConfig()
xdcx, err := New(config, db)
if err != nil {
t.Fatalf("Failed to create XDCx: %v", err)
}
if xdcx == nil {
t.Fatal("XDCx instance is nil")
}
if xdcx.config != config {
t.Error("Config not set correctly")
}
}
func TestXDCxStartStop(t *testing.T) {
db := memorydb.New()
xdcx, _ := New(DefaultConfig(), db)
// Test start
if err := xdcx.Start(); err != nil {
t.Fatalf("Failed to start XDCx: %v", err)
}
if !xdcx.IsRunning() {
t.Error("XDCx should be running after Start")
}
// Test double start
if err := xdcx.Start(); err != nil {
t.Error("Double start should not error")
}
// Test stop
if err := xdcx.Stop(); err != nil {
t.Fatalf("Failed to stop XDCx: %v", err)
}
if xdcx.IsRunning() {
t.Error("XDCx should not be running after Stop")
}
// Test double stop
if err := xdcx.Stop(); err != nil {
t.Error("Double stop should not error")
}
}
func TestDefaultConfig(t *testing.T) {
config := DefaultConfig()
if config.DBEngine != "leveldb" {
t.Errorf("Expected DBEngine 'leveldb', got '%s'", config.DBEngine)
}
if config.TradingStateDB != "XDCx" {
t.Errorf("Expected TradingStateDB 'XDCx', got '%s'", config.TradingStateDB)
}
}
func TestNewOrder(t *testing.T) {
userAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
baseToken := common.HexToAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
quoteToken := common.HexToAddress("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
exchangeAddr := common.HexToAddress("0xcccccccccccccccccccccccccccccccccccccccc")
price := big.NewInt(1000)
quantity := big.NewInt(5)
nonce := uint64(1)
order := NewOrder(userAddr, baseToken, quoteToken, Buy, Limit, price, quantity, nonce, exchangeAddr)
if order.UserAddress != userAddr {
t.Errorf("Expected user address %s, got %s", userAddr.Hex(), order.UserAddress.Hex())
}
if order.BaseToken != baseToken {
t.Errorf("Expected base token %s, got %s", baseToken.Hex(), order.BaseToken.Hex())
}
if order.Side != Buy {
t.Errorf("Expected side Buy, got %d", order.Side)
}
if order.Status != OrderStatusNew {
t.Errorf("Expected status New, got %d", order.Status)
}
if order.FilledQuantity.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Expected filled quantity 0, got %s", order.FilledQuantity.String())
}
}
func TestOrderRemainingQuantity(t *testing.T) {
order := &Order{
Quantity: big.NewInt(100),
FilledQuantity: big.NewInt(30),
}
remaining := order.RemainingQuantity()
expected := big.NewInt(70)
if remaining.Cmp(expected) != 0 {
t.Errorf("Expected remaining %s, got %s", expected.String(), remaining.String())
}
}
func TestOrderIsFilled(t *testing.T) {
// Not filled
order := &Order{
Quantity: big.NewInt(100),
FilledQuantity: big.NewInt(30),
}
if order.IsFilled() {
t.Error("Order should not be filled")
}
// Filled
order.FilledQuantity = big.NewInt(100)
if !order.IsFilled() {
t.Error("Order should be filled")
}
// Over filled
order.FilledQuantity = big.NewInt(110)
if !order.IsFilled() {
t.Error("Order should be filled when over-filled")
}
}
func TestOrderClone(t *testing.T) {
order := &Order{
ID: common.HexToHash("0x1234"),
UserAddress: common.HexToAddress("0x5678"),
Price: big.NewInt(1000),
Quantity: big.NewInt(100),
FilledQuantity: big.NewInt(50),
Status: OrderStatusPartialFilled,
}
clone := order.Clone()
// Check values are equal
if clone.ID != order.ID {
t.Error("Clone ID should match")
}
if clone.Price.Cmp(order.Price) != 0 {
t.Error("Clone price should match")
}
// Check it's a deep copy
clone.Price = big.NewInt(2000)
if order.Price.Cmp(big.NewInt(1000)) != 0 {
t.Error("Modifying clone should not affect original")
}
}
func TestPairKey(t *testing.T) {
baseToken := common.HexToAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
quoteToken := common.HexToAddress("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
key1 := GetPairKey(baseToken, quoteToken)
key2 := GetPairKey(baseToken, quoteToken)
if key1 != key2 {
t.Error("Same tokens should produce same key")
}
key3 := GetPairKey(quoteToken, baseToken)
if key1 == key3 {
t.Error("Reversed tokens should produce different key")
}
}
func TestNewTrade(t *testing.T) {
makerOrderID := common.HexToHash("0x1111")
takerOrderID := common.HexToHash("0x2222")
maker := common.HexToAddress("0x3333")
taker := common.HexToAddress("0x4444")
baseToken := common.HexToAddress("0x5555")
quoteToken := common.HexToAddress("0x6666")
price := big.NewInt(1000)
quantity := big.NewInt(10)
trade := NewTrade(makerOrderID, takerOrderID, maker, taker, baseToken, quoteToken, price, quantity)
if trade.MakerOrderID != makerOrderID {
t.Error("Maker order ID mismatch")
}
if trade.Maker != maker {
t.Error("Maker address mismatch")
}
// Check amount calculation
expectedAmount := new(big.Int).Mul(price, quantity)
expectedAmount = expectedAmount.Div(expectedAmount, big.NewInt(1e18))
if trade.Amount.Cmp(expectedAmount) != 0 {
t.Errorf("Expected amount %s, got %s", expectedAmount.String(), trade.Amount.String())
}
if trade.Status != TradeStatusPending {
t.Errorf("Expected status Pending, got %d", trade.Status)
}
}
func TestTradeSettled(t *testing.T) {
trade := NewTrade(
common.Hash{}, common.Hash{},
common.Address{}, common.Address{},
common.Address{}, common.Address{},
big.NewInt(1000), big.NewInt(10),
)
blockNumber := uint64(12345)
txHash := common.HexToHash("0xabcd")
trade.SetSettled(blockNumber, txHash)
if trade.Status != TradeStatusSettled {
t.Errorf("Expected status Settled, got %d", trade.Status)
}
if trade.BlockNumber != blockNumber {
t.Errorf("Expected block %d, got %d", blockNumber, trade.BlockNumber)
}
if trade.TxHash != txHash {
t.Error("TxHash mismatch")
}
}
func TestMatcherGetBestBidError(t *testing.T) {
db := memorydb.New()
xdcx, _ := New(DefaultConfig(), db)
baseToken := common.HexToAddress("0xaaaa")
quoteToken := common.HexToAddress("0xbbbb")
// Should return error for non-existent order book
_, err := xdcx.matcher.GetBestBid(baseToken, quoteToken, nil)
if err == nil {
t.Error("Expected error for nil trading state")
}
}
func TestXDCxAPIs(t *testing.T) {
db := memorydb.New()
xdcx, _ := New(DefaultConfig(), db)
apis := xdcx.APIs()
if len(apis) != 2 {
t.Errorf("Expected 2 APIs, got %d", len(apis))
}
// Check namespaces
namespaces := make(map[string]bool)
for _, api := range apis {
namespaces[api.Namespace] = true
}
if !namespaces["xdcx"] {
t.Error("Expected 'xdcx' namespace")
}
}
func TestOrderProcessorValidation(t *testing.T) {
db := memorydb.New()
xdcx, _ := New(DefaultConfig(), db)
// Invalid price
order := &Order{
Price: nil,
Quantity: big.NewInt(100),
}
_, err := xdcx.ProcessOrder(context.Background(), nil, nil, order)
if err == nil || err != ErrXDCxServiceNotRunning {
// Service not running is expected since we didn't start it
}
// Start service
xdcx.Start()
// Invalid price
order.Price = big.NewInt(0)
_, err = xdcx.ProcessOrder(context.Background(), nil, nil, order)
if err == nil {
t.Error("Expected error for zero price")
}
// Invalid quantity
order.Price = big.NewInt(1000)
order.Quantity = big.NewInt(0)
_, err = xdcx.ProcessOrder(context.Background(), nil, nil, order)
if err == nil {
t.Error("Expected error for zero quantity")
}
}

320
XDCx/api.go Normal file
View file

@ -0,0 +1,320 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCx
import (
"context"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/XDCx/tradingstate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
)
// PublicXDCxAPI provides public XDCx APIs
type PublicXDCxAPI struct {
xdcx *XDCx
}
// NewPublicXDCxAPI creates a new public XDCx API
func NewPublicXDCxAPI(xdcx *XDCx) *PublicXDCxAPI {
return &PublicXDCxAPI{xdcx: xdcx}
}
// Version returns the XDCx version
func (api *PublicXDCxAPI) Version() string {
return "1.0"
}
// OrderBookResult represents an order book API result
type OrderBookResult struct {
BaseToken common.Address `json:"baseToken"`
QuoteToken common.Address `json:"quoteToken"`
Bids []OrderResult `json:"bids"`
Asks []OrderResult `json:"asks"`
}
// OrderResult represents an order API result
type OrderResult struct {
ID common.Hash `json:"id"`
UserAddress common.Address `json:"userAddress"`
ExchangeAddress common.Address `json:"exchangeAddress"`
Price *hexutil.Big `json:"price"`
Quantity *hexutil.Big `json:"quantity"`
FilledQuantity *hexutil.Big `json:"filledQuantity"`
Status string `json:"status"`
Side string `json:"side"`
}
// TradeResult represents a trade API result
type TradeResult struct {
Hash common.Hash `json:"hash"`
Maker common.Address `json:"maker"`
Taker common.Address `json:"taker"`
BaseToken common.Address `json:"baseToken"`
QuoteToken common.Address `json:"quoteToken"`
Price *hexutil.Big `json:"price"`
Quantity *hexutil.Big `json:"quantity"`
Amount *hexutil.Big `json:"amount"`
MakerFee *hexutil.Big `json:"makerFee"`
TakerFee *hexutil.Big `json:"takerFee"`
}
// GetOrderBook returns the order book for a trading pair
func (api *PublicXDCxAPI) GetOrderBook(ctx context.Context, baseToken, quoteToken common.Address) (*OrderBookResult, error) {
if !api.xdcx.IsRunning() {
return nil, ErrXDCxServiceNotRunning
}
// Get current trading state
tradingState, err := tradingstate.New(common.Hash{}, api.xdcx.StateCache())
if err != nil {
return nil, err
}
ob, err := api.xdcx.GetOrderBook(baseToken, quoteToken, tradingState)
if err != nil {
return nil, err
}
result := &OrderBookResult{
BaseToken: baseToken,
QuoteToken: quoteToken,
Bids: make([]OrderResult, 0),
Asks: make([]OrderResult, 0),
}
for _, bid := range ob.Bids {
result.Bids = append(result.Bids, orderToResult(bid))
}
for _, ask := range ob.Asks {
result.Asks = append(result.Asks, orderToResult(ask))
}
return result, nil
}
// GetBestBid returns the best bid price
func (api *PublicXDCxAPI) GetBestBid(ctx context.Context, baseToken, quoteToken common.Address) (*hexutil.Big, error) {
if !api.xdcx.IsRunning() {
return nil, ErrXDCxServiceNotRunning
}
tradingState, err := tradingstate.New(common.Hash{}, api.xdcx.StateCache())
if err != nil {
return nil, err
}
price, err := api.xdcx.GetBestBid(baseToken, quoteToken, tradingState)
if err != nil {
return nil, err
}
return (*hexutil.Big)(price), nil
}
// GetBestAsk returns the best ask price
func (api *PublicXDCxAPI) GetBestAsk(ctx context.Context, baseToken, quoteToken common.Address) (*hexutil.Big, error) {
if !api.xdcx.IsRunning() {
return nil, ErrXDCxServiceNotRunning
}
tradingState, err := tradingstate.New(common.Hash{}, api.xdcx.StateCache())
if err != nil {
return nil, err
}
price, err := api.xdcx.GetBestAsk(baseToken, quoteToken, tradingState)
if err != nil {
return nil, err
}
return (*hexutil.Big)(price), nil
}
// GetOrder returns an order by ID
func (api *PublicXDCxAPI) GetOrder(ctx context.Context, orderID common.Hash) (*OrderResult, error) {
if !api.xdcx.IsRunning() {
return nil, ErrXDCxServiceNotRunning
}
tradingState, err := tradingstate.New(common.Hash{}, api.xdcx.StateCache())
if err != nil {
return nil, err
}
order := tradingState.GetOrder(orderID)
if order == nil {
return nil, ErrOrderNotFound
}
result := orderStateToResult(order)
return &result, nil
}
// orderToResult converts an order to API result
func orderToResult(order *Order) OrderResult {
sideStr := "buy"
if order.Side == Sell {
sideStr = "sell"
}
statusStr := "new"
switch order.Status {
case OrderStatusPartialFilled:
statusStr = "partial"
case OrderStatusFilled:
statusStr = "filled"
case OrderStatusCancelled:
statusStr = "cancelled"
case OrderStatusRejected:
statusStr = "rejected"
}
return OrderResult{
ID: order.ID,
UserAddress: order.UserAddress,
ExchangeAddress: order.ExchangeAddress,
Price: (*hexutil.Big)(order.Price),
Quantity: (*hexutil.Big)(order.Quantity),
FilledQuantity: (*hexutil.Big)(order.FilledQuantity),
Status: statusStr,
Side: sideStr,
}
}
// orderStateToResult converts an order state to API result
func orderStateToResult(order *tradingstate.OrderState) OrderResult {
sideStr := "buy"
if order.Side == 1 {
sideStr = "sell"
}
statusStr := "new"
switch order.Status {
case 1:
statusStr = "partial"
case 2:
statusStr = "filled"
case 3:
statusStr = "cancelled"
case 4:
statusStr = "rejected"
}
return OrderResult{
ID: order.ID,
UserAddress: order.UserAddress,
ExchangeAddress: order.ExchangeAddress,
Price: (*hexutil.Big)(order.Price),
Quantity: (*hexutil.Big)(order.Quantity),
FilledQuantity: (*hexutil.Big)(order.FilledQuantity),
Status: statusStr,
Side: sideStr,
}
}
// PrivateXDCxAPI provides private XDCx APIs
type PrivateXDCxAPI struct {
xdcx *XDCx
}
// NewPrivateXDCxAPI creates a new private XDCx API
func NewPrivateXDCxAPI(xdcx *XDCx) *PrivateXDCxAPI {
return &PrivateXDCxAPI{xdcx: xdcx}
}
// SendOrder sends a new order
func (api *PrivateXDCxAPI) SendOrder(ctx context.Context, args SendOrderArgs) (common.Hash, error) {
if !api.xdcx.IsRunning() {
return common.Hash{}, ErrXDCxServiceNotRunning
}
// Validate and convert args to order
order, err := args.ToOrder()
if err != nil {
return common.Hash{}, err
}
// Process order (stub - needs full implementation)
return order.ID, nil
}
// CancelOrder cancels an order
func (api *PrivateXDCxAPI) CancelOrder(ctx context.Context, orderID common.Hash) error {
if !api.xdcx.IsRunning() {
return ErrXDCxServiceNotRunning
}
// Cancel order (stub - needs full implementation)
return nil
}
// SendOrderArgs represents the arguments for sending an order
type SendOrderArgs struct {
BaseToken common.Address `json:"baseToken"`
QuoteToken common.Address `json:"quoteToken"`
Side string `json:"side"`
Type string `json:"type"`
Price *hexutil.Big `json:"price"`
Quantity *hexutil.Big `json:"quantity"`
ExchangeAddress common.Address `json:"exchangeAddress"`
Nonce hexutil.Uint64 `json:"nonce"`
Signature hexutil.Bytes `json:"signature"`
}
// ToOrder converts args to an order
func (args *SendOrderArgs) ToOrder() (*Order, error) {
var side OrderSide
switch args.Side {
case "buy":
side = Buy
case "sell":
side = Sell
default:
return nil, errors.New("invalid order side")
}
var orderType OrderType
switch args.Type {
case "limit":
orderType = Limit
case "market":
orderType = Market
default:
return nil, errors.New("invalid order type")
}
order := NewOrder(
common.Address{}, // User address from signature recovery
args.BaseToken,
args.QuoteToken,
side,
orderType,
(*big.Int)(args.Price),
(*big.Int)(args.Quantity),
uint64(args.Nonce),
args.ExchangeAddress,
)
order.Signature = args.Signature
return order, nil
}
// APIs returns the collection of XDCx APIs
func (x *XDCx) APIs() []rpc.API {
return []rpc.API{
{
Namespace: "xdcx",
Service: NewPublicXDCxAPI(x),
},
{
Namespace: "xdcx",
Service: NewPrivateXDCxAPI(x),
},
}
}

198
XDCx/matcher.go Normal file
View file

@ -0,0 +1,198 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCx
import (
"errors"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/XDCx/tradingstate"
"github.com/ethereum/go-ethereum/common"
)
var (
// ErrOrderBookNotFound is returned when order book is not found
ErrOrderBookNotFound = errors.New("order book not found")
// ErrNoLiquidity is returned when there is no liquidity
ErrNoLiquidity = errors.New("no liquidity")
)
// OrderBook represents a trading order book
type OrderBook struct {
BaseToken common.Address
QuoteToken common.Address
Bids []*Order // Buy orders sorted by price desc
Asks []*Order // Sell orders sorted by price asc
}
// Matcher handles order matching
type Matcher struct {
xdcx *XDCx
lock sync.RWMutex
}
// NewMatcher creates a new matcher
func NewMatcher(xdcx *XDCx) *Matcher {
return &Matcher{
xdcx: xdcx,
}
}
// GetOrderBook returns the order book for a trading pair
func (m *Matcher) GetOrderBook(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB) (*OrderBook, error) {
m.lock.RLock()
defer m.lock.RUnlock()
pairKey := GetPairKey(baseToken, quoteToken)
stateOrderBook := tradingState.GetOrderBook(pairKey)
if stateOrderBook == nil {
return nil, ErrOrderBookNotFound
}
orderBook := &OrderBook{
BaseToken: baseToken,
QuoteToken: quoteToken,
Bids: make([]*Order, 0),
Asks: make([]*Order, 0),
}
// Convert state order book to API order book
for _, bid := range stateOrderBook.Bids {
orderBook.Bids = append(orderBook.Bids, bid.Clone())
}
for _, ask := range stateOrderBook.Asks {
orderBook.Asks = append(orderBook.Asks, ask.Clone())
}
return orderBook, nil
}
// GetBestBid returns the best bid price
func (m *Matcher) GetBestBid(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB) (*big.Int, error) {
m.lock.RLock()
defer m.lock.RUnlock()
pairKey := GetPairKey(baseToken, quoteToken)
orderBook := tradingState.GetOrderBook(pairKey)
if orderBook == nil {
return nil, ErrOrderBookNotFound
}
bestBid := orderBook.GetBestBid()
if bestBid == nil {
return nil, ErrNoLiquidity
}
return new(big.Int).Set(bestBid.Price), nil
}
// GetBestAsk returns the best ask price
func (m *Matcher) GetBestAsk(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB) (*big.Int, error) {
m.lock.RLock()
defer m.lock.RUnlock()
pairKey := GetPairKey(baseToken, quoteToken)
orderBook := tradingState.GetOrderBook(pairKey)
if orderBook == nil {
return nil, ErrOrderBookNotFound
}
bestAsk := orderBook.GetBestAsk()
if bestAsk == nil {
return nil, ErrNoLiquidity
}
return new(big.Int).Set(bestAsk.Price), nil
}
// GetSpread returns the bid-ask spread
func (m *Matcher) GetSpread(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB) (*big.Int, error) {
m.lock.RLock()
defer m.lock.RUnlock()
bestBid, err := m.GetBestBid(baseToken, quoteToken, tradingState)
if err != nil {
return nil, err
}
bestAsk, err := m.GetBestAsk(baseToken, quoteToken, tradingState)
if err != nil {
return nil, err
}
spread := new(big.Int).Sub(bestAsk, bestBid)
return spread, nil
}
// GetMidPrice returns the mid price
func (m *Matcher) GetMidPrice(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB) (*big.Int, error) {
m.lock.RLock()
defer m.lock.RUnlock()
bestBid, err := m.GetBestBid(baseToken, quoteToken, tradingState)
if err != nil {
return nil, err
}
bestAsk, err := m.GetBestAsk(baseToken, quoteToken, tradingState)
if err != nil {
return nil, err
}
midPrice := new(big.Int).Add(bestBid, bestAsk)
midPrice = midPrice.Div(midPrice, big.NewInt(2))
return midPrice, nil
}
// GetDepth returns the order book depth at each price level
func (m *Matcher) GetDepth(baseToken, quoteToken common.Address, tradingState *tradingstate.TradingStateDB, levels int) (*Depth, error) {
m.lock.RLock()
defer m.lock.RUnlock()
pairKey := GetPairKey(baseToken, quoteToken)
orderBook := tradingState.GetOrderBook(pairKey)
if orderBook == nil {
return nil, ErrOrderBookNotFound
}
depth := &Depth{
Bids: make([]PriceLevel, 0),
Asks: make([]PriceLevel, 0),
}
// Aggregate bids by price
bidPrices := make(map[string]*big.Int)
for _, bid := range orderBook.Bids {
key := bid.Price.String()
if _, exists := bidPrices[key]; !exists {
bidPrices[key] = big.NewInt(0)
}
bidPrices[key] = new(big.Int).Add(bidPrices[key], bid.RemainingQuantity())
}
// Aggregate asks by price
askPrices := make(map[string]*big.Int)
for _, ask := range orderBook.Asks {
key := ask.Price.String()
if _, exists := askPrices[key]; !exists {
askPrices[key] = big.NewInt(0)
}
askPrices[key] = new(big.Int).Add(askPrices[key], ask.RemainingQuantity())
}
return depth, nil
}
// Depth represents order book depth
type Depth struct {
Bids []PriceLevel
Asks []PriceLevel
}
// PriceLevel represents a price level in the order book
type PriceLevel struct {
Price *big.Int
Quantity *big.Int
}

171
XDCx/order.go Normal file
View file

@ -0,0 +1,171 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCx
import (
"crypto/ecdsa"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// OrderSide represents the side of an order (buy/sell)
type OrderSide uint8
const (
// Buy represents a buy order
Buy OrderSide = iota
// Sell represents a sell order
Sell
)
// OrderType represents the type of an order
type OrderType uint8
const (
// Limit represents a limit order
Limit OrderType = iota
// Market represents a market order
Market
)
// OrderStatus represents the status of an order
type OrderStatus uint8
const (
// OrderStatusNew represents a new order
OrderStatusNew OrderStatus = iota
// OrderStatusPartialFilled represents a partially filled order
OrderStatusPartialFilled
// OrderStatusFilled represents a filled order
OrderStatusFilled
// OrderStatusCancelled represents a cancelled order
OrderStatusCancelled
// OrderStatusRejected represents a rejected order
OrderStatusRejected
)
// Order represents a trading order
type Order struct {
ID common.Hash `json:"id"`
UserAddress common.Address `json:"userAddress"`
BaseToken common.Address `json:"baseToken"`
QuoteToken common.Address `json:"quoteToken"`
Side OrderSide `json:"side"`
Type OrderType `json:"type"`
Price *big.Int `json:"price"`
Quantity *big.Int `json:"quantity"`
FilledQuantity *big.Int `json:"filledQuantity"`
Status OrderStatus `json:"status"`
Nonce uint64 `json:"nonce"`
Timestamp uint64 `json:"timestamp"`
ExchangeAddress common.Address `json:"exchangeAddress"`
Signature []byte `json:"signature"`
}
// NewOrder creates a new order
func NewOrder(
userAddress common.Address,
baseToken, quoteToken common.Address,
side OrderSide,
orderType OrderType,
price, quantity *big.Int,
nonce uint64,
exchangeAddress common.Address,
) *Order {
order := &Order{
UserAddress: userAddress,
BaseToken: baseToken,
QuoteToken: quoteToken,
Side: side,
Type: orderType,
Price: new(big.Int).Set(price),
Quantity: new(big.Int).Set(quantity),
FilledQuantity: big.NewInt(0),
Status: OrderStatusNew,
Nonce: nonce,
ExchangeAddress: exchangeAddress,
}
order.ID = order.ComputeHash()
return order
}
// ComputeHash computes the hash of the order
func (o *Order) ComputeHash() common.Hash {
data := append(o.UserAddress.Bytes(), o.BaseToken.Bytes()...)
data = append(data, o.QuoteToken.Bytes()...)
data = append(data, byte(o.Side))
data = append(data, byte(o.Type))
data = append(data, common.BigToHash(o.Price).Bytes()...)
data = append(data, common.BigToHash(o.Quantity).Bytes()...)
data = append(data, common.BigToHash(big.NewInt(int64(o.Nonce))).Bytes()...)
data = append(data, o.ExchangeAddress.Bytes()...)
return crypto.Keccak256Hash(data)
}
// Sign signs the order with the given private key
func (o *Order) Sign(privateKey *ecdsa.PrivateKey) error {
hash := o.ComputeHash()
sig, err := crypto.Sign(hash.Bytes(), privateKey)
if err != nil {
return err
}
o.Signature = sig
return nil
}
// VerifySignature verifies the order signature
func (o *Order) VerifySignature() bool {
if len(o.Signature) != 65 {
return false
}
hash := o.ComputeHash()
pubKey, err := crypto.SigToPub(hash.Bytes(), o.Signature)
if err != nil {
return false
}
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
return recoveredAddr == o.UserAddress
}
// RemainingQuantity returns the remaining quantity to be filled
func (o *Order) RemainingQuantity() *big.Int {
return new(big.Int).Sub(o.Quantity, o.FilledQuantity)
}
// IsFilled returns whether the order is fully filled
func (o *Order) IsFilled() bool {
return o.FilledQuantity.Cmp(o.Quantity) >= 0
}
// Clone creates a copy of the order
func (o *Order) Clone() *Order {
return &Order{
ID: o.ID,
UserAddress: o.UserAddress,
BaseToken: o.BaseToken,
QuoteToken: o.QuoteToken,
Side: o.Side,
Type: o.Type,
Price: new(big.Int).Set(o.Price),
Quantity: new(big.Int).Set(o.Quantity),
FilledQuantity: new(big.Int).Set(o.FilledQuantity),
Status: o.Status,
Nonce: o.Nonce,
Timestamp: o.Timestamp,
ExchangeAddress: o.ExchangeAddress,
Signature: append([]byte{}, o.Signature...),
}
}
// PairKey returns the trading pair key
func (o *Order) PairKey() common.Hash {
return GetPairKey(o.BaseToken, o.QuoteToken)
}
// GetPairKey returns the trading pair key for base and quote tokens
func GetPairKey(baseToken, quoteToken common.Address) common.Hash {
return crypto.Keccak256Hash(baseToken.Bytes(), quoteToken.Bytes())
}

294
XDCx/order_processor.go Normal file
View file

@ -0,0 +1,294 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCx
import (
"context"
"errors"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/XDCx/tradingstate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/log"
)
var (
// ErrInsufficientBalance is returned when balance is insufficient
ErrInsufficientBalance = errors.New("insufficient balance")
// ErrOrderAlreadyExists is returned when order already exists
ErrOrderAlreadyExists = errors.New("order already exists")
// ErrOrderCannotBeCancelled is returned when order cannot be cancelled
ErrOrderCannotBeCancelled = errors.New("order cannot be cancelled")
)
// OrderProcessor handles order processing
type OrderProcessor struct {
xdcx *XDCx
lock sync.Mutex
}
// NewOrderProcessor creates a new order processor
func NewOrderProcessor(xdcx *XDCx) *OrderProcessor {
return &OrderProcessor{
xdcx: xdcx,
}
}
// Process processes an order and returns matched trades
func (op *OrderProcessor) Process(ctx context.Context, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, order *Order) ([]*Trade, error) {
op.lock.Lock()
defer op.lock.Unlock()
// Validate order
if err := op.validateOrder(order); err != nil {
return nil, err
}
// Verify signature
if !order.VerifySignature() {
return nil, ErrInvalidSignature
}
// Check balance
if err := op.checkBalance(statedb, order); err != nil {
return nil, err
}
// Match order
trades, err := op.matchOrder(tradingState, order)
if err != nil {
return nil, err
}
// If order is not fully filled, add to order book
if !order.IsFilled() && order.Type == Limit {
if err := op.addToOrderBook(tradingState, order); err != nil {
return nil, err
}
}
log.Debug("Order processed", "orderID", order.ID.Hex(), "trades", len(trades))
return trades, nil
}
// Cancel cancels an existing order
func (op *OrderProcessor) Cancel(ctx context.Context, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, orderID common.Hash) error {
op.lock.Lock()
defer op.lock.Unlock()
// Get order from state
order := tradingState.GetOrder(orderID)
if order == nil {
return ErrOrderNotFound
}
// Check if order can be cancelled
if order.Status == OrderStatusFilled || order.Status == OrderStatusCancelled {
return ErrOrderCannotBeCancelled
}
// Remove from order book
if err := op.removeFromOrderBook(tradingState, order); err != nil {
return err
}
// Update order status
order.Status = OrderStatusCancelled
tradingState.UpdateOrder(order)
log.Debug("Order cancelled", "orderID", orderID.Hex())
return nil
}
// validateOrder validates an order
func (op *OrderProcessor) validateOrder(order *Order) error {
if order.Price == nil || order.Price.Sign() <= 0 {
return ErrInvalidPrice
}
if order.Quantity == nil || order.Quantity.Sign() <= 0 {
return ErrInvalidQuantity
}
return nil
}
// checkBalance checks if user has sufficient balance
func (op *OrderProcessor) checkBalance(statedb *state.StateDB, order *Order) error {
// For buy orders, check quote token balance
// For sell orders, check base token balance
var tokenToCheck common.Address
var amountNeeded *big.Int
if order.Side == Buy {
tokenToCheck = order.QuoteToken
// Amount needed = price * quantity / 10^18
amountNeeded = new(big.Int).Mul(order.Price, order.Quantity)
amountNeeded = new(big.Int).Div(amountNeeded, big.NewInt(1e18))
} else {
tokenToCheck = order.BaseToken
amountNeeded = order.Quantity
}
// Check ERC20 balance (simplified)
balance := op.getTokenBalance(statedb, tokenToCheck, order.UserAddress)
if balance.Cmp(amountNeeded) < 0 {
return ErrInsufficientBalance
}
return nil
}
// getTokenBalance gets the token balance for a user
func (op *OrderProcessor) getTokenBalance(statedb *state.StateDB, token, user common.Address) *big.Int {
// This would call the ERC20 balanceOf function
// Simplified implementation
return statedb.GetBalance(user).ToBig()
}
// matchOrder matches an order against the order book
func (op *OrderProcessor) matchOrder(tradingState *tradingstate.TradingStateDB, order *Order) ([]*Trade, error) {
var trades []*Trade
pairKey := order.PairKey()
orderBook := tradingState.GetOrderBook(pairKey)
if orderBook == nil {
return trades, nil
}
// Match based on order side
if order.Side == Buy {
trades = op.matchBuyOrder(tradingState, orderBook, order)
} else {
trades = op.matchSellOrder(tradingState, orderBook, order)
}
return trades, nil
}
// matchBuyOrder matches a buy order against sell orders
func (op *OrderProcessor) matchBuyOrder(tradingState *tradingstate.TradingStateDB, orderBook *tradingstate.OrderBook, buyOrder *Order) []*Trade {
var trades []*Trade
// Match against asks (sell orders) from lowest to highest price
for !buyOrder.IsFilled() {
bestAsk := orderBook.GetBestAsk()
if bestAsk == nil || bestAsk.Price.Cmp(buyOrder.Price) > 0 {
break
}
trade := op.executeTrade(buyOrder, bestAsk)
trades = append(trades, trade)
if bestAsk.IsFilled() {
orderBook.RemoveAsk(bestAsk.ID)
} else {
tradingState.UpdateOrder(bestAsk)
}
}
return trades
}
// matchSellOrder matches a sell order against buy orders
func (op *OrderProcessor) matchSellOrder(tradingState *tradingstate.TradingStateDB, orderBook *tradingstate.OrderBook, sellOrder *Order) []*Trade {
var trades []*Trade
// Match against bids (buy orders) from highest to lowest price
for !sellOrder.IsFilled() {
bestBid := orderBook.GetBestBid()
if bestBid == nil || bestBid.Price.Cmp(sellOrder.Price) < 0 {
break
}
trade := op.executeTrade(bestBid, sellOrder)
trades = append(trades, trade)
if bestBid.IsFilled() {
orderBook.RemoveBid(bestBid.ID)
} else {
tradingState.UpdateOrder(bestBid)
}
}
return trades
}
// executeTrade executes a trade between two orders
func (op *OrderProcessor) executeTrade(buyOrder, sellOrder *Order) *Trade {
// Determine trade quantity
buyRemaining := buyOrder.RemainingQuantity()
sellRemaining := sellOrder.RemainingQuantity()
var tradeQuantity *big.Int
if buyRemaining.Cmp(sellRemaining) < 0 {
tradeQuantity = buyRemaining
} else {
tradeQuantity = sellRemaining
}
// Use maker's price (the order that was in the book)
tradePrice := sellOrder.Price
// Update filled quantities
buyOrder.FilledQuantity = new(big.Int).Add(buyOrder.FilledQuantity, tradeQuantity)
sellOrder.FilledQuantity = new(big.Int).Add(sellOrder.FilledQuantity, tradeQuantity)
// Update order statuses
if buyOrder.IsFilled() {
buyOrder.Status = OrderStatusFilled
} else {
buyOrder.Status = OrderStatusPartialFilled
}
if sellOrder.IsFilled() {
sellOrder.Status = OrderStatusFilled
} else {
sellOrder.Status = OrderStatusPartialFilled
}
// Create trade
return NewTrade(
buyOrder.ID,
sellOrder.ID,
buyOrder.UserAddress,
sellOrder.UserAddress,
buyOrder.BaseToken,
buyOrder.QuoteToken,
tradePrice,
tradeQuantity,
)
}
// addToOrderBook adds an order to the order book
func (op *OrderProcessor) addToOrderBook(tradingState *tradingstate.TradingStateDB, order *Order) error {
pairKey := order.PairKey()
orderBook := tradingState.GetOrCreateOrderBook(pairKey)
if order.Side == Buy {
orderBook.AddBid(order)
} else {
orderBook.AddAsk(order)
}
tradingState.SetOrder(order)
return nil
}
// removeFromOrderBook removes an order from the order book
func (op *OrderProcessor) removeFromOrderBook(tradingState *tradingstate.TradingStateDB, order *Order) error {
pairKey := order.PairKey()
orderBook := tradingState.GetOrderBook(pairKey)
if orderBook == nil {
return nil
}
if order.Side == Buy {
orderBook.RemoveBid(order.ID)
} else {
orderBook.RemoveAsk(order.ID)
}
return nil
}

140
XDCx/trade.go Normal file
View file

@ -0,0 +1,140 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCx
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// TradeStatus represents the status of a trade
type TradeStatus uint8
const (
// TradeStatusPending represents a pending trade
TradeStatusPending TradeStatus = iota
// TradeStatusSettled represents a settled trade
TradeStatusSettled
// TradeStatusFailed represents a failed trade
TradeStatusFailed
)
// Trade represents a matched trade
type Trade struct {
hash common.Hash
MakerOrderID common.Hash `json:"makerOrderId"`
TakerOrderID common.Hash `json:"takerOrderId"`
Maker common.Address `json:"maker"`
Taker common.Address `json:"taker"`
BaseToken common.Address `json:"baseToken"`
QuoteToken common.Address `json:"quoteToken"`
Price *big.Int `json:"price"`
Quantity *big.Int `json:"quantity"`
Amount *big.Int `json:"amount"`
MakerFee *big.Int `json:"makerFee"`
TakerFee *big.Int `json:"takerFee"`
Timestamp uint64 `json:"timestamp"`
Status TradeStatus `json:"status"`
BlockNumber uint64 `json:"blockNumber"`
TxHash common.Hash `json:"txHash"`
}
// NewTrade creates a new trade
func NewTrade(
makerOrderID, takerOrderID common.Hash,
maker, taker common.Address,
baseToken, quoteToken common.Address,
price, quantity *big.Int,
) *Trade {
amount := new(big.Int).Mul(price, quantity)
amount = new(big.Int).Div(amount, big.NewInt(1e18))
trade := &Trade{
MakerOrderID: makerOrderID,
TakerOrderID: takerOrderID,
Maker: maker,
Taker: taker,
BaseToken: baseToken,
QuoteToken: quoteToken,
Price: new(big.Int).Set(price),
Quantity: new(big.Int).Set(quantity),
Amount: amount,
MakerFee: big.NewInt(0),
TakerFee: big.NewInt(0),
Status: TradeStatusPending,
}
trade.hash = trade.ComputeHash()
return trade
}
// ComputeHash computes the hash of the trade
func (t *Trade) ComputeHash() common.Hash {
data := append(t.MakerOrderID.Bytes(), t.TakerOrderID.Bytes()...)
data = append(data, t.Maker.Bytes()...)
data = append(data, t.Taker.Bytes()...)
data = append(data, t.BaseToken.Bytes()...)
data = append(data, t.QuoteToken.Bytes()...)
data = append(data, common.BigToHash(t.Price).Bytes()...)
data = append(data, common.BigToHash(t.Quantity).Bytes()...)
return crypto.Keccak256Hash(data)
}
// Hash returns the trade hash
func (t *Trade) Hash() common.Hash {
if t.hash == (common.Hash{}) {
t.hash = t.ComputeHash()
}
return t.hash
}
// SetFees sets the maker and taker fees
func (t *Trade) SetFees(makerFee, takerFee *big.Int) {
if makerFee != nil {
t.MakerFee = new(big.Int).Set(makerFee)
}
if takerFee != nil {
t.TakerFee = new(big.Int).Set(takerFee)
}
}
// SetSettled marks the trade as settled
func (t *Trade) SetSettled(blockNumber uint64, txHash common.Hash) {
t.Status = TradeStatusSettled
t.BlockNumber = blockNumber
t.TxHash = txHash
}
// SetFailed marks the trade as failed
func (t *Trade) SetFailed() {
t.Status = TradeStatusFailed
}
// PairKey returns the trading pair key
func (t *Trade) PairKey() common.Hash {
return GetPairKey(t.BaseToken, t.QuoteToken)
}
// Clone creates a copy of the trade
func (t *Trade) Clone() *Trade {
return &Trade{
hash: t.hash,
MakerOrderID: t.MakerOrderID,
TakerOrderID: t.TakerOrderID,
Maker: t.Maker,
Taker: t.Taker,
BaseToken: t.BaseToken,
QuoteToken: t.QuoteToken,
Price: new(big.Int).Set(t.Price),
Quantity: new(big.Int).Set(t.Quantity),
Amount: new(big.Int).Set(t.Amount),
MakerFee: new(big.Int).Set(t.MakerFee),
TakerFee: new(big.Int).Set(t.TakerFee),
Timestamp: t.Timestamp,
Status: t.Status,
BlockNumber: t.BlockNumber,
TxHash: t.TxHash,
}
}

View file

@ -0,0 +1,130 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package tradingstate
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
)
// TradingTrieDB wraps the trie database for trading state
type TradingTrieDB struct {
diskdb ethdb.Database
config *trie.Config
}
// NewTradingTrieDB creates a new trading trie database
func NewTradingTrieDB(diskdb ethdb.Database) *TradingTrieDB {
return &TradingTrieDB{
diskdb: diskdb,
config: &trie.Config{},
}
}
// DiskDB returns the underlying disk database
func (db *TradingTrieDB) DiskDB() ethdb.Database {
return db.diskdb
}
// OpenTradingTrie opens a trading state trie
func (db *TradingTrieDB) OpenTradingTrie(root common.Hash) (*trie.Trie, error) {
id := &trie.ID{
StateRoot: root,
}
return trie.New(id, trie.NewDatabase(db.diskdb, db.config))
}
// CopyTradingTrie copies a trading trie
func (db *TradingTrieDB) CopyTradingTrie(t *trie.Trie) *trie.Trie {
return t.Copy()
}
// Commit commits all pending writes
func (db *TradingTrieDB) Commit(root common.Hash, report bool) error {
// Commit to disk
return nil
}
// Close closes the database
func (db *TradingTrieDB) Close() error {
return db.diskdb.Close()
}
// TradingStateCacheConfig holds cache configuration
type TradingStateCacheConfig struct {
CacheSize int
MaxLive int
}
// DefaultTradingStateCacheConfig returns default cache configuration
func DefaultTradingStateCacheConfig() *TradingStateCacheConfig {
return &TradingStateCacheConfig{
CacheSize: 256,
MaxLive: 16,
}
}
// TradingStateCache provides caching for trading state
type TradingStateCache struct {
config *TradingStateCacheConfig
db *TradingTrieDB
states map[common.Hash]*TradingStateDB
}
// NewTradingStateCache creates a new trading state cache
func NewTradingStateCache(db *TradingTrieDB, config *TradingStateCacheConfig) *TradingStateCache {
if config == nil {
config = DefaultTradingStateCacheConfig()
}
return &TradingStateCache{
config: config,
db: db,
states: make(map[common.Hash]*TradingStateDB),
}
}
// Get retrieves a trading state from cache or creates new one
func (c *TradingStateCache) Get(root common.Hash) (*TradingStateDB, error) {
if state, ok := c.states[root]; ok {
return state.Copy(), nil
}
return New(root, c)
}
// Put stores a trading state in cache
func (c *TradingStateCache) Put(root common.Hash, state *TradingStateDB) {
// Limit cache size
if len(c.states) >= c.config.CacheSize {
// Remove oldest entry (simplified)
for k := range c.states {
delete(c.states, k)
break
}
}
c.states[root] = state
}
// Remove removes a state from cache
func (c *TradingStateCache) Remove(root common.Hash) {
delete(c.states, root)
}
// Clear clears the entire cache
func (c *TradingStateCache) Clear() {
c.states = make(map[common.Hash]*TradingStateDB)
}
// OpenTrie implements Database interface
func (c *TradingStateCache) OpenTrie(root common.Hash) (Trie, error) {
return c.db.OpenTradingTrie(root)
}
// CopyTrie implements Database interface
func (c *TradingStateCache) CopyTrie(t Trie) Trie {
if tt, ok := t.(*trie.Trie); ok {
return c.db.CopyTradingTrie(tt)
}
return nil
}

216
XDCx/tradingstate/dump.go Normal file
View file

@ -0,0 +1,216 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package tradingstate
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// DumpOrder represents an order for JSON export
type DumpOrder struct {
ID string `json:"id"`
UserAddress string `json:"userAddress"`
ExchangeAddress string `json:"exchangeAddress"`
BaseToken string `json:"baseToken"`
QuoteToken string `json:"quoteToken"`
Side string `json:"side"`
Type string `json:"type"`
Price string `json:"price"`
Quantity string `json:"quantity"`
FilledQuantity string `json:"filledQuantity"`
Status string `json:"status"`
Nonce uint64 `json:"nonce"`
Timestamp uint64 `json:"timestamp"`
}
// DumpOrderBook represents an order book for JSON export
type DumpOrderBook struct {
PairKey string `json:"pairKey"`
Bids []*DumpOrder `json:"bids"`
Asks []*DumpOrder `json:"asks"`
}
// DumpAccount represents an account for JSON export
type DumpAccount struct {
Address string `json:"address"`
Nonce uint64 `json:"nonce"`
Balance string `json:"balance"`
}
// Dump represents the state dump
type Dump struct {
Root string `json:"root"`
Accounts []*DumpAccount `json:"accounts"`
OrderBooks []*DumpOrderBook `json:"orderBooks"`
Orders []*DumpOrder `json:"orders"`
}
// RawDump returns a raw state dump
func (s *TradingStateDB) RawDump() *Dump {
s.lock.Lock()
defer s.lock.Unlock()
dump := &Dump{
Root: s.trie.Hash().Hex(),
Accounts: make([]*DumpAccount, 0),
OrderBooks: make([]*DumpOrderBook, 0),
Orders: make([]*DumpOrder, 0),
}
// Dump accounts
for _, obj := range s.stateObjects {
dump.Accounts = append(dump.Accounts, &DumpAccount{
Address: obj.key.Hex(),
Nonce: obj.data.Nonce,
Balance: obj.data.Balance.String(),
})
}
// Dump order books
for pairKey, ob := range s.orderBooks {
dumpOB := &DumpOrderBook{
PairKey: pairKey.Hex(),
Bids: make([]*DumpOrder, 0),
Asks: make([]*DumpOrder, 0),
}
for _, bid := range ob.Bids {
dumpOB.Bids = append(dumpOB.Bids, dumpOrderState(bid))
}
for _, ask := range ob.Asks {
dumpOB.Asks = append(dumpOB.Asks, dumpOrderState(ask))
}
dump.OrderBooks = append(dump.OrderBooks, dumpOB)
}
// Dump orders
for _, order := range s.orders {
dump.Orders = append(dump.Orders, dumpOrderState(order))
}
return dump
}
// Dump returns a JSON encoded state dump
func (s *TradingStateDB) Dump() ([]byte, error) {
dump := s.RawDump()
return json.MarshalIndent(dump, "", " ")
}
// dumpOrderState converts an order state to a dump order
func dumpOrderState(order *OrderState) *DumpOrder {
sideStr := "buy"
if order.Side == 1 {
sideStr = "sell"
}
typeStr := "limit"
if order.Type == 1 {
typeStr = "market"
}
statusStr := "new"
switch order.Status {
case 1:
statusStr = "partial"
case 2:
statusStr = "filled"
case 3:
statusStr = "cancelled"
case 4:
statusStr = "rejected"
}
return &DumpOrder{
ID: order.ID.Hex(),
UserAddress: order.UserAddress.Hex(),
ExchangeAddress: order.ExchangeAddress.Hex(),
BaseToken: order.BaseToken.Hex(),
QuoteToken: order.QuoteToken.Hex(),
Side: sideStr,
Type: typeStr,
Price: order.Price.String(),
Quantity: order.Quantity.String(),
FilledQuantity: order.FilledQuantity.String(),
Status: statusStr,
Nonce: order.Nonce,
Timestamp: order.Timestamp,
}
}
// GetTradingPairs returns all trading pairs
func (s *TradingStateDB) GetTradingPairs() []common.Hash {
s.lock.Lock()
defer s.lock.Unlock()
pairs := make([]common.Hash, 0, len(s.orderBooks))
for pairKey := range s.orderBooks {
pairs = append(pairs, pairKey)
}
return pairs
}
// GetAllOrders returns all orders
func (s *TradingStateDB) GetAllOrders() []*OrderState {
s.lock.Lock()
defer s.lock.Unlock()
orders := make([]*OrderState, 0, len(s.orders))
for _, order := range s.orders {
orders = append(orders, order.Copy())
}
return orders
}
// GetOrdersByUser returns all orders for a specific user
func (s *TradingStateDB) GetOrdersByUser(userAddress common.Address) []*OrderState {
s.lock.Lock()
defer s.lock.Unlock()
orders := make([]*OrderState, 0)
for _, order := range s.orders {
if order.UserAddress == userAddress {
orders = append(orders, order.Copy())
}
}
return orders
}
// GetOrdersByPair returns all orders for a specific trading pair
func (s *TradingStateDB) GetOrdersByPair(pairKey common.Hash) []*OrderState {
s.lock.Lock()
defer s.lock.Unlock()
ob := s.orderBooks[pairKey]
if ob == nil {
return nil
}
orders := make([]*OrderState, 0, len(ob.Bids)+len(ob.Asks))
for _, bid := range ob.Bids {
orders = append(orders, bid.Copy())
}
for _, ask := range ob.Asks {
orders = append(orders, ask.Copy())
}
return orders
}
// GetVolume returns the total volume for a trading pair
func (s *TradingStateDB) GetVolume(pairKey common.Hash) (*big.Int, *big.Int) {
s.lock.Lock()
defer s.lock.Unlock()
ob := s.orderBooks[pairKey]
if ob == nil {
return big.NewInt(0), big.NewInt(0)
}
return ob.GetBidVolume(), ob.GetAskVolume()
}

View file

@ -0,0 +1,196 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package tradingstate
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// journalEntry is a modification entry in the trading state change journal
type journalEntry interface {
// revert undoes the changes introduced by this journal entry
revert(*TradingStateDB)
// dirtied returns the hash of the object modified by this journal entry
dirtied() *common.Hash
}
// journal contains the list of state modifications applied since the last state commit
type journal struct {
entries []journalEntry
dirties map[common.Hash]int
}
// newJournal creates a new initialized journal
func newJournal() *journal {
return &journal{
dirties: make(map[common.Hash]int),
}
}
// append adds a new journal entry
func (j *journal) append(entry journalEntry) {
j.entries = append(j.entries, entry)
if hash := entry.dirtied(); hash != nil {
j.dirties[*hash]++
}
}
// revert undoes a batch of journal entries
func (j *journal) revert(statedb *TradingStateDB, snapshot int) {
for i := len(j.entries) - 1; i >= snapshot; i-- {
j.entries[i].revert(statedb)
if hash := j.entries[i].dirtied(); hash != nil {
if j.dirties[*hash]--; j.dirties[*hash] == 0 {
delete(j.dirties, *hash)
}
}
}
j.entries = j.entries[:snapshot]
}
// dirty returns the dirty count for a specific object
func (j *journal) dirty(hash common.Hash) int {
return j.dirties[hash]
}
// length returns the current number of entries in the journal
func (j *journal) length() int {
return len(j.entries)
}
// orderChange represents a change to an order
type orderChange struct {
orderID common.Hash
prev *OrderState
}
func (ch orderChange) revert(s *TradingStateDB) {
if ch.prev == nil {
delete(s.orders, ch.orderID)
} else {
s.orders[ch.orderID] = ch.prev
}
}
func (ch orderChange) dirtied() *common.Hash {
return &ch.orderID
}
// orderBookChange represents a change to an order book
type orderBookChange struct {
pairKey common.Hash
prev *OrderBook
}
func (ch orderBookChange) revert(s *TradingStateDB) {
if ch.prev == nil {
delete(s.orderBooks, ch.pairKey)
} else {
s.orderBooks[ch.pairKey] = ch.prev
}
}
func (ch orderBookChange) dirtied() *common.Hash {
return &ch.pairKey
}
// bidAddChange represents adding a bid
type bidAddChange struct {
pairKey common.Hash
orderID common.Hash
}
func (ch bidAddChange) revert(s *TradingStateDB) {
if ob, exists := s.orderBooks[ch.pairKey]; exists {
ob.RemoveBid(ch.orderID)
}
}
func (ch bidAddChange) dirtied() *common.Hash {
return &ch.pairKey
}
// askAddChange represents adding an ask
type askAddChange struct {
pairKey common.Hash
orderID common.Hash
}
func (ch askAddChange) revert(s *TradingStateDB) {
if ob, exists := s.orderBooks[ch.pairKey]; exists {
ob.RemoveAsk(ch.orderID)
}
}
func (ch askAddChange) dirtied() *common.Hash {
return &ch.pairKey
}
// bidRemoveChange represents removing a bid
type bidRemoveChange struct {
pairKey common.Hash
order *OrderState
}
func (ch bidRemoveChange) revert(s *TradingStateDB) {
if ob, exists := s.orderBooks[ch.pairKey]; exists {
ob.AddBid(ch.order)
}
}
func (ch bidRemoveChange) dirtied() *common.Hash {
return &ch.pairKey
}
// askRemoveChange represents removing an ask
type askRemoveChange struct {
pairKey common.Hash
order *OrderState
}
func (ch askRemoveChange) revert(s *TradingStateDB) {
if ob, exists := s.orderBooks[ch.pairKey]; exists {
ob.AddAsk(ch.order)
}
}
func (ch askRemoveChange) dirtied() *common.Hash {
return &ch.pairKey
}
// balanceChange represents a balance change
type balanceChange struct {
key common.Hash
amount *big.Int
}
func (ch balanceChange) revert(s *TradingStateDB) {
if obj, exists := s.stateObjects[ch.key]; exists {
obj.SubBalance(ch.amount)
}
}
func (ch balanceChange) dirtied() *common.Hash {
return &ch.key
}
// nonceChange represents a nonce change
type nonceChange struct {
key common.Hash
prev uint64
}
func (ch nonceChange) revert(s *TradingStateDB) {
if obj, exists := s.stateObjects[ch.key]; exists {
obj.SetNonce(ch.prev)
}
}
func (ch nonceChange) dirtied() *common.Hash {
return &ch.key
}

View file

@ -0,0 +1,242 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package tradingstate
import (
"math/big"
"sort"
"sync"
"github.com/ethereum/go-ethereum/common"
)
// Order interface for XDCx orders
type Order interface {
GetID() common.Hash
GetPrice() *big.Int
GetQuantity() *big.Int
GetFilledQuantity() *big.Int
GetSide() uint8
RemainingQuantity() *big.Int
IsFilled() bool
}
// OrderBook represents a trading pair order book
type OrderBook struct {
PairKey common.Hash
Bids []*OrderState // Buy orders sorted by price desc
Asks []*OrderState // Sell orders sorted by price asc
bidIndex map[common.Hash]int
askIndex map[common.Hash]int
lock sync.RWMutex
}
// NewOrderBook creates a new order book
func NewOrderBook(pairKey common.Hash) *OrderBook {
return &OrderBook{
PairKey: pairKey,
Bids: make([]*OrderState, 0),
Asks: make([]*OrderState, 0),
bidIndex: make(map[common.Hash]int),
askIndex: make(map[common.Hash]int),
}
}
// AddBid adds a buy order to the order book
func (ob *OrderBook) AddBid(order interface{}) {
ob.lock.Lock()
defer ob.lock.Unlock()
var orderState *OrderState
switch o := order.(type) {
case *OrderState:
orderState = o
default:
return
}
ob.Bids = append(ob.Bids, orderState)
ob.sortBids()
ob.rebuildBidIndex()
}
// AddAsk adds a sell order to the order book
func (ob *OrderBook) AddAsk(order interface{}) {
ob.lock.Lock()
defer ob.lock.Unlock()
var orderState *OrderState
switch o := order.(type) {
case *OrderState:
orderState = o
default:
return
}
ob.Asks = append(ob.Asks, orderState)
ob.sortAsks()
ob.rebuildAskIndex()
}
// RemoveBid removes a buy order from the order book
func (ob *OrderBook) RemoveBid(orderID common.Hash) {
ob.lock.Lock()
defer ob.lock.Unlock()
if idx, exists := ob.bidIndex[orderID]; exists {
ob.Bids = append(ob.Bids[:idx], ob.Bids[idx+1:]...)
ob.rebuildBidIndex()
}
}
// RemoveAsk removes a sell order from the order book
func (ob *OrderBook) RemoveAsk(orderID common.Hash) {
ob.lock.Lock()
defer ob.lock.Unlock()
if idx, exists := ob.askIndex[orderID]; exists {
ob.Asks = append(ob.Asks[:idx], ob.Asks[idx+1:]...)
ob.rebuildAskIndex()
}
}
// GetBestBid returns the best bid order
func (ob *OrderBook) GetBestBid() *OrderState {
ob.lock.RLock()
defer ob.lock.RUnlock()
if len(ob.Bids) == 0 {
return nil
}
return ob.Bids[0]
}
// GetBestAsk returns the best ask order
func (ob *OrderBook) GetBestAsk() *OrderState {
ob.lock.RLock()
defer ob.lock.RUnlock()
if len(ob.Asks) == 0 {
return nil
}
return ob.Asks[0]
}
// GetBid returns a specific bid order
func (ob *OrderBook) GetBid(orderID common.Hash) *OrderState {
ob.lock.RLock()
defer ob.lock.RUnlock()
if idx, exists := ob.bidIndex[orderID]; exists {
return ob.Bids[idx]
}
return nil
}
// GetAsk returns a specific ask order
func (ob *OrderBook) GetAsk(orderID common.Hash) *OrderState {
ob.lock.RLock()
defer ob.lock.RUnlock()
if idx, exists := ob.askIndex[orderID]; exists {
return ob.Asks[idx]
}
return nil
}
// BidCount returns the number of bids
func (ob *OrderBook) BidCount() int {
ob.lock.RLock()
defer ob.lock.RUnlock()
return len(ob.Bids)
}
// AskCount returns the number of asks
func (ob *OrderBook) AskCount() int {
ob.lock.RLock()
defer ob.lock.RUnlock()
return len(ob.Asks)
}
// GetBidVolume returns total bid volume
func (ob *OrderBook) GetBidVolume() *big.Int {
ob.lock.RLock()
defer ob.lock.RUnlock()
total := big.NewInt(0)
for _, bid := range ob.Bids {
total = new(big.Int).Add(total, bid.RemainingQuantity())
}
return total
}
// GetAskVolume returns total ask volume
func (ob *OrderBook) GetAskVolume() *big.Int {
ob.lock.RLock()
defer ob.lock.RUnlock()
total := big.NewInt(0)
for _, ask := range ob.Asks {
total = new(big.Int).Add(total, ask.RemainingQuantity())
}
return total
}
// Copy creates a copy of the order book
func (ob *OrderBook) Copy() *OrderBook {
ob.lock.RLock()
defer ob.lock.RUnlock()
copy := &OrderBook{
PairKey: ob.PairKey,
Bids: make([]*OrderState, len(ob.Bids)),
Asks: make([]*OrderState, len(ob.Asks)),
bidIndex: make(map[common.Hash]int),
askIndex: make(map[common.Hash]int),
}
for i, bid := range ob.Bids {
copy.Bids[i] = bid.Copy()
copy.bidIndex[bid.ID] = i
}
for i, ask := range ob.Asks {
copy.Asks[i] = ask.Copy()
copy.askIndex[ask.ID] = i
}
return copy
}
// sortBids sorts bids by price descending
func (ob *OrderBook) sortBids() {
sort.Slice(ob.Bids, func(i, j int) bool {
return ob.Bids[i].Price.Cmp(ob.Bids[j].Price) > 0
})
}
// sortAsks sorts asks by price ascending
func (ob *OrderBook) sortAsks() {
sort.Slice(ob.Asks, func(i, j int) bool {
return ob.Asks[i].Price.Cmp(ob.Asks[j].Price) < 0
})
}
// rebuildBidIndex rebuilds the bid index
func (ob *OrderBook) rebuildBidIndex() {
ob.bidIndex = make(map[common.Hash]int)
for i, bid := range ob.Bids {
ob.bidIndex[bid.ID] = i
}
}
// rebuildAskIndex rebuilds the ask index
func (ob *OrderBook) rebuildAskIndex() {
ob.askIndex = make(map[common.Hash]int)
for i, ask := range ob.Asks {
ob.askIndex[ask.ID] = i
}
}

View file

@ -0,0 +1,177 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package tradingstate
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
// stateObject represents a trading state object
type stateObject struct {
key common.Hash
data StateData
db *TradingStateDB
trie Trie
dirty bool
deleted bool
}
// StateData holds the trading state data
type StateData struct {
Nonce uint64
Balance *big.Int
Root common.Hash // merkle root of the state trie
CodeHash []byte
}
// newObject creates a new state object
func newObject(db *TradingStateDB, key common.Hash, data StateData) *stateObject {
return &stateObject{
key: key,
data: data,
db: db,
dirty: false,
}
}
// GetNonce returns the nonce
func (s *stateObject) GetNonce() uint64 {
return s.data.Nonce
}
// SetNonce sets the nonce
func (s *stateObject) SetNonce(nonce uint64) {
s.data.Nonce = nonce
s.dirty = true
}
// GetBalance returns the balance
func (s *stateObject) GetBalance() *big.Int {
return s.data.Balance
}
// SetBalance sets the balance
func (s *stateObject) SetBalance(balance *big.Int) {
s.data.Balance = new(big.Int).Set(balance)
s.dirty = true
}
// AddBalance adds amount to balance
func (s *stateObject) AddBalance(amount *big.Int) {
if amount.Sign() == 0 {
return
}
s.SetBalance(new(big.Int).Add(s.GetBalance(), amount))
}
// SubBalance subtracts amount from balance
func (s *stateObject) SubBalance(amount *big.Int) {
if amount.Sign() == 0 {
return
}
s.SetBalance(new(big.Int).Sub(s.GetBalance(), amount))
}
// deepCopy creates a deep copy of the state object
func (s *stateObject) deepCopy() *stateObject {
return &stateObject{
key: s.key,
data: s.data,
db: s.db,
trie: s.trie,
dirty: s.dirty,
deleted: s.deleted,
}
}
// EncodeRLP implements rlp.Encoder
func (s *stateObject) EncodeRLP() ([]byte, error) {
return rlp.EncodeToBytes(&s.data)
}
// OrderState represents an order in the state
type OrderState struct {
ID common.Hash
UserAddress common.Address
ExchangeAddress common.Address
BaseToken common.Address
QuoteToken common.Address
Side uint8
Type uint8
Price *big.Int
Quantity *big.Int
FilledQuantity *big.Int
Status uint8
Nonce uint64
Timestamp uint64
Signature []byte
}
// NewOrderState creates a new order state
func NewOrderState(
id common.Hash,
userAddress common.Address,
baseToken, quoteToken common.Address,
side, orderType uint8,
price, quantity *big.Int,
) *OrderState {
return &OrderState{
ID: id,
UserAddress: userAddress,
BaseToken: baseToken,
QuoteToken: quoteToken,
Side: side,
Type: orderType,
Price: new(big.Int).Set(price),
Quantity: new(big.Int).Set(quantity),
FilledQuantity: big.NewInt(0),
Status: 0, // New
}
}
// Hash computes the order hash
func (o *OrderState) Hash() common.Hash {
data := append(o.UserAddress.Bytes(), o.BaseToken.Bytes()...)
data = append(data, o.QuoteToken.Bytes()...)
data = append(data, byte(o.Side))
data = append(data, byte(o.Type))
data = append(data, common.BigToHash(o.Price).Bytes()...)
data = append(data, common.BigToHash(o.Quantity).Bytes()...)
data = append(data, common.BigToHash(big.NewInt(int64(o.Nonce))).Bytes()...)
return crypto.Keccak256Hash(data)
}
// Copy creates a copy of the order state
func (o *OrderState) Copy() *OrderState {
return &OrderState{
ID: o.ID,
UserAddress: o.UserAddress,
ExchangeAddress: o.ExchangeAddress,
BaseToken: o.BaseToken,
QuoteToken: o.QuoteToken,
Side: o.Side,
Type: o.Type,
Price: new(big.Int).Set(o.Price),
Quantity: new(big.Int).Set(o.Quantity),
FilledQuantity: new(big.Int).Set(o.FilledQuantity),
Status: o.Status,
Nonce: o.Nonce,
Timestamp: o.Timestamp,
Signature: append([]byte{}, o.Signature...),
}
}
// RemainingQuantity returns the remaining quantity
func (o *OrderState) RemainingQuantity() *big.Int {
return new(big.Int).Sub(o.Quantity, o.FilledQuantity)
}
// IsFilled returns whether the order is filled
func (o *OrderState) IsFilled() bool {
return o.FilledQuantity.Cmp(o.Quantity) >= 0
}

View file

@ -0,0 +1,320 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
// Package tradingstate provides trading state management for XDCx
package tradingstate
import (
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
)
// TradingStateDB represents the trading state database
type TradingStateDB struct {
db Database
trie Trie
// Cached objects
stateObjects map[common.Hash]*stateObject
stateObjectsDirty map[common.Hash]struct{}
// Order books cache
orderBooks map[common.Hash]*OrderBook
orderBooksDirty map[common.Hash]struct{}
// Orders cache
orders map[common.Hash]*OrderState
ordersDirty map[common.Hash]struct{}
lock sync.Mutex
}
// New creates a new trading state database
func New(root common.Hash, db Database) (*TradingStateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil {
return nil, err
}
return &TradingStateDB{
db: db,
trie: tr,
stateObjects: make(map[common.Hash]*stateObject),
stateObjectsDirty: make(map[common.Hash]struct{}),
orderBooks: make(map[common.Hash]*OrderBook),
orderBooksDirty: make(map[common.Hash]struct{}),
orders: make(map[common.Hash]*OrderState),
ordersDirty: make(map[common.Hash]struct{}),
}, nil
}
// GetOrder returns an order by ID
func (s *TradingStateDB) GetOrder(orderID common.Hash) *OrderState {
s.lock.Lock()
defer s.lock.Unlock()
if order, exists := s.orders[orderID]; exists {
return order
}
// Load from trie
order := s.loadOrder(orderID)
if order != nil {
s.orders[orderID] = order
}
return order
}
// SetOrder sets an order in the state
func (s *TradingStateDB) SetOrder(order interface{}) {
s.lock.Lock()
defer s.lock.Unlock()
// Convert order interface to OrderState
orderState := &OrderState{}
// Implementation depends on order type
s.orders[orderState.ID] = orderState
s.ordersDirty[orderState.ID] = struct{}{}
}
// UpdateOrder updates an order in the state
func (s *TradingStateDB) UpdateOrder(order interface{}) {
s.SetOrder(order)
}
// DeleteOrder deletes an order from the state
func (s *TradingStateDB) DeleteOrder(orderID common.Hash) {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.orders, orderID)
s.ordersDirty[orderID] = struct{}{}
}
// GetOrderBook returns an order book by pair key
func (s *TradingStateDB) GetOrderBook(pairKey common.Hash) *OrderBook {
s.lock.Lock()
defer s.lock.Unlock()
if ob, exists := s.orderBooks[pairKey]; exists {
return ob
}
// Load from trie
ob := s.loadOrderBook(pairKey)
if ob != nil {
s.orderBooks[pairKey] = ob
}
return ob
}
// GetOrCreateOrderBook returns or creates an order book
func (s *TradingStateDB) GetOrCreateOrderBook(pairKey common.Hash) *OrderBook {
ob := s.GetOrderBook(pairKey)
if ob == nil {
s.lock.Lock()
ob = NewOrderBook(pairKey)
s.orderBooks[pairKey] = ob
s.orderBooksDirty[pairKey] = struct{}{}
s.lock.Unlock()
}
return ob
}
// SetOrderBook sets an order book in the state
func (s *TradingStateDB) SetOrderBook(pairKey common.Hash, ob *OrderBook) {
s.lock.Lock()
defer s.lock.Unlock()
s.orderBooks[pairKey] = ob
s.orderBooksDirty[pairKey] = struct{}{}
}
// GetExchangeAddress returns the exchange address for a relayer
func (s *TradingStateDB) GetExchangeAddress(relayer common.Address) common.Address {
// Implementation
return relayer
}
// GetRelayerFee returns the fee for a relayer
func (s *TradingStateDB) GetRelayerFee(relayer common.Address) *big.Int {
// Implementation
return big.NewInt(0)
}
// Commit commits all changes to the underlying database
func (s *TradingStateDB) Commit() (common.Hash, error) {
s.lock.Lock()
defer s.lock.Unlock()
// Commit dirty objects
for key := range s.stateObjectsDirty {
if obj, exists := s.stateObjects[key]; exists {
if err := s.commitStateObject(obj); err != nil {
return common.Hash{}, err
}
}
delete(s.stateObjectsDirty, key)
}
// Commit dirty order books
for pairKey := range s.orderBooksDirty {
if ob, exists := s.orderBooks[pairKey]; exists {
if err := s.commitOrderBook(ob); err != nil {
return common.Hash{}, err
}
}
delete(s.orderBooksDirty, pairKey)
}
// Commit dirty orders
for orderID := range s.ordersDirty {
if order, exists := s.orders[orderID]; exists {
if err := s.commitOrder(order); err != nil {
return common.Hash{}, err
}
}
delete(s.ordersDirty, orderID)
}
// Commit trie
root, err := s.trie.Commit(nil)
if err != nil {
return common.Hash{}, err
}
log.Debug("Trading state committed", "root", root.Hex())
return root, nil
}
// Copy creates a copy of the trading state
func (s *TradingStateDB) Copy() *TradingStateDB {
s.lock.Lock()
defer s.lock.Unlock()
state := &TradingStateDB{
db: s.db,
trie: s.db.CopyTrie(s.trie),
stateObjects: make(map[common.Hash]*stateObject),
stateObjectsDirty: make(map[common.Hash]struct{}),
orderBooks: make(map[common.Hash]*OrderBook),
orderBooksDirty: make(map[common.Hash]struct{}),
orders: make(map[common.Hash]*OrderState),
ordersDirty: make(map[common.Hash]struct{}),
}
// Copy state objects
for key, obj := range s.stateObjects {
state.stateObjects[key] = obj.deepCopy()
}
for key := range s.stateObjectsDirty {
state.stateObjectsDirty[key] = struct{}{}
}
// Copy order books
for key, ob := range s.orderBooks {
state.orderBooks[key] = ob.Copy()
}
for key := range s.orderBooksDirty {
state.orderBooksDirty[key] = struct{}{}
}
// Copy orders
for key, order := range s.orders {
state.orders[key] = order.Copy()
}
for key := range s.ordersDirty {
state.ordersDirty[key] = struct{}{}
}
return state
}
// loadOrder loads an order from the trie
func (s *TradingStateDB) loadOrder(orderID common.Hash) *OrderState {
// Implementation
return nil
}
// loadOrderBook loads an order book from the trie
func (s *TradingStateDB) loadOrderBook(pairKey common.Hash) *OrderBook {
// Implementation
return nil
}
// commitStateObject commits a state object
func (s *TradingStateDB) commitStateObject(obj *stateObject) error {
// Implementation
return nil
}
// commitOrderBook commits an order book
func (s *TradingStateDB) commitOrderBook(ob *OrderBook) error {
// Implementation
return nil
}
// commitOrder commits an order
func (s *TradingStateDB) commitOrder(order *OrderState) error {
// Implementation
return nil
}
// Database wraps access to tries and contract code
type Database interface {
// OpenTrie opens the main trading state trie
OpenTrie(root common.Hash) (Trie, error)
// CopyTrie returns an independent copy of the given trie
CopyTrie(Trie) Trie
}
// Trie is a XDCx Merkle Patricia trie
type Trie interface {
// GetKey returns the sha3 preimage of a hashed key
GetKey([]byte) []byte
// TryGet returns the value for key stored in the trie
TryGet(key []byte) ([]byte, error)
// TryUpdate associates key with value in the trie
TryUpdate(key, value []byte) error
// TryDelete removes any existing value for key from the trie
TryDelete(key []byte) error
// Hash returns the root hash of the trie
Hash() common.Hash
// Commit writes all nodes to the trie's database
Commit(onleaf trie.LeafCallback) (common.Hash, error)
// NodeIterator returns an iterator that returns nodes of the trie
NodeIterator(startKey []byte) trie.NodeIterator
}
// NewDatabase creates a new trading state database
func NewDatabase(db ethdb.Database) Database {
return &cachingDB{
db: db,
}
}
type cachingDB struct {
db ethdb.Database
}
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
// Implementation
return nil, nil
}
func (db *cachingDB) CopyTrie(t Trie) Trie {
// Implementation
return nil
}

254
XDCxlending/XDCxlending.go Normal file
View file

@ -0,0 +1,254 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
// Package XDCxlending implements the XDC decentralized lending protocol
package XDCxlending
import (
"context"
"errors"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/XDCxlending/lendingstate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
var (
// ErrLendingServiceNotRunning is returned when lending service is not running
ErrLendingServiceNotRunning = errors.New("lending service is not running")
// ErrLoanNotFound is returned when loan is not found
ErrLoanNotFound = errors.New("loan not found")
// ErrInvalidInterestRate is returned when interest rate is invalid
ErrInvalidInterestRate = errors.New("invalid interest rate")
// ErrInvalidTerm is returned when term is invalid
ErrInvalidTerm = errors.New("invalid term")
// ErrInsufficientCollateral is returned when collateral is insufficient
ErrInsufficientCollateral = errors.New("insufficient collateral")
)
// XDCxLending represents the XDC decentralized lending protocol
type XDCxLending struct {
config *Config
db ethdb.Database
stateCache lendingstate.Database
lock sync.RWMutex
running bool
orderProcessor *OrderProcessor
liquidator *Liquidator
}
// Config holds XDCxLending configuration
type Config struct {
DataDir string
DBEngine string
LendingStateDB string
DefaultTerm uint64
MinCollateral *big.Int
LiquidationRate *big.Int
}
// DefaultConfig returns default XDCxLending configuration
func DefaultConfig() *Config {
return &Config{
DataDir: "",
DBEngine: "leveldb",
LendingStateDB: "XDCxlending",
DefaultTerm: 86400 * 30, // 30 days in seconds
MinCollateral: big.NewInt(150), // 150%
LiquidationRate: big.NewInt(110), // 110%
}
}
// New creates a new XDCxLending instance
func New(config *Config, db ethdb.Database) (*XDCxLending, error) {
if config == nil {
config = DefaultConfig()
}
lending := &XDCxLending{
config: config,
db: db,
running: false,
}
lending.stateCache = lendingstate.NewDatabase(db)
lending.orderProcessor = NewOrderProcessor(lending)
lending.liquidator = NewLiquidator(lending)
return lending, nil
}
// Start starts the XDCxLending service
func (l *XDCxLending) Start() error {
l.lock.Lock()
defer l.lock.Unlock()
if l.running {
return nil
}
log.Info("Starting XDCxLending service")
l.running = true
return nil
}
// Stop stops the XDCxLending service
func (l *XDCxLending) Stop() error {
l.lock.Lock()
defer l.lock.Unlock()
if !l.running {
return nil
}
log.Info("Stopping XDCxLending service")
l.running = false
return nil
}
// IsRunning returns whether XDCxLending is running
func (l *XDCxLending) IsRunning() bool {
l.lock.RLock()
defer l.lock.RUnlock()
return l.running
}
// GetLendingState returns the lending state for a given root
func (l *XDCxLending) GetLendingState(block *types.Block, statedb *state.StateDB) (*lendingstate.LendingStateDB, error) {
if block == nil {
return nil, errors.New("block is nil")
}
root := block.Root()
lendingState, err := lendingstate.New(root, l.stateCache)
if err != nil {
return nil, err
}
return lendingState, nil
}
// ProcessLendingOrder processes a lending order
func (l *XDCxLending) ProcessLendingOrder(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, order *LendingOrder) ([]*LendingTrade, error) {
if !l.IsRunning() {
return nil, ErrLendingServiceNotRunning
}
return l.orderProcessor.Process(ctx, statedb, lendingState, order)
}
// CancelLendingOrder cancels an existing lending order
func (l *XDCxLending) CancelLendingOrder(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, orderID common.Hash) error {
if !l.IsRunning() {
return ErrLendingServiceNotRunning
}
return l.orderProcessor.Cancel(ctx, statedb, lendingState, orderID)
}
// Topup adds collateral to a loan
func (l *XDCxLending) Topup(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, loanID common.Hash, amount *big.Int) error {
if !l.IsRunning() {
return ErrLendingServiceNotRunning
}
loan := lendingState.GetLoan(loanID)
if loan == nil {
return ErrLoanNotFound
}
// Add collateral
loan.CollateralAmount = new(big.Int).Add(loan.CollateralAmount, amount)
lendingState.UpdateLoan(loan)
log.Debug("Loan topped up", "loanID", loanID.Hex(), "amount", amount)
return nil
}
// Repay repays a loan
func (l *XDCxLending) Repay(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, loanID common.Hash) error {
if !l.IsRunning() {
return ErrLendingServiceNotRunning
}
loan := lendingState.GetLoan(loanID)
if loan == nil {
return ErrLoanNotFound
}
// Calculate total repayment amount
interest := l.calculateInterest(loan)
totalRepayment := new(big.Int).Add(loan.Principal, interest)
// Update loan status
loan.Status = LoanStatusRepaid
lendingState.UpdateLoan(loan)
log.Debug("Loan repaid", "loanID", loanID.Hex(), "total", totalRepayment)
return nil
}
// Liquidate liquidates an undercollateralized loan
func (l *XDCxLending) Liquidate(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, loanID common.Hash) error {
if !l.IsRunning() {
return ErrLendingServiceNotRunning
}
return l.liquidator.Liquidate(ctx, statedb, lendingState, loanID)
}
// CheckLiquidation checks if loans need liquidation
func (l *XDCxLending) CheckLiquidation(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB) ([]common.Hash, error) {
if !l.IsRunning() {
return nil, ErrLendingServiceNotRunning
}
return l.liquidator.CheckLoansForLiquidation(lendingState)
}
// ApplyXDCxLendingMatchedTransaction applies matched lending trades to state
func (l *XDCxLending) ApplyXDCxLendingMatchedTransaction(chainConfig *params.ChainConfig, statedb *state.StateDB, block *types.Block, trades []*LendingTrade) error {
for _, trade := range trades {
if err := l.settleLendingTrade(statedb, trade); err != nil {
return err
}
}
return nil
}
// settleLendingTrade settles a lending trade
func (l *XDCxLending) settleLendingTrade(statedb *state.StateDB, trade *LendingTrade) error {
log.Debug("Settling lending trade", "trade", trade.Hash())
return nil
}
// calculateInterest calculates the interest for a loan
func (l *XDCxLending) calculateInterest(loan *Loan) *big.Int {
// Simple interest calculation: Principal * Rate * Time / (365 * 100)
interest := new(big.Int).Mul(loan.Principal, loan.InterestRate)
interest = interest.Mul(interest, big.NewInt(int64(loan.Term)))
interest = interest.Div(interest, big.NewInt(365*24*3600*100))
return interest
}
// GetConfig returns XDCxLending configuration
func (l *XDCxLending) GetConfig() *Config {
return l.config
}
// Database returns the database instance
func (l *XDCxLending) Database() ethdb.Database {
return l.db
}
// StateCache returns the lending state cache
func (l *XDCxLending) StateCache() lendingstate.Database {
return l.stateCache
}

View file

@ -0,0 +1,338 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCxlending
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
)
func TestNewXDCxLending(t *testing.T) {
db := memorydb.New()
config := DefaultConfig()
lending, err := New(config, db)
if err != nil {
t.Fatalf("Failed to create XDCxLending: %v", err)
}
if lending == nil {
t.Fatal("XDCxLending instance is nil")
}
if lending.config != config {
t.Error("Config not set correctly")
}
}
func TestXDCxLendingStartStop(t *testing.T) {
db := memorydb.New()
lending, _ := New(DefaultConfig(), db)
// Test start
if err := lending.Start(); err != nil {
t.Fatalf("Failed to start XDCxLending: %v", err)
}
if !lending.IsRunning() {
t.Error("XDCxLending should be running after Start")
}
// Test stop
if err := lending.Stop(); err != nil {
t.Fatalf("Failed to stop XDCxLending: %v", err)
}
if lending.IsRunning() {
t.Error("XDCxLending should not be running after Stop")
}
}
func TestDefaultLendingConfig(t *testing.T) {
config := DefaultConfig()
if config.DBEngine != "leveldb" {
t.Errorf("Expected DBEngine 'leveldb', got '%s'", config.DBEngine)
}
if config.DefaultTerm != 86400*30 {
t.Errorf("Expected DefaultTerm 30 days, got %d", config.DefaultTerm)
}
if config.MinCollateral.Cmp(big.NewInt(150)) != 0 {
t.Errorf("Expected MinCollateral 150, got %s", config.MinCollateral.String())
}
if config.LiquidationRate.Cmp(big.NewInt(110)) != 0 {
t.Errorf("Expected LiquidationRate 110, got %s", config.LiquidationRate.String())
}
}
func TestNewLendingOrder(t *testing.T) {
userAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
lendingToken := common.HexToAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
collateralToken := common.HexToAddress("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
relayerAddr := common.HexToAddress("0xcccccccccccccccccccccccccccccccccccccccc")
interestRate := big.NewInt(500) // 5%
term := uint64(86400 * 30) // 30 days
quantity := big.NewInt(1000)
nonce := uint64(1)
order := NewLendingOrder(
userAddr, lendingToken, collateralToken,
Borrow, LimitOrder,
interestRate, term, quantity, nonce, relayerAddr,
)
if order.UserAddress != userAddr {
t.Errorf("Expected user address %s, got %s", userAddr.Hex(), order.UserAddress.Hex())
}
if order.LendingToken != lendingToken {
t.Errorf("Expected lending token %s, got %s", lendingToken.Hex(), order.LendingToken.Hex())
}
if order.Side != Borrow {
t.Errorf("Expected side Borrow, got %d", order.Side)
}
if order.Status != OrderStatusNew {
t.Errorf("Expected status New, got %d", order.Status)
}
if order.Term != term {
t.Errorf("Expected term %d, got %d", term, order.Term)
}
}
func TestLendingOrderRemainingQuantity(t *testing.T) {
order := &LendingOrder{
Quantity: big.NewInt(100),
FilledQuantity: big.NewInt(30),
}
remaining := order.RemainingQuantity()
expected := big.NewInt(70)
if remaining.Cmp(expected) != 0 {
t.Errorf("Expected remaining %s, got %s", expected.String(), remaining.String())
}
}
func TestLendingOrderIsFilled(t *testing.T) {
order := &LendingOrder{
Quantity: big.NewInt(100),
FilledQuantity: big.NewInt(30),
}
if order.IsFilled() {
t.Error("Order should not be filled")
}
order.FilledQuantity = big.NewInt(100)
if !order.IsFilled() {
t.Error("Order should be filled")
}
}
func TestNewLoan(t *testing.T) {
borrower := common.HexToAddress("0x1111")
lender := common.HexToAddress("0x2222")
lendingToken := common.HexToAddress("0x3333")
collateralToken := common.HexToAddress("0x4444")
principal := big.NewInt(1000)
collateral := big.NewInt(1500)
interestRate := big.NewInt(500)
term := uint64(86400 * 30)
startTime := uint64(1000000)
loan := NewLoan(
borrower, lender,
lendingToken, collateralToken,
principal, collateral,
interestRate, term, startTime,
)
if loan.BorrowerAddress != borrower {
t.Error("Borrower address mismatch")
}
if loan.LenderAddress != lender {
t.Error("Lender address mismatch")
}
if loan.ExpiryTime != startTime+term {
t.Errorf("Expected expiry %d, got %d", startTime+term, loan.ExpiryTime)
}
if loan.Status != LoanStatusActive {
t.Errorf("Expected status Active, got %d", loan.Status)
}
}
func TestLoanIsExpired(t *testing.T) {
loan := &Loan{
ExpiryTime: 1000000,
}
// Not expired
if loan.IsExpired(500000) {
t.Error("Loan should not be expired")
}
// Exactly expired
if !loan.IsExpired(1000000) {
t.Error("Loan should be expired at expiry time")
}
// Past expiry
if !loan.IsExpired(1500000) {
t.Error("Loan should be expired past expiry time")
}
}
func TestLoanGetCollateralRatio(t *testing.T) {
loan := &Loan{
Principal: big.NewInt(1000),
CollateralAmount: big.NewInt(1500),
}
// Collateral price = 1
collateralPrice := big.NewInt(1)
ratio := loan.GetCollateralRatio(collateralPrice)
// Expected: (1500 * 1) / 1000 * 100 = 150
expected := big.NewInt(150)
if ratio.Cmp(expected) != 0 {
t.Errorf("Expected ratio %s, got %s", expected.String(), ratio.String())
}
}
func TestGetLendingPairKey(t *testing.T) {
lendingToken := common.HexToAddress("0xaaaa")
collateralToken := common.HexToAddress("0xbbbb")
term := uint64(86400 * 30)
key1 := GetLendingPairKey(lendingToken, collateralToken, term)
key2 := GetLendingPairKey(lendingToken, collateralToken, term)
if key1 != key2 {
t.Error("Same parameters should produce same key")
}
// Different term should produce different key
key3 := GetLendingPairKey(lendingToken, collateralToken, term*2)
if key1 == key3 {
t.Error("Different term should produce different key")
}
}
func TestNewLendingTrade(t *testing.T) {
borrowOrderID := common.HexToHash("0x1111")
lendOrderID := common.HexToHash("0x2222")
borrower := common.HexToAddress("0x3333")
lender := common.HexToAddress("0x4444")
lendingToken := common.HexToAddress("0x5555")
collateralToken := common.HexToAddress("0x6666")
principal := big.NewInt(1000)
collateral := big.NewInt(1500)
interestRate := big.NewInt(500)
term := uint64(86400 * 30)
trade := NewLendingTrade(
borrowOrderID, lendOrderID,
borrower, lender,
lendingToken, collateralToken,
principal, collateral,
interestRate, term,
)
if trade.BorrowOrderID != borrowOrderID {
t.Error("Borrow order ID mismatch")
}
if trade.Borrower != borrower {
t.Error("Borrower address mismatch")
}
if trade.Status != TradeStatusPending {
t.Errorf("Expected status Pending, got %d", trade.Status)
}
}
func TestLendingTradeCalculateInterest(t *testing.T) {
trade := &LendingTrade{
Principal: big.NewInt(10000),
InterestRate: big.NewInt(500), // 5%
Term: 86400 * 30, // 30 days
}
interest := trade.CalculateInterest()
// Interest = 10000 * 5 * 30 / (365 * 100) ≈ 41
// This is approximate due to integer division
if interest.Sign() <= 0 {
t.Error("Interest should be positive")
}
}
func TestLendingTradeTotalRepayment(t *testing.T) {
trade := &LendingTrade{
Principal: big.NewInt(10000),
InterestRate: big.NewInt(500),
Term: 86400 * 30,
}
total := trade.TotalRepayment()
interest := trade.CalculateInterest()
expected := new(big.Int).Add(trade.Principal, interest)
if total.Cmp(expected) != 0 {
t.Errorf("Expected total %s, got %s", expected.String(), total.String())
}
}
func TestXDCxLendingAPIs(t *testing.T) {
db := memorydb.New()
lending, _ := New(DefaultConfig(), db)
apis := lending.APIs()
if len(apis) != 2 {
t.Errorf("Expected 2 APIs, got %d", len(apis))
}
// Check namespaces
namespaces := make(map[string]bool)
for _, api := range apis {
namespaces[api.Namespace] = true
}
if !namespaces["xdcxlending"] {
t.Error("Expected 'xdcxlending' namespace")
}
}
func TestCalculateInterest(t *testing.T) {
db := memorydb.New()
lending, _ := New(DefaultConfig(), db)
loan := &Loan{
Principal: big.NewInt(10000),
InterestRate: big.NewInt(1000), // 10%
Term: 86400 * 365, // 1 year
}
interest := lending.calculateInterest(loan)
// With 10% annual rate for 1 year, interest should be around 1000
// Allow some tolerance due to integer division
if interest.Cmp(big.NewInt(900)) < 0 || interest.Cmp(big.NewInt(1100)) > 0 {
t.Errorf("Expected interest around 1000, got %s", interest.String())
}
}

297
XDCxlending/api.go Normal file
View file

@ -0,0 +1,297 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCxlending
import (
"context"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/XDCxlending/lendingstate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
)
// PublicXDCxLendingAPI provides public XDCxLending APIs
type PublicXDCxLendingAPI struct {
lending *XDCxLending
}
// NewPublicXDCxLendingAPI creates a new public XDCxLending API
func NewPublicXDCxLendingAPI(lending *XDCxLending) *PublicXDCxLendingAPI {
return &PublicXDCxLendingAPI{lending: lending}
}
// Version returns the XDCxLending version
func (api *PublicXDCxLendingAPI) Version() string {
return "1.0"
}
// LendingOrderBookResult represents a lending order book API result
type LendingOrderBookResult struct {
LendingToken common.Address `json:"lendingToken"`
CollateralToken common.Address `json:"collateralToken"`
Term uint64 `json:"term"`
Borrows []LendingOrderResult `json:"borrows"`
Lends []LendingOrderResult `json:"lends"`
}
// LendingOrderResult represents a lending order API result
type LendingOrderResult struct {
ID common.Hash `json:"id"`
UserAddress common.Address `json:"userAddress"`
RelayerAddress common.Address `json:"relayerAddress"`
InterestRate *hexutil.Big `json:"interestRate"`
Quantity *hexutil.Big `json:"quantity"`
FilledQuantity *hexutil.Big `json:"filledQuantity"`
Status string `json:"status"`
Side string `json:"side"`
}
// LoanResult represents a loan API result
type LoanResult struct {
ID common.Hash `json:"id"`
Borrower common.Address `json:"borrower"`
Lender common.Address `json:"lender"`
LendingToken common.Address `json:"lendingToken"`
CollateralToken common.Address `json:"collateralToken"`
Principal *hexutil.Big `json:"principal"`
CollateralAmount *hexutil.Big `json:"collateralAmount"`
InterestRate *hexutil.Big `json:"interestRate"`
Term uint64 `json:"term"`
StartTime uint64 `json:"startTime"`
ExpiryTime uint64 `json:"expiryTime"`
Status string `json:"status"`
}
// GetLendingOrderBook returns the lending order book
func (api *PublicXDCxLendingAPI) GetLendingOrderBook(ctx context.Context, lendingToken, collateralToken common.Address, term uint64) (*LendingOrderBookResult, error) {
if !api.lending.IsRunning() {
return nil, ErrLendingServiceNotRunning
}
// Get current lending state
lendingState, err := lendingstate.New(common.Hash{}, api.lending.StateCache())
if err != nil {
return nil, err
}
pairKey := GetLendingPairKey(lendingToken, collateralToken, term)
ob := lendingState.GetLendingOrderBook(pairKey)
if ob == nil {
return nil, errors.New("order book not found")
}
result := &LendingOrderBookResult{
LendingToken: lendingToken,
CollateralToken: collateralToken,
Term: term,
Borrows: make([]LendingOrderResult, 0),
Lends: make([]LendingOrderResult, 0),
}
for _, borrow := range ob.Borrows {
result.Borrows = append(result.Borrows, orderStateToResult(borrow))
}
for _, lend := range ob.Lends {
result.Lends = append(result.Lends, orderStateToResult(lend))
}
return result, nil
}
// GetLoan returns a loan by ID
func (api *PublicXDCxLendingAPI) GetLoan(ctx context.Context, loanID common.Hash) (*LoanResult, error) {
if !api.lending.IsRunning() {
return nil, ErrLendingServiceNotRunning
}
lendingState, err := lendingstate.New(common.Hash{}, api.lending.StateCache())
if err != nil {
return nil, err
}
loan := lendingState.GetLoan(loanID)
if loan == nil {
return nil, ErrLoanNotFound
}
return loanStateToResult(loan), nil
}
// GetLoansByBorrower returns all loans for a borrower
func (api *PublicXDCxLendingAPI) GetLoansByBorrower(ctx context.Context, borrower common.Address) ([]*LoanResult, error) {
if !api.lending.IsRunning() {
return nil, ErrLendingServiceNotRunning
}
lendingState, err := lendingstate.New(common.Hash{}, api.lending.StateCache())
if err != nil {
return nil, err
}
allLoans := lendingState.GetAllLoans()
results := make([]*LoanResult, 0)
for _, loan := range allLoans {
if loan.BorrowerAddress == borrower {
results = append(results, loanStateToResult(loan))
}
}
return results, nil
}
// orderStateToResult converts a lending order state to API result
func orderStateToResult(order *lendingstate.LendingOrderState) LendingOrderResult {
sideStr := "borrow"
if order.Side == 1 {
sideStr = "lend"
}
statusStr := "new"
switch order.Status {
case 1:
statusStr = "partial"
case 2:
statusStr = "filled"
case 3:
statusStr = "cancelled"
case 4:
statusStr = "rejected"
}
return LendingOrderResult{
ID: order.ID,
UserAddress: order.UserAddress,
RelayerAddress: order.RelayerAddress,
InterestRate: (*hexutil.Big)(order.InterestRate),
Quantity: (*hexutil.Big)(order.Quantity),
FilledQuantity: (*hexutil.Big)(order.FilledQuantity),
Status: statusStr,
Side: sideStr,
}
}
// loanStateToResult converts a loan state to API result
func loanStateToResult(loan *lendingstate.LoanState) *LoanResult {
statusStr := "active"
switch loan.Status {
case 1:
statusStr = "repaid"
case 2:
statusStr = "liquidated"
case 3:
statusStr = "defaulted"
}
return &LoanResult{
ID: loan.ID,
Borrower: loan.BorrowerAddress,
Lender: loan.LenderAddress,
LendingToken: loan.LendingToken,
CollateralToken: loan.CollateralToken,
Principal: (*hexutil.Big)(loan.Principal),
CollateralAmount: (*hexutil.Big)(loan.CollateralAmount),
InterestRate: (*hexutil.Big)(loan.InterestRate),
Term: loan.Term,
StartTime: loan.StartTime,
ExpiryTime: loan.ExpiryTime,
Status: statusStr,
}
}
// PrivateXDCxLendingAPI provides private XDCxLending APIs
type PrivateXDCxLendingAPI struct {
lending *XDCxLending
}
// NewPrivateXDCxLendingAPI creates a new private XDCxLending API
func NewPrivateXDCxLendingAPI(lending *XDCxLending) *PrivateXDCxLendingAPI {
return &PrivateXDCxLendingAPI{lending: lending}
}
// SendLendingOrder sends a new lending order
func (api *PrivateXDCxLendingAPI) SendLendingOrder(ctx context.Context, args SendLendingOrderArgs) (common.Hash, error) {
if !api.lending.IsRunning() {
return common.Hash{}, ErrLendingServiceNotRunning
}
// Convert args to order
order, err := args.ToOrder()
if err != nil {
return common.Hash{}, err
}
return order.ID, nil
}
// SendLendingOrderArgs represents the arguments for sending a lending order
type SendLendingOrderArgs struct {
LendingToken common.Address `json:"lendingToken"`
CollateralToken common.Address `json:"collateralToken"`
Side string `json:"side"`
Type string `json:"type"`
InterestRate *hexutil.Big `json:"interestRate"`
Term hexutil.Uint64 `json:"term"`
Quantity *hexutil.Big `json:"quantity"`
RelayerAddress common.Address `json:"relayerAddress"`
Nonce hexutil.Uint64 `json:"nonce"`
Signature hexutil.Bytes `json:"signature"`
}
// ToOrder converts args to a lending order
func (args *SendLendingOrderArgs) ToOrder() (*LendingOrder, error) {
var side OrderSide
switch args.Side {
case "borrow":
side = Borrow
case "lend":
side = Lend
default:
return nil, errors.New("invalid order side")
}
var orderType OrderType
switch args.Type {
case "limit":
orderType = LimitOrder
case "market":
orderType = MarketOrder
default:
return nil, errors.New("invalid order type")
}
order := NewLendingOrder(
common.Address{},
args.LendingToken,
args.CollateralToken,
side,
orderType,
(*big.Int)(args.InterestRate),
uint64(args.Term),
(*big.Int)(args.Quantity),
uint64(args.Nonce),
args.RelayerAddress,
)
order.Signature = args.Signature
return order, nil
}
// APIs returns the collection of XDCxLending APIs
func (l *XDCxLending) APIs() []rpc.API {
return []rpc.API{
{
Namespace: "xdcxlending",
Service: NewPublicXDCxLendingAPI(l),
},
{
Namespace: "xdcxlending",
Service: NewPrivateXDCxLendingAPI(l),
},
}
}

View file

@ -0,0 +1,127 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package lendingstate
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
)
// LendingTrieDB wraps the trie database for lending state
type LendingTrieDB struct {
diskdb ethdb.Database
config *trie.Config
}
// NewLendingTrieDB creates a new lending trie database
func NewLendingTrieDB(diskdb ethdb.Database) *LendingTrieDB {
return &LendingTrieDB{
diskdb: diskdb,
config: &trie.Config{},
}
}
// DiskDB returns the underlying disk database
func (db *LendingTrieDB) DiskDB() ethdb.Database {
return db.diskdb
}
// OpenLendingTrie opens a lending state trie
func (db *LendingTrieDB) OpenLendingTrie(root common.Hash) (*trie.Trie, error) {
id := &trie.ID{
StateRoot: root,
}
return trie.New(id, trie.NewDatabase(db.diskdb, db.config))
}
// CopyLendingTrie copies a lending trie
func (db *LendingTrieDB) CopyLendingTrie(t *trie.Trie) *trie.Trie {
return t.Copy()
}
// Commit commits all pending writes
func (db *LendingTrieDB) Commit(root common.Hash, report bool) error {
return nil
}
// Close closes the database
func (db *LendingTrieDB) Close() error {
return db.diskdb.Close()
}
// LendingStateCacheConfig holds cache configuration
type LendingStateCacheConfig struct {
CacheSize int
MaxLive int
}
// DefaultLendingStateCacheConfig returns default cache configuration
func DefaultLendingStateCacheConfig() *LendingStateCacheConfig {
return &LendingStateCacheConfig{
CacheSize: 256,
MaxLive: 16,
}
}
// LendingStateCache provides caching for lending state
type LendingStateCache struct {
config *LendingStateCacheConfig
db *LendingTrieDB
states map[common.Hash]*LendingStateDB
}
// NewLendingStateCache creates a new lending state cache
func NewLendingStateCache(db *LendingTrieDB, config *LendingStateCacheConfig) *LendingStateCache {
if config == nil {
config = DefaultLendingStateCacheConfig()
}
return &LendingStateCache{
config: config,
db: db,
states: make(map[common.Hash]*LendingStateDB),
}
}
// Get retrieves a lending state from cache or creates new one
func (c *LendingStateCache) Get(root common.Hash) (*LendingStateDB, error) {
if state, ok := c.states[root]; ok {
return state.Copy(), nil
}
return New(root, c)
}
// Put stores a lending state in cache
func (c *LendingStateCache) Put(root common.Hash, state *LendingStateDB) {
if len(c.states) >= c.config.CacheSize {
for k := range c.states {
delete(c.states, k)
break
}
}
c.states[root] = state
}
// Remove removes a state from cache
func (c *LendingStateCache) Remove(root common.Hash) {
delete(c.states, root)
}
// Clear clears the entire cache
func (c *LendingStateCache) Clear() {
c.states = make(map[common.Hash]*LendingStateDB)
}
// OpenTrie implements Database interface
func (c *LendingStateCache) OpenTrie(root common.Hash) (Trie, error) {
return c.db.OpenLendingTrie(root)
}
// CopyTrie implements Database interface
func (c *LendingStateCache) CopyTrie(t Trie) Trie {
if tt, ok := t.(*trie.Trie); ok {
return c.db.CopyLendingTrie(tt)
}
return nil
}

View file

@ -0,0 +1,256 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package lendingstate
import (
"math/big"
"sort"
"sync"
"github.com/ethereum/go-ethereum/common"
)
// LendingOrderBook represents a lending pair order book
type LendingOrderBook struct {
PairKey common.Hash
Borrows []*LendingOrderState // Borrow orders sorted by interest rate desc
Lends []*LendingOrderState // Lend orders sorted by interest rate asc
borrowIndex map[common.Hash]int
lendIndex map[common.Hash]int
lock sync.RWMutex
}
// NewLendingOrderBook creates a new lending order book
func NewLendingOrderBook(pairKey common.Hash) *LendingOrderBook {
return &LendingOrderBook{
PairKey: pairKey,
Borrows: make([]*LendingOrderState, 0),
Lends: make([]*LendingOrderState, 0),
borrowIndex: make(map[common.Hash]int),
lendIndex: make(map[common.Hash]int),
}
}
// AddBorrow adds a borrow order to the order book
func (ob *LendingOrderBook) AddBorrow(order interface{}) {
ob.lock.Lock()
defer ob.lock.Unlock()
var orderState *LendingOrderState
switch o := order.(type) {
case *LendingOrderState:
orderState = o
default:
return
}
ob.Borrows = append(ob.Borrows, orderState)
ob.sortBorrows()
ob.rebuildBorrowIndex()
}
// AddLend adds a lend order to the order book
func (ob *LendingOrderBook) AddLend(order interface{}) {
ob.lock.Lock()
defer ob.lock.Unlock()
var orderState *LendingOrderState
switch o := order.(type) {
case *LendingOrderState:
orderState = o
default:
return
}
ob.Lends = append(ob.Lends, orderState)
ob.sortLends()
ob.rebuildLendIndex()
}
// RemoveBorrow removes a borrow order from the order book
func (ob *LendingOrderBook) RemoveBorrow(orderID common.Hash) {
ob.lock.Lock()
defer ob.lock.Unlock()
if idx, exists := ob.borrowIndex[orderID]; exists {
ob.Borrows = append(ob.Borrows[:idx], ob.Borrows[idx+1:]...)
ob.rebuildBorrowIndex()
}
}
// RemoveLend removes a lend order from the order book
func (ob *LendingOrderBook) RemoveLend(orderID common.Hash) {
ob.lock.Lock()
defer ob.lock.Unlock()
if idx, exists := ob.lendIndex[orderID]; exists {
ob.Lends = append(ob.Lends[:idx], ob.Lends[idx+1:]...)
ob.rebuildLendIndex()
}
}
// GetBestBorrow returns the best borrow order (highest interest rate)
func (ob *LendingOrderBook) GetBestBorrow() *LendingOrderState {
ob.lock.RLock()
defer ob.lock.RUnlock()
if len(ob.Borrows) == 0 {
return nil
}
return ob.Borrows[0]
}
// GetBestLend returns the best lend order (lowest interest rate)
func (ob *LendingOrderBook) GetBestLend() *LendingOrderState {
ob.lock.RLock()
defer ob.lock.RUnlock()
if len(ob.Lends) == 0 {
return nil
}
return ob.Lends[0]
}
// GetBorrow returns a specific borrow order
func (ob *LendingOrderBook) GetBorrow(orderID common.Hash) *LendingOrderState {
ob.lock.RLock()
defer ob.lock.RUnlock()
if idx, exists := ob.borrowIndex[orderID]; exists {
return ob.Borrows[idx]
}
return nil
}
// GetLend returns a specific lend order
func (ob *LendingOrderBook) GetLend(orderID common.Hash) *LendingOrderState {
ob.lock.RLock()
defer ob.lock.RUnlock()
if idx, exists := ob.lendIndex[orderID]; exists {
return ob.Lends[idx]
}
return nil
}
// BorrowCount returns the number of borrow orders
func (ob *LendingOrderBook) BorrowCount() int {
ob.lock.RLock()
defer ob.lock.RUnlock()
return len(ob.Borrows)
}
// LendCount returns the number of lend orders
func (ob *LendingOrderBook) LendCount() int {
ob.lock.RLock()
defer ob.lock.RUnlock()
return len(ob.Lends)
}
// GetBorrowVolume returns total borrow volume
func (ob *LendingOrderBook) GetBorrowVolume() *big.Int {
ob.lock.RLock()
defer ob.lock.RUnlock()
total := big.NewInt(0)
for _, borrow := range ob.Borrows {
total = new(big.Int).Add(total, borrow.RemainingQuantity())
}
return total
}
// GetLendVolume returns total lend volume
func (ob *LendingOrderBook) GetLendVolume() *big.Int {
ob.lock.RLock()
defer ob.lock.RUnlock()
total := big.NewInt(0)
for _, lend := range ob.Lends {
total = new(big.Int).Add(total, lend.RemainingQuantity())
}
return total
}
// Copy creates a copy of the order book
func (ob *LendingOrderBook) Copy() *LendingOrderBook {
ob.lock.RLock()
defer ob.lock.RUnlock()
copy := &LendingOrderBook{
PairKey: ob.PairKey,
Borrows: make([]*LendingOrderState, len(ob.Borrows)),
Lends: make([]*LendingOrderState, len(ob.Lends)),
borrowIndex: make(map[common.Hash]int),
lendIndex: make(map[common.Hash]int),
}
for i, borrow := range ob.Borrows {
copy.Borrows[i] = borrow.Copy()
copy.borrowIndex[borrow.ID] = i
}
for i, lend := range ob.Lends {
copy.Lends[i] = lend.Copy()
copy.lendIndex[lend.ID] = i
}
return copy
}
// sortBorrows sorts borrows by interest rate descending
func (ob *LendingOrderBook) sortBorrows() {
sort.Slice(ob.Borrows, func(i, j int) bool {
return ob.Borrows[i].InterestRate.Cmp(ob.Borrows[j].InterestRate) > 0
})
}
// sortLends sorts lends by interest rate ascending
func (ob *LendingOrderBook) sortLends() {
sort.Slice(ob.Lends, func(i, j int) bool {
return ob.Lends[i].InterestRate.Cmp(ob.Lends[j].InterestRate) < 0
})
}
// rebuildBorrowIndex rebuilds the borrow index
func (ob *LendingOrderBook) rebuildBorrowIndex() {
ob.borrowIndex = make(map[common.Hash]int)
for i, borrow := range ob.Borrows {
ob.borrowIndex[borrow.ID] = i
}
}
// rebuildLendIndex rebuilds the lend index
func (ob *LendingOrderBook) rebuildLendIndex() {
ob.lendIndex = make(map[common.Hash]int)
for i, lend := range ob.Lends {
ob.lendIndex[lend.ID] = i
}
}
// GetMarketRate returns the market interest rate
func (ob *LendingOrderBook) GetMarketRate() *big.Int {
ob.lock.RLock()
defer ob.lock.RUnlock()
bestBorrow := ob.GetBestBorrow()
bestLend := ob.GetBestLend()
if bestBorrow == nil && bestLend == nil {
return nil
}
if bestBorrow == nil {
return new(big.Int).Set(bestLend.InterestRate)
}
if bestLend == nil {
return new(big.Int).Set(bestBorrow.InterestRate)
}
// Return midpoint
sum := new(big.Int).Add(bestBorrow.InterestRate, bestLend.InterestRate)
return sum.Div(sum, big.NewInt(2))
}

View file

@ -0,0 +1,416 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
// Package lendingstate provides lending state management for XDCxLending
package lendingstate
import (
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
)
// LendingStateDB represents the lending state database
type LendingStateDB struct {
db Database
trie Trie
// Order books cache
orderBooks map[common.Hash]*LendingOrderBook
orderBooksDirty map[common.Hash]struct{}
// Orders cache
orders map[common.Hash]*LendingOrderState
ordersDirty map[common.Hash]struct{}
// Loans cache
loans map[common.Hash]*LoanState
loansDirty map[common.Hash]struct{}
lock sync.Mutex
}
// New creates a new lending state database
func New(root common.Hash, db Database) (*LendingStateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil {
return nil, err
}
return &LendingStateDB{
db: db,
trie: tr,
orderBooks: make(map[common.Hash]*LendingOrderBook),
orderBooksDirty: make(map[common.Hash]struct{}),
orders: make(map[common.Hash]*LendingOrderState),
ordersDirty: make(map[common.Hash]struct{}),
loans: make(map[common.Hash]*LoanState),
loansDirty: make(map[common.Hash]struct{}),
}, nil
}
// GetLendingOrder returns a lending order by ID
func (s *LendingStateDB) GetLendingOrder(orderID common.Hash) *LendingOrderState {
s.lock.Lock()
defer s.lock.Unlock()
if order, exists := s.orders[orderID]; exists {
return order
}
// Load from trie
order := s.loadLendingOrder(orderID)
if order != nil {
s.orders[orderID] = order
}
return order
}
// SetLendingOrder sets a lending order in the state
func (s *LendingStateDB) SetLendingOrder(order interface{}) {
s.lock.Lock()
defer s.lock.Unlock()
// Convert order interface to LendingOrderState
orderState := &LendingOrderState{}
// Implementation depends on order type
s.orders[orderState.ID] = orderState
s.ordersDirty[orderState.ID] = struct{}{}
}
// UpdateLendingOrder updates a lending order in the state
func (s *LendingStateDB) UpdateLendingOrder(order interface{}) {
s.SetLendingOrder(order)
}
// DeleteLendingOrder deletes a lending order from the state
func (s *LendingStateDB) DeleteLendingOrder(orderID common.Hash) {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.orders, orderID)
s.ordersDirty[orderID] = struct{}{}
}
// GetLendingOrderBook returns a lending order book by pair key
func (s *LendingStateDB) GetLendingOrderBook(pairKey common.Hash) *LendingOrderBook {
s.lock.Lock()
defer s.lock.Unlock()
if ob, exists := s.orderBooks[pairKey]; exists {
return ob
}
// Load from trie
ob := s.loadLendingOrderBook(pairKey)
if ob != nil {
s.orderBooks[pairKey] = ob
}
return ob
}
// GetOrCreateLendingOrderBook returns or creates a lending order book
func (s *LendingStateDB) GetOrCreateLendingOrderBook(pairKey common.Hash) *LendingOrderBook {
ob := s.GetLendingOrderBook(pairKey)
if ob == nil {
s.lock.Lock()
ob = NewLendingOrderBook(pairKey)
s.orderBooks[pairKey] = ob
s.orderBooksDirty[pairKey] = struct{}{}
s.lock.Unlock()
}
return ob
}
// GetLoan returns a loan by ID
func (s *LendingStateDB) GetLoan(loanID common.Hash) *LoanState {
s.lock.Lock()
defer s.lock.Unlock()
if loan, exists := s.loans[loanID]; exists {
return loan
}
// Load from trie
loan := s.loadLoan(loanID)
if loan != nil {
s.loans[loanID] = loan
}
return loan
}
// SetLoan sets a loan in the state
func (s *LendingStateDB) SetLoan(loan *LoanState) {
s.lock.Lock()
defer s.lock.Unlock()
s.loans[loan.ID] = loan
s.loansDirty[loan.ID] = struct{}{}
}
// UpdateLoan updates a loan in the state
func (s *LendingStateDB) UpdateLoan(loan interface{}) {
// Convert interface to LoanState
if l, ok := loan.(*LoanState); ok {
s.SetLoan(l)
}
}
// GetAllLoans returns all loans
func (s *LendingStateDB) GetAllLoans() []*LoanState {
s.lock.Lock()
defer s.lock.Unlock()
loans := make([]*LoanState, 0, len(s.loans))
for _, loan := range s.loans {
loans = append(loans, loan.Copy())
}
return loans
}
// Commit commits all changes to the underlying database
func (s *LendingStateDB) Commit() (common.Hash, error) {
s.lock.Lock()
defer s.lock.Unlock()
// Commit dirty order books
for pairKey := range s.orderBooksDirty {
if ob, exists := s.orderBooks[pairKey]; exists {
if err := s.commitOrderBook(ob); err != nil {
return common.Hash{}, err
}
}
delete(s.orderBooksDirty, pairKey)
}
// Commit dirty orders
for orderID := range s.ordersDirty {
if order, exists := s.orders[orderID]; exists {
if err := s.commitOrder(order); err != nil {
return common.Hash{}, err
}
}
delete(s.ordersDirty, orderID)
}
// Commit dirty loans
for loanID := range s.loansDirty {
if loan, exists := s.loans[loanID]; exists {
if err := s.commitLoan(loan); err != nil {
return common.Hash{}, err
}
}
delete(s.loansDirty, loanID)
}
// Commit trie
root, err := s.trie.Commit(nil)
if err != nil {
return common.Hash{}, err
}
log.Debug("Lending state committed", "root", root.Hex())
return root, nil
}
// Copy creates a copy of the lending state
func (s *LendingStateDB) Copy() *LendingStateDB {
s.lock.Lock()
defer s.lock.Unlock()
state := &LendingStateDB{
db: s.db,
trie: s.db.CopyTrie(s.trie),
orderBooks: make(map[common.Hash]*LendingOrderBook),
orderBooksDirty: make(map[common.Hash]struct{}),
orders: make(map[common.Hash]*LendingOrderState),
ordersDirty: make(map[common.Hash]struct{}),
loans: make(map[common.Hash]*LoanState),
loansDirty: make(map[common.Hash]struct{}),
}
// Copy order books
for key, ob := range s.orderBooks {
state.orderBooks[key] = ob.Copy()
}
for key := range s.orderBooksDirty {
state.orderBooksDirty[key] = struct{}{}
}
// Copy orders
for key, order := range s.orders {
state.orders[key] = order.Copy()
}
for key := range s.ordersDirty {
state.ordersDirty[key] = struct{}{}
}
// Copy loans
for key, loan := range s.loans {
state.loans[key] = loan.Copy()
}
for key := range s.loansDirty {
state.loansDirty[key] = struct{}{}
}
return state
}
// loadLendingOrder loads a lending order from the trie
func (s *LendingStateDB) loadLendingOrder(orderID common.Hash) *LendingOrderState {
return nil
}
// loadLendingOrderBook loads a lending order book from the trie
func (s *LendingStateDB) loadLendingOrderBook(pairKey common.Hash) *LendingOrderBook {
return nil
}
// loadLoan loads a loan from the trie
func (s *LendingStateDB) loadLoan(loanID common.Hash) *LoanState {
return nil
}
// commitOrderBook commits a lending order book
func (s *LendingStateDB) commitOrderBook(ob *LendingOrderBook) error {
return nil
}
// commitOrder commits a lending order
func (s *LendingStateDB) commitOrder(order *LendingOrderState) error {
return nil
}
// commitLoan commits a loan
func (s *LendingStateDB) commitLoan(loan *LoanState) error {
return nil
}
// Database wraps access to tries and contract code
type Database interface {
// OpenTrie opens the main lending state trie
OpenTrie(root common.Hash) (Trie, error)
// CopyTrie returns an independent copy of the given trie
CopyTrie(Trie) Trie
}
// Trie is a XDCx Merkle Patricia trie
type Trie interface {
GetKey([]byte) []byte
TryGet(key []byte) ([]byte, error)
TryUpdate(key, value []byte) error
TryDelete(key []byte) error
Hash() common.Hash
Commit(onleaf trie.LeafCallback) (common.Hash, error)
NodeIterator(startKey []byte) trie.NodeIterator
}
// NewDatabase creates a new lending state database
func NewDatabase(db ethdb.Database) Database {
return &cachingDB{
db: db,
}
}
type cachingDB struct {
db ethdb.Database
}
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
return nil, nil
}
func (db *cachingDB) CopyTrie(t Trie) Trie {
return nil
}
// LendingOrderState represents a lending order in the state
type LendingOrderState struct {
ID common.Hash
UserAddress common.Address
RelayerAddress common.Address
LendingToken common.Address
CollateralToken common.Address
Side uint8
Type uint8
InterestRate *big.Int
Term uint64
Quantity *big.Int
FilledQuantity *big.Int
Status uint8
Nonce uint64
Timestamp uint64
Signature []byte
}
// Copy creates a copy of the lending order state
func (o *LendingOrderState) Copy() *LendingOrderState {
return &LendingOrderState{
ID: o.ID,
UserAddress: o.UserAddress,
RelayerAddress: o.RelayerAddress,
LendingToken: o.LendingToken,
CollateralToken: o.CollateralToken,
Side: o.Side,
Type: o.Type,
InterestRate: new(big.Int).Set(o.InterestRate),
Term: o.Term,
Quantity: new(big.Int).Set(o.Quantity),
FilledQuantity: new(big.Int).Set(o.FilledQuantity),
Status: o.Status,
Nonce: o.Nonce,
Timestamp: o.Timestamp,
Signature: append([]byte{}, o.Signature...),
}
}
// RemainingQuantity returns the remaining quantity
func (o *LendingOrderState) RemainingQuantity() *big.Int {
return new(big.Int).Sub(o.Quantity, o.FilledQuantity)
}
// IsFilled returns whether the order is filled
func (o *LendingOrderState) IsFilled() bool {
return o.FilledQuantity.Cmp(o.Quantity) >= 0
}
// LoanState represents a loan in the state
type LoanState struct {
ID common.Hash
BorrowerAddress common.Address
LenderAddress common.Address
LendingToken common.Address
CollateralToken common.Address
Principal *big.Int
CollateralAmount *big.Int
InterestRate *big.Int
Term uint64
StartTime uint64
ExpiryTime uint64
Status uint8
LiquidationPrice *big.Int
}
// Copy creates a copy of the loan state
func (l *LoanState) Copy() *LoanState {
return &LoanState{
ID: l.ID,
BorrowerAddress: l.BorrowerAddress,
LenderAddress: l.LenderAddress,
LendingToken: l.LendingToken,
CollateralToken: l.CollateralToken,
Principal: new(big.Int).Set(l.Principal),
CollateralAmount: new(big.Int).Set(l.CollateralAmount),
InterestRate: new(big.Int).Set(l.InterestRate),
Term: l.Term,
StartTime: l.StartTime,
ExpiryTime: l.ExpiryTime,
Status: l.Status,
LiquidationPrice: new(big.Int).Set(l.LiquidationPrice),
}
}

176
XDCxlending/liquidator.go Normal file
View file

@ -0,0 +1,176 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCxlending
import (
"context"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/XDCxlending/lendingstate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/log"
)
// Liquidator handles loan liquidation
type Liquidator struct {
lending *XDCxLending
lock sync.Mutex
}
// NewLiquidator creates a new liquidator
func NewLiquidator(lending *XDCxLending) *Liquidator {
return &Liquidator{
lending: lending,
}
}
// Liquidate liquidates an undercollateralized loan
func (l *Liquidator) Liquidate(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, loanID common.Hash) error {
l.lock.Lock()
defer l.lock.Unlock()
loan := lendingState.GetLoan(loanID)
if loan == nil {
return ErrLoanNotFound
}
// Check if loan is eligible for liquidation
collateralPrice := l.getCollateralPrice(statedb, loan.CollateralToken)
if !l.isLiquidatable(loan, collateralPrice) {
return ErrInsufficientCollateral
}
// Perform liquidation
if err := l.performLiquidation(statedb, lendingState, loan, collateralPrice); err != nil {
return err
}
// Update loan status
loan.Status = LoanStatusLiquidated
lendingState.UpdateLoan(loan)
log.Info("Loan liquidated", "loanID", loanID.Hex())
return nil
}
// CheckLoansForLiquidation checks all active loans for liquidation
func (l *Liquidator) CheckLoansForLiquidation(lendingState *lendingstate.LendingStateDB) ([]common.Hash, error) {
l.lock.Lock()
defer l.lock.Unlock()
liquidatableLoans := make([]common.Hash, 0)
allLoans := lendingState.GetAllLoans()
for _, loan := range allLoans {
if loan.Status != LoanStatusActive {
continue
}
// Check collateral ratio
collateralPrice := big.NewInt(1e18) // Placeholder - should get from oracle
if l.isLiquidatable(loan, collateralPrice) {
liquidatableLoans = append(liquidatableLoans, loan.ID)
}
}
return liquidatableLoans, nil
}
// isLiquidatable checks if a loan is eligible for liquidation
func (l *Liquidator) isLiquidatable(loan *Loan, collateralPrice *big.Int) bool {
// Calculate current collateral ratio
ratio := loan.GetCollateralRatio(collateralPrice)
// Compare with liquidation threshold
liquidationRate := l.lending.GetConfig().LiquidationRate
return ratio.Cmp(liquidationRate) < 0
}
// performLiquidation performs the actual liquidation
func (l *Liquidator) performLiquidation(statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, loan *Loan, collateralPrice *big.Int) error {
// Calculate liquidation amounts
principal := loan.Principal
interest := l.lending.calculateInterest(loan)
totalDebt := new(big.Int).Add(principal, interest)
// Calculate collateral to sell
collateralToSell := new(big.Int).Mul(totalDebt, big.NewInt(1e18))
collateralToSell = collateralToSell.Div(collateralToSell, collateralPrice)
// Add liquidation penalty (5%)
penalty := new(big.Int).Mul(collateralToSell, big.NewInt(5))
penalty = penalty.Div(penalty, big.NewInt(100))
totalCollateralSold := new(big.Int).Add(collateralToSell, penalty)
// Ensure we don't sell more than available
if totalCollateralSold.Cmp(loan.CollateralAmount) > 0 {
totalCollateralSold = loan.CollateralAmount
}
// Calculate remaining collateral for borrower
remainingCollateral := new(big.Int).Sub(loan.CollateralAmount, totalCollateralSold)
log.Debug("Liquidation performed",
"loanID", loan.ID.Hex(),
"totalDebt", totalDebt,
"collateralSold", totalCollateralSold,
"remainingCollateral", remainingCollateral,
)
return nil
}
// getCollateralPrice gets the current price of the collateral token
func (l *Liquidator) getCollateralPrice(statedb *state.StateDB, collateralToken common.Address) *big.Int {
// This should query the price oracle
// Placeholder implementation
return big.NewInt(1e18)
}
// LiquidationEvent represents a liquidation event
type LiquidationEvent struct {
LoanID common.Hash `json:"loanId"`
Borrower common.Address `json:"borrower"`
Lender common.Address `json:"lender"`
Liquidator common.Address `json:"liquidator"`
CollateralSold *big.Int `json:"collateralSold"`
DebtRepaid *big.Int `json:"debtRepaid"`
LiquidationBonus *big.Int `json:"liquidationBonus"`
BlockNumber uint64 `json:"blockNumber"`
TxHash common.Hash `json:"txHash"`
}
// RecallLoans recalls loans that have exceeded their term
func (l *Liquidator) RecallLoans(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, currentTime uint64) error {
l.lock.Lock()
defer l.lock.Unlock()
allLoans := lendingState.GetAllLoans()
for _, loan := range allLoans {
if loan.Status != LoanStatusActive {
continue
}
if loan.IsExpired(currentTime) {
log.Info("Recalling expired loan", "loanID", loan.ID.Hex())
// Mark for liquidation or automatic repayment
loan.Status = LoanStatusDefaulted
lendingState.UpdateLoan(loan)
}
}
return nil
}
// GetLiquidationPrice calculates the price at which a loan becomes liquidatable
func (l *Liquidator) GetLiquidationPrice(loan *Loan) *big.Int {
// Liquidation price = (Principal * LiquidationRate) / (CollateralAmount * 100)
liquidationRate := l.lending.GetConfig().LiquidationRate
numerator := new(big.Int).Mul(loan.Principal, liquidationRate)
denominator := new(big.Int).Mul(loan.CollateralAmount, big.NewInt(100))
price := new(big.Int).Div(numerator, denominator)
return price
}

259
XDCxlending/order.go Normal file
View file

@ -0,0 +1,259 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCxlending
import (
"crypto/ecdsa"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// OrderSide represents the side of a lending order (borrow/lend)
type OrderSide uint8
const (
// Borrow represents a borrow order
Borrow OrderSide = iota
// Lend represents a lend order
Lend
)
// OrderType represents the type of a lending order
type OrderType uint8
const (
// LimitOrder represents a limit order
LimitOrder OrderType = iota
// MarketOrder represents a market order
MarketOrder
)
// OrderStatus represents the status of a lending order
type OrderStatus uint8
const (
// OrderStatusNew represents a new order
OrderStatusNew OrderStatus = iota
// OrderStatusPartialFilled represents a partially filled order
OrderStatusPartialFilled
// OrderStatusFilled represents a filled order
OrderStatusFilled
// OrderStatusCancelled represents a cancelled order
OrderStatusCancelled
// OrderStatusRejected represents a rejected order
OrderStatusRejected
)
// LoanStatus represents the status of a loan
type LoanStatus uint8
const (
// LoanStatusActive represents an active loan
LoanStatusActive LoanStatus = iota
// LoanStatusRepaid represents a repaid loan
LoanStatusRepaid
// LoanStatusLiquidated represents a liquidated loan
LoanStatusLiquidated
// LoanStatusDefaulted represents a defaulted loan
LoanStatusDefaulted
)
// LendingOrder represents a lending order
type LendingOrder struct {
ID common.Hash `json:"id"`
UserAddress common.Address `json:"userAddress"`
RelayerAddress common.Address `json:"relayerAddress"`
LendingToken common.Address `json:"lendingToken"`
CollateralToken common.Address `json:"collateralToken"`
Side OrderSide `json:"side"`
Type OrderType `json:"type"`
InterestRate *big.Int `json:"interestRate"` // Annual rate in basis points
Term uint64 `json:"term"` // Duration in seconds
Quantity *big.Int `json:"quantity"`
FilledQuantity *big.Int `json:"filledQuantity"`
Status OrderStatus `json:"status"`
Nonce uint64 `json:"nonce"`
Timestamp uint64 `json:"timestamp"`
Signature []byte `json:"signature"`
}
// NewLendingOrder creates a new lending order
func NewLendingOrder(
userAddress common.Address,
lendingToken, collateralToken common.Address,
side OrderSide,
orderType OrderType,
interestRate *big.Int,
term uint64,
quantity *big.Int,
nonce uint64,
relayerAddress common.Address,
) *LendingOrder {
order := &LendingOrder{
UserAddress: userAddress,
RelayerAddress: relayerAddress,
LendingToken: lendingToken,
CollateralToken: collateralToken,
Side: side,
Type: orderType,
InterestRate: new(big.Int).Set(interestRate),
Term: term,
Quantity: new(big.Int).Set(quantity),
FilledQuantity: big.NewInt(0),
Status: OrderStatusNew,
Nonce: nonce,
}
order.ID = order.ComputeHash()
return order
}
// ComputeHash computes the hash of the lending order
func (o *LendingOrder) ComputeHash() common.Hash {
data := append(o.UserAddress.Bytes(), o.LendingToken.Bytes()...)
data = append(data, o.CollateralToken.Bytes()...)
data = append(data, byte(o.Side))
data = append(data, byte(o.Type))
data = append(data, common.BigToHash(o.InterestRate).Bytes()...)
data = append(data, common.BigToHash(big.NewInt(int64(o.Term))).Bytes()...)
data = append(data, common.BigToHash(o.Quantity).Bytes()...)
data = append(data, common.BigToHash(big.NewInt(int64(o.Nonce))).Bytes()...)
data = append(data, o.RelayerAddress.Bytes()...)
return crypto.Keccak256Hash(data)
}
// Sign signs the lending order with the given private key
func (o *LendingOrder) Sign(privateKey *ecdsa.PrivateKey) error {
hash := o.ComputeHash()
sig, err := crypto.Sign(hash.Bytes(), privateKey)
if err != nil {
return err
}
o.Signature = sig
return nil
}
// VerifySignature verifies the lending order signature
func (o *LendingOrder) VerifySignature() bool {
if len(o.Signature) != 65 {
return false
}
hash := o.ComputeHash()
pubKey, err := crypto.SigToPub(hash.Bytes(), o.Signature)
if err != nil {
return false
}
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
return recoveredAddr == o.UserAddress
}
// RemainingQuantity returns the remaining quantity to be filled
func (o *LendingOrder) RemainingQuantity() *big.Int {
return new(big.Int).Sub(o.Quantity, o.FilledQuantity)
}
// IsFilled returns whether the order is fully filled
func (o *LendingOrder) IsFilled() bool {
return o.FilledQuantity.Cmp(o.Quantity) >= 0
}
// Clone creates a copy of the lending order
func (o *LendingOrder) Clone() *LendingOrder {
return &LendingOrder{
ID: o.ID,
UserAddress: o.UserAddress,
RelayerAddress: o.RelayerAddress,
LendingToken: o.LendingToken,
CollateralToken: o.CollateralToken,
Side: o.Side,
Type: o.Type,
InterestRate: new(big.Int).Set(o.InterestRate),
Term: o.Term,
Quantity: new(big.Int).Set(o.Quantity),
FilledQuantity: new(big.Int).Set(o.FilledQuantity),
Status: o.Status,
Nonce: o.Nonce,
Timestamp: o.Timestamp,
Signature: append([]byte{}, o.Signature...),
}
}
// PairKey returns the lending pair key
func (o *LendingOrder) PairKey() common.Hash {
return GetLendingPairKey(o.LendingToken, o.CollateralToken, o.Term)
}
// GetLendingPairKey returns the lending pair key
func GetLendingPairKey(lendingToken, collateralToken common.Address, term uint64) common.Hash {
data := append(lendingToken.Bytes(), collateralToken.Bytes()...)
data = append(data, common.BigToHash(big.NewInt(int64(term))).Bytes()...)
return crypto.Keccak256Hash(data)
}
// Loan represents an active loan
type Loan struct {
ID common.Hash `json:"id"`
BorrowerAddress common.Address `json:"borrowerAddress"`
LenderAddress common.Address `json:"lenderAddress"`
LendingToken common.Address `json:"lendingToken"`
CollateralToken common.Address `json:"collateralToken"`
Principal *big.Int `json:"principal"`
CollateralAmount *big.Int `json:"collateralAmount"`
InterestRate *big.Int `json:"interestRate"`
Term uint64 `json:"term"`
StartTime uint64 `json:"startTime"`
ExpiryTime uint64 `json:"expiryTime"`
Status LoanStatus `json:"status"`
LiquidationPrice *big.Int `json:"liquidationPrice"`
}
// NewLoan creates a new loan
func NewLoan(
borrower, lender common.Address,
lendingToken, collateralToken common.Address,
principal, collateral *big.Int,
interestRate *big.Int,
term, startTime uint64,
) *Loan {
loan := &Loan{
BorrowerAddress: borrower,
LenderAddress: lender,
LendingToken: lendingToken,
CollateralToken: collateralToken,
Principal: new(big.Int).Set(principal),
CollateralAmount: new(big.Int).Set(collateral),
InterestRate: new(big.Int).Set(interestRate),
Term: term,
StartTime: startTime,
ExpiryTime: startTime + term,
Status: LoanStatusActive,
}
loan.ID = loan.ComputeHash()
return loan
}
// ComputeHash computes the loan hash
func (l *Loan) ComputeHash() common.Hash {
data := append(l.BorrowerAddress.Bytes(), l.LenderAddress.Bytes()...)
data = append(data, l.LendingToken.Bytes()...)
data = append(data, l.CollateralToken.Bytes()...)
data = append(data, common.BigToHash(l.Principal).Bytes()...)
data = append(data, common.BigToHash(big.NewInt(int64(l.StartTime))).Bytes()...)
return crypto.Keccak256Hash(data)
}
// IsExpired returns whether the loan has expired
func (l *Loan) IsExpired(currentTime uint64) bool {
return currentTime >= l.ExpiryTime
}
// GetCollateralRatio returns the current collateral ratio
func (l *Loan) GetCollateralRatio(collateralPrice *big.Int) *big.Int {
// Ratio = (CollateralAmount * CollateralPrice) / Principal * 100
collateralValue := new(big.Int).Mul(l.CollateralAmount, collateralPrice)
ratio := new(big.Int).Mul(collateralValue, big.NewInt(100))
ratio = ratio.Div(ratio, l.Principal)
return ratio
}

View file

@ -0,0 +1,306 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCxlending
import (
"context"
"errors"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/XDCxlending/lendingstate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/log"
)
var (
// ErrInsufficientLendingBalance is returned when lending balance is insufficient
ErrInsufficientLendingBalance = errors.New("insufficient lending balance")
// ErrOrderAlreadyExists is returned when order already exists
ErrOrderAlreadyExists = errors.New("order already exists")
// ErrOrderCannotBeCancelled is returned when order cannot be cancelled
ErrOrderCannotBeCancelled = errors.New("order cannot be cancelled")
// ErrInvalidSignature is returned when signature is invalid
ErrInvalidSignature = errors.New("invalid signature")
)
// OrderProcessor handles lending order processing
type OrderProcessor struct {
lending *XDCxLending
lock sync.Mutex
}
// NewOrderProcessor creates a new order processor
func NewOrderProcessor(lending *XDCxLending) *OrderProcessor {
return &OrderProcessor{
lending: lending,
}
}
// Process processes a lending order and returns matched trades
func (op *OrderProcessor) Process(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, order *LendingOrder) ([]*LendingTrade, error) {
op.lock.Lock()
defer op.lock.Unlock()
// Validate order
if err := op.validateOrder(order); err != nil {
return nil, err
}
// Verify signature
if !order.VerifySignature() {
return nil, ErrInvalidSignature
}
// Check balance and collateral
if err := op.checkBalanceAndCollateral(statedb, order); err != nil {
return nil, err
}
// Match order
trades, err := op.matchOrder(lendingState, order)
if err != nil {
return nil, err
}
// If order is not fully filled, add to order book
if !order.IsFilled() && order.Type == LimitOrder {
if err := op.addToOrderBook(lendingState, order); err != nil {
return nil, err
}
}
log.Debug("Lending order processed", "orderID", order.ID.Hex(), "trades", len(trades))
return trades, nil
}
// Cancel cancels an existing lending order
func (op *OrderProcessor) Cancel(ctx context.Context, statedb *state.StateDB, lendingState *lendingstate.LendingStateDB, orderID common.Hash) error {
op.lock.Lock()
defer op.lock.Unlock()
// Get order from state
order := lendingState.GetLendingOrder(orderID)
if order == nil {
return ErrLoanNotFound
}
// Check if order can be cancelled
if order.Status == OrderStatusFilled || order.Status == OrderStatusCancelled {
return ErrOrderCannotBeCancelled
}
// Remove from order book
if err := op.removeFromOrderBook(lendingState, order); err != nil {
return err
}
// Update order status
order.Status = OrderStatusCancelled
lendingState.UpdateLendingOrder(order)
log.Debug("Lending order cancelled", "orderID", orderID.Hex())
return nil
}
// validateOrder validates a lending order
func (op *OrderProcessor) validateOrder(order *LendingOrder) error {
if order.InterestRate == nil || order.InterestRate.Sign() < 0 {
return ErrInvalidInterestRate
}
if order.Term == 0 {
return ErrInvalidTerm
}
if order.Quantity == nil || order.Quantity.Sign() <= 0 {
return errors.New("invalid quantity")
}
return nil
}
// checkBalanceAndCollateral checks if user has sufficient balance and collateral
func (op *OrderProcessor) checkBalanceAndCollateral(statedb *state.StateDB, order *LendingOrder) error {
if order.Side == Borrow {
// Borrowers need collateral
collateralNeeded := op.calculateRequiredCollateral(order)
balance := op.getTokenBalance(statedb, order.CollateralToken, order.UserAddress)
if balance.Cmp(collateralNeeded) < 0 {
return ErrInsufficientCollateral
}
} else {
// Lenders need the lending token
balance := op.getTokenBalance(statedb, order.LendingToken, order.UserAddress)
if balance.Cmp(order.Quantity) < 0 {
return ErrInsufficientLendingBalance
}
}
return nil
}
// calculateRequiredCollateral calculates the required collateral for a borrow order
func (op *OrderProcessor) calculateRequiredCollateral(order *LendingOrder) *big.Int {
// Required collateral = quantity * minCollateralRatio / 100
minCollateral := op.lending.GetConfig().MinCollateral
required := new(big.Int).Mul(order.Quantity, minCollateral)
required = required.Div(required, big.NewInt(100))
return required
}
// getTokenBalance gets the token balance for a user
func (op *OrderProcessor) getTokenBalance(statedb *state.StateDB, token, user common.Address) *big.Int {
// This would call the ERC20 balanceOf function
// Simplified implementation
return statedb.GetBalance(user).ToBig()
}
// matchOrder matches a lending order against the order book
func (op *OrderProcessor) matchOrder(lendingState *lendingstate.LendingStateDB, order *LendingOrder) ([]*LendingTrade, error) {
var trades []*LendingTrade
pairKey := order.PairKey()
orderBook := lendingState.GetLendingOrderBook(pairKey)
if orderBook == nil {
return trades, nil
}
// Match based on order side
if order.Side == Borrow {
trades = op.matchBorrowOrder(lendingState, orderBook, order)
} else {
trades = op.matchLendOrder(lendingState, orderBook, order)
}
return trades, nil
}
// matchBorrowOrder matches a borrow order against lend orders
func (op *OrderProcessor) matchBorrowOrder(lendingState *lendingstate.LendingStateDB, orderBook *lendingstate.LendingOrderBook, borrowOrder *LendingOrder) []*LendingTrade {
var trades []*LendingTrade
// Match against lend orders from lowest to highest interest rate
for !borrowOrder.IsFilled() {
bestLend := orderBook.GetBestLend()
if bestLend == nil || bestLend.InterestRate.Cmp(borrowOrder.InterestRate) > 0 {
break
}
trade := op.executeTrade(borrowOrder, bestLend)
trades = append(trades, trade)
if bestLend.IsFilled() {
orderBook.RemoveLend(bestLend.ID)
} else {
lendingState.UpdateLendingOrder(bestLend)
}
}
return trades
}
// matchLendOrder matches a lend order against borrow orders
func (op *OrderProcessor) matchLendOrder(lendingState *lendingstate.LendingStateDB, orderBook *lendingstate.LendingOrderBook, lendOrder *LendingOrder) []*LendingTrade {
var trades []*LendingTrade
// Match against borrow orders from highest to lowest interest rate
for !lendOrder.IsFilled() {
bestBorrow := orderBook.GetBestBorrow()
if bestBorrow == nil || bestBorrow.InterestRate.Cmp(lendOrder.InterestRate) < 0 {
break
}
trade := op.executeTrade(bestBorrow, lendOrder)
trades = append(trades, trade)
if bestBorrow.IsFilled() {
orderBook.RemoveBorrow(bestBorrow.ID)
} else {
lendingState.UpdateLendingOrder(bestBorrow)
}
}
return trades
}
// executeTrade executes a lending trade between two orders
func (op *OrderProcessor) executeTrade(borrowOrder, lendOrder *LendingOrder) *LendingTrade {
// Determine trade quantity
borrowRemaining := borrowOrder.RemainingQuantity()
lendRemaining := lendOrder.RemainingQuantity()
var tradeQuantity *big.Int
if borrowRemaining.Cmp(lendRemaining) < 0 {
tradeQuantity = borrowRemaining
} else {
tradeQuantity = lendRemaining
}
// Use market maker's interest rate
tradeInterestRate := lendOrder.InterestRate
// Update filled quantities
borrowOrder.FilledQuantity = new(big.Int).Add(borrowOrder.FilledQuantity, tradeQuantity)
lendOrder.FilledQuantity = new(big.Int).Add(lendOrder.FilledQuantity, tradeQuantity)
// Update order statuses
if borrowOrder.IsFilled() {
borrowOrder.Status = OrderStatusFilled
} else {
borrowOrder.Status = OrderStatusPartialFilled
}
if lendOrder.IsFilled() {
lendOrder.Status = OrderStatusFilled
} else {
lendOrder.Status = OrderStatusPartialFilled
}
// Calculate collateral
collateral := op.calculateRequiredCollateral(borrowOrder)
// Create trade
return NewLendingTrade(
borrowOrder.ID,
lendOrder.ID,
borrowOrder.UserAddress,
lendOrder.UserAddress,
borrowOrder.LendingToken,
borrowOrder.CollateralToken,
tradeQuantity,
collateral,
tradeInterestRate,
borrowOrder.Term,
)
}
// addToOrderBook adds an order to the order book
func (op *OrderProcessor) addToOrderBook(lendingState *lendingstate.LendingStateDB, order *LendingOrder) error {
pairKey := order.PairKey()
orderBook := lendingState.GetOrCreateLendingOrderBook(pairKey)
if order.Side == Borrow {
orderBook.AddBorrow(order)
} else {
orderBook.AddLend(order)
}
lendingState.SetLendingOrder(order)
return nil
}
// removeFromOrderBook removes an order from the order book
func (op *OrderProcessor) removeFromOrderBook(lendingState *lendingstate.LendingStateDB, order *LendingOrder) error {
pairKey := order.PairKey()
orderBook := lendingState.GetLendingOrderBook(pairKey)
if orderBook == nil {
return nil
}
if order.Side == Borrow {
orderBook.RemoveBorrow(order.ID)
} else {
orderBook.RemoveLend(order.ID)
}
return nil
}

162
XDCxlending/trade.go Normal file
View file

@ -0,0 +1,162 @@
// Copyright 2019 XDC Network
// This file is part of the XDC library.
package XDCxlending
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// LendingTradeStatus represents the status of a lending trade
type LendingTradeStatus uint8
const (
// TradeStatusPending represents a pending trade
TradeStatusPending LendingTradeStatus = iota
// TradeStatusSettled represents a settled trade
TradeStatusSettled
// TradeStatusFailed represents a failed trade
TradeStatusFailed
)
// LendingTrade represents a matched lending trade
type LendingTrade struct {
hash common.Hash
BorrowOrderID common.Hash `json:"borrowOrderId"`
LendOrderID common.Hash `json:"lendOrderId"`
Borrower common.Address `json:"borrower"`
Lender common.Address `json:"lender"`
LendingToken common.Address `json:"lendingToken"`
CollateralToken common.Address `json:"collateralToken"`
Principal *big.Int `json:"principal"`
CollateralAmount *big.Int `json:"collateralAmount"`
InterestRate *big.Int `json:"interestRate"`
Term uint64 `json:"term"`
BorrowFee *big.Int `json:"borrowFee"`
LendFee *big.Int `json:"lendFee"`
Timestamp uint64 `json:"timestamp"`
Status LendingTradeStatus `json:"status"`
BlockNumber uint64 `json:"blockNumber"`
TxHash common.Hash `json:"txHash"`
LoanID common.Hash `json:"loanId"`
}
// NewLendingTrade creates a new lending trade
func NewLendingTrade(
borrowOrderID, lendOrderID common.Hash,
borrower, lender common.Address,
lendingToken, collateralToken common.Address,
principal, collateral *big.Int,
interestRate *big.Int,
term uint64,
) *LendingTrade {
trade := &LendingTrade{
BorrowOrderID: borrowOrderID,
LendOrderID: lendOrderID,
Borrower: borrower,
Lender: lender,
LendingToken: lendingToken,
CollateralToken: collateralToken,
Principal: new(big.Int).Set(principal),
CollateralAmount: new(big.Int).Set(collateral),
InterestRate: new(big.Int).Set(interestRate),
Term: term,
BorrowFee: big.NewInt(0),
LendFee: big.NewInt(0),
Status: TradeStatusPending,
}
trade.hash = trade.ComputeHash()
return trade
}
// ComputeHash computes the hash of the lending trade
func (t *LendingTrade) ComputeHash() common.Hash {
data := append(t.BorrowOrderID.Bytes(), t.LendOrderID.Bytes()...)
data = append(data, t.Borrower.Bytes()...)
data = append(data, t.Lender.Bytes()...)
data = append(data, t.LendingToken.Bytes()...)
data = append(data, t.CollateralToken.Bytes()...)
data = append(data, common.BigToHash(t.Principal).Bytes()...)
data = append(data, common.BigToHash(t.CollateralAmount).Bytes()...)
data = append(data, common.BigToHash(t.InterestRate).Bytes()...)
data = append(data, common.BigToHash(big.NewInt(int64(t.Term))).Bytes()...)
return crypto.Keccak256Hash(data)
}
// Hash returns the trade hash
func (t *LendingTrade) Hash() common.Hash {
if t.hash == (common.Hash{}) {
t.hash = t.ComputeHash()
}
return t.hash
}
// SetFees sets the borrower and lender fees
func (t *LendingTrade) SetFees(borrowFee, lendFee *big.Int) {
if borrowFee != nil {
t.BorrowFee = new(big.Int).Set(borrowFee)
}
if lendFee != nil {
t.LendFee = new(big.Int).Set(lendFee)
}
}
// SetSettled marks the trade as settled
func (t *LendingTrade) SetSettled(blockNumber uint64, txHash common.Hash, loanID common.Hash) {
t.Status = TradeStatusSettled
t.BlockNumber = blockNumber
t.TxHash = txHash
t.LoanID = loanID
}
// SetFailed marks the trade as failed
func (t *LendingTrade) SetFailed() {
t.Status = TradeStatusFailed
}
// PairKey returns the lending pair key
func (t *LendingTrade) PairKey() common.Hash {
return GetLendingPairKey(t.LendingToken, t.CollateralToken, t.Term)
}
// CalculateInterest calculates the interest for the loan
func (t *LendingTrade) CalculateInterest() *big.Int {
// Simple interest: Principal * Rate * Time / (365 * 100)
interest := new(big.Int).Mul(t.Principal, t.InterestRate)
interest = interest.Mul(interest, big.NewInt(int64(t.Term)))
interest = interest.Div(interest, big.NewInt(365*24*3600*100))
return interest
}
// TotalRepayment calculates the total repayment amount
func (t *LendingTrade) TotalRepayment() *big.Int {
interest := t.CalculateInterest()
return new(big.Int).Add(t.Principal, interest)
}
// Clone creates a copy of the lending trade
func (t *LendingTrade) Clone() *LendingTrade {
return &LendingTrade{
hash: t.hash,
BorrowOrderID: t.BorrowOrderID,
LendOrderID: t.LendOrderID,
Borrower: t.Borrower,
Lender: t.Lender,
LendingToken: t.LendingToken,
CollateralToken: t.CollateralToken,
Principal: new(big.Int).Set(t.Principal),
CollateralAmount: new(big.Int).Set(t.CollateralAmount),
InterestRate: new(big.Int).Set(t.InterestRate),
Term: t.Term,
BorrowFee: new(big.Int).Set(t.BorrowFee),
LendFee: new(big.Int).Set(t.LendFee),
Timestamp: t.Timestamp,
Status: t.Status,
BlockNumber: t.BlockNumber,
TxHash: t.TxHash,
LoanID: t.LoanID,
}
}

258
cmd/utils/flags_xdc.go Normal file
View file

@ -0,0 +1,258 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package utils
import (
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/node"
"github.com/urfave/cli/v2"
)
// XDC-specific command line flags
var (
// XDPoS flags
XDPoSRewardFlag = &cli.BoolFlag{
Name: "xdpos.rewards",
Usage: "Enable block rewards for validators",
Value: true,
Category: "XDPoS",
}
XDPoSSlashingFlag = &cli.BoolFlag{
Name: "xdpos.slashing",
Usage: "Enable slashing for misbehaving validators",
Value: true,
Category: "XDPoS",
}
XDPoSValidatorFlag = &cli.StringFlag{
Name: "xdpos.validator",
Usage: "Public address for block validation (coinbase)",
Category: "XDPoS",
}
XDPoSGapFlag = &cli.Uint64Flag{
Name: "xdpos.gap",
Usage: "Gap for snapshot creation in XDPoS",
Value: 450,
Category: "XDPoS",
}
XDPoSEpochFlag = &cli.Uint64Flag{
Name: "xdpos.epoch",
Usage: "Epoch length for validator set updates",
Value: 900,
Category: "XDPoS",
}
// XDCx flags
XDCxEnableFlag = &cli.BoolFlag{
Name: "xdcx",
Usage: "Enable XDC decentralized exchange",
Value: false,
Category: "XDCx",
}
XDCxDataDirFlag = &cli.StringFlag{
Name: "xdcx.datadir",
Usage: "Data directory for XDCx",
Category: "XDCx",
}
// XDCxLending flags
XDCxLendingEnableFlag = &cli.BoolFlag{
Name: "xdcxlending",
Usage: "Enable XDC lending protocol",
Value: false,
Category: "XDCxLending",
}
XDCxLendingDataDirFlag = &cli.StringFlag{
Name: "xdcxlending.datadir",
Usage: "Data directory for XDCxLending",
Category: "XDCxLending",
}
// Network flags
XDCMainnetFlag = &cli.BoolFlag{
Name: "xdc.mainnet",
Usage: "Connect to XDC mainnet",
Category: "XDC Network",
}
XDCTestnetFlag = &cli.BoolFlag{
Name: "xdc.testnet",
Usage: "Connect to XDC Apothem testnet",
Category: "XDC Network",
}
XDCDevnetFlag = &cli.BoolFlag{
Name: "xdc.devnet",
Usage: "Connect to XDC devnet",
Category: "XDC Network",
}
// Sync flags
XDCSnapSyncFlag = &cli.BoolFlag{
Name: "xdc.snapsync",
Usage: "Enable XDC snapshot sync",
Value: false,
Category: "XDC Sync",
}
XDCSnapShotBlockFlag = &cli.Uint64Flag{
Name: "xdc.snapshot.block",
Usage: "Snapshot block number for sync",
Category: "XDC Sync",
}
XDCCheckpointIntervalFlag = &cli.Uint64Flag{
Name: "xdc.checkpoint.interval",
Usage: "Checkpoint interval in blocks",
Value: 900,
Category: "XDC Sync",
}
// Masternode flags
MasternodeFlag = &cli.BoolFlag{
Name: "masternode",
Usage: "Run as a masternode",
Value: false,
Category: "Masternode",
}
MasternodeKeyFlag = &cli.StringFlag{
Name: "masternode.key",
Usage: "Masternode private key for signing",
Category: "Masternode",
}
MasternodeCoinbaseFlag = &cli.StringFlag{
Name: "masternode.coinbase",
Usage: "Masternode coinbase address",
Category: "Masternode",
}
)
// XDCFlags contains all XDC-specific flags
var XDCFlags = []cli.Flag{
XDPoSRewardFlag,
XDPoSSlashingFlag,
XDPoSValidatorFlag,
XDPoSGapFlag,
XDPoSEpochFlag,
XDCxEnableFlag,
XDCxDataDirFlag,
XDCxLendingEnableFlag,
XDCxLendingDataDirFlag,
XDCMainnetFlag,
XDCTestnetFlag,
XDCDevnetFlag,
XDCSnapSyncFlag,
XDCSnapShotBlockFlag,
XDCCheckpointIntervalFlag,
MasternodeFlag,
MasternodeKeyFlag,
MasternodeCoinbaseFlag,
}
// SetXDCConfig applies XDC-specific configuration
func SetXDCConfig(ctx *cli.Context, cfg *ethconfig.Config) {
// XDPoS configuration
if ctx.IsSet(XDPoSRewardFlag.Name) {
cfg.XDPoSRewards = ctx.Bool(XDPoSRewardFlag.Name)
}
if ctx.IsSet(XDPoSSlashingFlag.Name) {
cfg.XDPoSSlashing = ctx.Bool(XDPoSSlashingFlag.Name)
}
if ctx.IsSet(XDPoSGapFlag.Name) {
cfg.XDPoSGap = ctx.Uint64(XDPoSGapFlag.Name)
}
if ctx.IsSet(XDPoSEpochFlag.Name) {
cfg.XDPoSEpoch = ctx.Uint64(XDPoSEpochFlag.Name)
}
// XDCx configuration
if ctx.IsSet(XDCxEnableFlag.Name) {
cfg.XDCxEnabled = ctx.Bool(XDCxEnableFlag.Name)
}
// XDCxLending configuration
if ctx.IsSet(XDCxLendingEnableFlag.Name) {
cfg.XDCxLendingEnabled = ctx.Bool(XDCxLendingEnableFlag.Name)
}
// Sync configuration
if ctx.IsSet(XDCSnapSyncFlag.Name) {
cfg.XDCSnapSync = ctx.Bool(XDCSnapSyncFlag.Name)
}
if ctx.IsSet(XDCCheckpointIntervalFlag.Name) {
cfg.XDCCheckpointInterval = ctx.Uint64(XDCCheckpointIntervalFlag.Name)
}
}
// SetXDCNodeConfig applies XDC-specific node configuration
func SetXDCNodeConfig(ctx *cli.Context, cfg *node.Config) {
// Set XDC-specific data directories
if ctx.IsSet(XDCxDataDirFlag.Name) {
// Configure XDCx data directory
}
if ctx.IsSet(XDCxLendingDataDirFlag.Name) {
// Configure XDCxLending data directory
}
}
// SetXDCNetworkConfig configures the network for XDC
func SetXDCNetworkConfig(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.Bool(XDCMainnetFlag.Name) {
// Configure for mainnet
cfg.NetworkId = 50
} else if ctx.Bool(XDCTestnetFlag.Name) {
// Configure for Apothem testnet
cfg.NetworkId = 51
} else if ctx.Bool(XDCDevnetFlag.Name) {
// Configure for devnet
cfg.NetworkId = 551
}
}
// MasternodeConfig holds masternode configuration
type MasternodeConfig struct {
Enable bool
Key string
Coinbase string
}
// SetMasternodeConfig sets masternode configuration
func SetMasternodeConfig(ctx *cli.Context) *MasternodeConfig {
config := &MasternodeConfig{
Enable: ctx.Bool(MasternodeFlag.Name),
}
if config.Enable {
if ctx.IsSet(MasternodeKeyFlag.Name) {
config.Key = ctx.String(MasternodeKeyFlag.Name)
}
if ctx.IsSet(MasternodeCoinbaseFlag.Name) {
config.Coinbase = ctx.String(MasternodeCoinbaseFlag.Name)
}
}
return config
}
// ValidateXDCFlags validates XDC-specific flags
func ValidateXDCFlags(ctx *cli.Context) error {
// Validate that mainnet, testnet, and devnet are mutually exclusive
networks := 0
if ctx.Bool(XDCMainnetFlag.Name) {
networks++
}
if ctx.Bool(XDCTestnetFlag.Name) {
networks++
}
if ctx.Bool(XDCDevnetFlag.Name) {
networks++
}
if networks > 1 {
return cli.Exit("Cannot specify multiple XDC networks", 1)
}
// Validate masternode configuration
if ctx.Bool(MasternodeFlag.Name) {
if !ctx.IsSet(MasternodeCoinbaseFlag.Name) {
return cli.Exit("Masternode coinbase address is required", 1)
}
}
return nil
}

View file

@ -0,0 +1,400 @@
// 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{}

View file

@ -0,0 +1,361 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package engines
import (
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
// mockChain implements consensus.ChainHeaderReader for testing
type mockChain struct {
headers map[common.Hash]*types.Header
}
func newMockChain() *mockChain {
return &mockChain{
headers: make(map[common.Hash]*types.Header),
}
}
func (m *mockChain) Config() *params.ChainConfig {
return &params.ChainConfig{}
}
func (m *mockChain) CurrentHeader() *types.Header {
return nil
}
func (m *mockChain) GetHeader(hash common.Hash, number uint64) *types.Header {
return m.headers[hash]
}
func (m *mockChain) GetHeaderByNumber(number uint64) *types.Header {
for _, h := range m.headers {
if h.Number.Uint64() == number {
return h
}
}
return nil
}
func (m *mockChain) GetHeaderByHash(hash common.Hash) *types.Header {
return m.headers[hash]
}
func (m *mockChain) GetTd(hash common.Hash, number uint64) *big.Int {
return big.NewInt(1)
}
func (m *mockChain) addHeader(header *types.Header) {
m.headers[header.Hash()] = header
}
// mockDB implements Database for testing
type mockDB struct {
data map[string][]byte
}
func newMockDB() *mockDB {
return &mockDB{
data: make(map[string][]byte),
}
}
func (m *mockDB) Get(key []byte) ([]byte, error) {
return m.data[string(key)], nil
}
func (m *mockDB) Put(key []byte, value []byte) error {
m.data[string(key)] = value
return nil
}
func (m *mockDB) Delete(key []byte) error {
delete(m.data, string(key))
return nil
}
func (m *mockDB) Has(key []byte) (bool, error) {
_, ok := m.data[string(key)]
return ok, nil
}
// createTestHeader creates a test header
func createTestHeader(parent *types.Header, signer common.Address) *types.Header {
number := big.NewInt(1)
parentHash := common.Hash{}
timestamp := uint64(time.Now().Unix())
if parent != nil {
number = new(big.Int).Add(parent.Number, big.NewInt(1))
parentHash = parent.Hash()
timestamp = parent.Time + 2
}
header := &types.Header{
ParentHash: parentHash,
UncleHash: types.EmptyUncleHash,
Coinbase: signer,
Root: common.Hash{},
TxHash: types.EmptyTxsHash,
ReceiptHash: types.EmptyReceiptsHash,
Bloom: types.Bloom{},
Difficulty: big.NewInt(1),
Number: number,
GasLimit: 8000000,
GasUsed: 0,
Time: timestamp,
Extra: make([]byte, ExtraVanity+crypto.SignatureLength),
MixDigest: common.Hash{},
Nonce: types.BlockNonce{},
}
return header
}
func TestNewEngineV1(t *testing.T) {
config := &params.XDPoSConfig{
Period: 2,
Epoch: 900,
}
db := newMockDB()
engine := NewEngineV1(config, db)
if engine == nil {
t.Fatal("Failed to create engine")
}
if engine.period != 2 {
t.Errorf("Expected period 2, got %d", engine.period)
}
}
func TestEngineV1_Author(t *testing.T) {
config := &params.XDPoSConfig{Period: 2}
db := newMockDB()
engine := NewEngineV1(config, db)
// Create a test key
privateKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
signer := crypto.PubkeyToAddress(privateKey.PublicKey)
// Create and sign a header
header := createTestHeader(nil, signer)
// Sign the header
sig, err := crypto.Sign(sigHash(header).Bytes(), privateKey)
if err != nil {
t.Fatal(err)
}
copy(header.Extra[ExtraVanity:], sig)
// Test Author
author, err := engine.Author(header)
if err != nil {
t.Fatal(err)
}
if author != signer {
t.Errorf("Expected author %s, got %s", signer.Hex(), author.Hex())
}
}
func TestEngineV1_CalcDifficulty(t *testing.T) {
config := &params.XDPoSConfig{Period: 2}
db := newMockDB()
engine := NewEngineV1(config, db)
chain := newMockChain()
parent := createTestHeader(nil, common.Address{})
difficulty := engine.CalcDifficulty(chain, uint64(time.Now().Unix()), parent)
if difficulty.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Expected difficulty 1, got %s", difficulty.String())
}
}
func TestEngineV1_Prepare(t *testing.T) {
config := &params.XDPoSConfig{Period: 2}
db := newMockDB()
engine := NewEngineV1(config, db)
// Set up signer
privateKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
signer := crypto.PubkeyToAddress(privateKey.PublicKey)
engine.Authorize(signer, func(account common.Address, data []byte) ([]byte, error) {
return crypto.Sign(data, privateKey)
})
// Create chain with parent
chain := newMockChain()
parent := createTestHeader(nil, signer)
chain.addHeader(parent)
// Create child header
header := &types.Header{
ParentHash: parent.Hash(),
Number: new(big.Int).Add(parent.Number, big.NewInt(1)),
}
// Prepare
err = engine.Prepare(chain, header)
if err != nil {
t.Fatal(err)
}
// Check header was prepared correctly
if header.Coinbase != signer {
t.Errorf("Expected coinbase %s, got %s", signer.Hex(), header.Coinbase.Hex())
}
if len(header.Extra) != ExtraVanity+crypto.SignatureLength {
t.Errorf("Expected extra length %d, got %d", ExtraVanity+crypto.SignatureLength, len(header.Extra))
}
}
func TestEngineV1_VerifyUncles(t *testing.T) {
config := &params.XDPoSConfig{Period: 2}
db := newMockDB()
engine := NewEngineV1(config, db)
// Block without uncles should be valid
block := types.NewBlockWithHeader(&types.Header{})
err := engine.VerifyUncles(nil, block)
if err != nil {
t.Errorf("Expected no error for block without uncles, got %v", err)
}
}
func TestEngineV1_SealHash(t *testing.T) {
config := &params.XDPoSConfig{Period: 2}
db := newMockDB()
engine := NewEngineV1(config, db)
header := createTestHeader(nil, common.Address{})
hash := engine.SealHash(header)
if hash == (common.Hash{}) {
t.Error("Expected non-zero seal hash")
}
}
func TestEngineV1_Authorize(t *testing.T) {
config := &params.XDPoSConfig{Period: 2}
db := newMockDB()
engine := NewEngineV1(config, db)
privateKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
signer := crypto.PubkeyToAddress(privateKey.PublicKey)
signFn := func(account common.Address, data []byte) ([]byte, error) {
return crypto.Sign(data, privateKey)
}
engine.Authorize(signer, signFn)
engine.lock.RLock()
defer engine.lock.RUnlock()
if engine.signer != signer {
t.Errorf("Expected signer %s, got %s", signer.Hex(), engine.signer.Hex())
}
if engine.signFn == nil {
t.Error("Expected signFn to be set")
}
}
func TestSigHash(t *testing.T) {
header := createTestHeader(nil, common.Address{})
hash1 := sigHash(header)
hash2 := sigHash(header)
if hash1 != hash2 {
t.Error("Expected consistent hash")
}
// Modify header
header.GasLimit = 9000000
hash3 := sigHash(header)
if hash1 == hash3 {
t.Error("Expected different hash after modification")
}
}
func TestEcrecover(t *testing.T) {
privateKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
signer := crypto.PubkeyToAddress(privateKey.PublicKey)
header := createTestHeader(nil, signer)
// Sign the header
sig, err := crypto.Sign(sigHash(header).Bytes(), privateKey)
if err != nil {
t.Fatal(err)
}
copy(header.Extra[ExtraVanity:], sig)
// Recover
cache := newLRU(10)
recovered, err := ecrecover(header, cache)
if err != nil {
t.Fatal(err)
}
if recovered != signer {
t.Errorf("Expected %s, got %s", signer.Hex(), recovered.Hex())
}
// Check cache
if _, ok := cache.Get(header.Hash()); !ok {
t.Error("Expected address to be cached")
}
}
func TestLRU(t *testing.T) {
cache := newLRU(10)
key := common.HexToHash("0x1234")
value := "test"
// Test Get on empty cache
_, ok := cache.Get(key)
if ok {
t.Error("Expected cache miss")
}
// Test Add and Get
cache.Add(key, value)
got, ok := cache.Get(key)
if !ok {
t.Error("Expected cache hit")
}
if got != value {
t.Errorf("Expected %s, got %s", value, got)
}
}

View file

@ -0,0 +1,209 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package rawdb
import (
"encoding/binary"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
)
// XDC-specific database key prefixes
var (
validatorSetPrefix = []byte("xdc-validators-")
epochDataPrefix = []byte("xdc-epoch-")
penaltyPrefix = []byte("xdc-penalty-")
checkpointPrefix = []byte("xdc-checkpoint-")
snapshotPrefix = []byte("xdc-snapshot-")
tradingStatePrefix = []byte("xdcx-trading-")
lendingStatePrefix = []byte("xdcx-lending-")
)
// encodeBlockNumber encodes a block number as big endian uint64
func encodeBlockNumber(number uint64) []byte {
enc := make([]byte, 8)
binary.BigEndian.PutUint64(enc, number)
return enc
}
// validatorSetKey returns the key for validator set at block number
func validatorSetKey(number uint64) []byte {
return append(validatorSetPrefix, encodeBlockNumber(number)...)
}
// epochDataKey returns the key for epoch data
func epochDataKey(epoch uint64) []byte {
return append(epochDataPrefix, encodeBlockNumber(epoch)...)
}
// penaltyKey returns the key for a penalty record
func penaltyKey(validator common.Address, block uint64) []byte {
key := append(penaltyPrefix, validator.Bytes()...)
return append(key, encodeBlockNumber(block)...)
}
// checkpointKey returns the key for a checkpoint
func checkpointKey(number uint64) []byte {
return append(checkpointPrefix, encodeBlockNumber(number)...)
}
// snapshotKey returns the key for a snapshot
func snapshotKey(hash common.Hash) []byte {
return append(snapshotPrefix, hash.Bytes()...)
}
// WriteValidatorSet writes the validator set for a block
func WriteValidatorSet(db ethdb.KeyValueWriter, number uint64, validators []common.Address) error {
data := make([]byte, len(validators)*common.AddressLength)
for i, v := range validators {
copy(data[i*common.AddressLength:], v.Bytes())
}
return db.Put(validatorSetKey(number), data)
}
// ReadValidatorSet reads the validator set for a block
func ReadValidatorSet(db ethdb.KeyValueReader, number uint64) []common.Address {
data, err := db.Get(validatorSetKey(number))
if err != nil || len(data) == 0 {
return nil
}
count := len(data) / common.AddressLength
validators := make([]common.Address, count)
for i := 0; i < count; i++ {
validators[i] = common.BytesToAddress(data[i*common.AddressLength : (i+1)*common.AddressLength])
}
return validators
}
// DeleteValidatorSet deletes the validator set for a block
func DeleteValidatorSet(db ethdb.KeyValueWriter, number uint64) error {
return db.Delete(validatorSetKey(number))
}
// WriteEpochData writes epoch data
func WriteEpochData(db ethdb.KeyValueWriter, epoch uint64, data []byte) error {
return db.Put(epochDataKey(epoch), data)
}
// ReadEpochData reads epoch data
func ReadEpochData(db ethdb.KeyValueReader, epoch uint64) []byte {
data, err := db.Get(epochDataKey(epoch))
if err != nil {
return nil
}
return data
}
// DeleteEpochData deletes epoch data
func DeleteEpochData(db ethdb.KeyValueWriter, epoch uint64) error {
return db.Delete(epochDataKey(epoch))
}
// WritePenalty writes a penalty record
func WritePenalty(db ethdb.KeyValueWriter, validator common.Address, block uint64, amount uint64) error {
data := encodeBlockNumber(amount)
return db.Put(penaltyKey(validator, block), data)
}
// ReadPenalty reads a penalty record
func ReadPenalty(db ethdb.KeyValueReader, validator common.Address, block uint64) uint64 {
data, err := db.Get(penaltyKey(validator, block))
if err != nil || len(data) != 8 {
return 0
}
return binary.BigEndian.Uint64(data)
}
// DeletePenalty deletes a penalty record
func DeletePenalty(db ethdb.KeyValueWriter, validator common.Address, block uint64) error {
return db.Delete(penaltyKey(validator, block))
}
// WriteCheckpoint writes a checkpoint
func WriteCheckpoint(db ethdb.KeyValueWriter, number uint64, hash common.Hash) error {
return db.Put(checkpointKey(number), hash.Bytes())
}
// ReadCheckpoint reads a checkpoint
func ReadCheckpoint(db ethdb.KeyValueReader, number uint64) common.Hash {
data, err := db.Get(checkpointKey(number))
if err != nil || len(data) != common.HashLength {
return common.Hash{}
}
return common.BytesToHash(data)
}
// DeleteCheckpoint deletes a checkpoint
func DeleteCheckpoint(db ethdb.KeyValueWriter, number uint64) error {
return db.Delete(checkpointKey(number))
}
// WriteXDPoSSnapshot writes an XDPoS snapshot
func WriteXDPoSSnapshot(db ethdb.KeyValueWriter, hash common.Hash, data []byte) error {
return db.Put(snapshotKey(hash), data)
}
// ReadXDPoSSnapshot reads an XDPoS snapshot
func ReadXDPoSSnapshot(db ethdb.KeyValueReader, hash common.Hash) []byte {
data, err := db.Get(snapshotKey(hash))
if err != nil {
return nil
}
return data
}
// DeleteXDPoSSnapshot deletes an XDPoS snapshot
func DeleteXDPoSSnapshot(db ethdb.KeyValueWriter, hash common.Hash) error {
return db.Delete(snapshotKey(hash))
}
// HasXDPoSSnapshot checks if a snapshot exists
func HasXDPoSSnapshot(db ethdb.KeyValueReader, hash common.Hash) bool {
has, _ := db.Has(snapshotKey(hash))
return has
}
// XDCx Trading State
// tradingStateKey returns the key for trading state
func tradingStateKey(root common.Hash) []byte {
return append(tradingStatePrefix, root.Bytes()...)
}
// WriteTradingStateRoot writes the trading state root for a block
func WriteTradingStateRoot(db ethdb.KeyValueWriter, blockHash, tradingRoot common.Hash) error {
return db.Put(tradingStateKey(blockHash), tradingRoot.Bytes())
}
// ReadTradingStateRoot reads the trading state root for a block
func ReadTradingStateRoot(db ethdb.KeyValueReader, blockHash common.Hash) common.Hash {
data, err := db.Get(tradingStateKey(blockHash))
if err != nil || len(data) != common.HashLength {
return common.Hash{}
}
return common.BytesToHash(data)
}
// XDCx Lending State
// lendingStateKey returns the key for lending state
func lendingStateKey(root common.Hash) []byte {
return append(lendingStatePrefix, root.Bytes()...)
}
// WriteLendingStateRoot writes the lending state root for a block
func WriteLendingStateRoot(db ethdb.KeyValueWriter, blockHash, lendingRoot common.Hash) error {
return db.Put(lendingStateKey(blockHash), lendingRoot.Bytes())
}
// ReadLendingStateRoot reads the lending state root for a block
func ReadLendingStateRoot(db ethdb.KeyValueReader, blockHash common.Hash) common.Hash {
data, err := db.Get(lendingStateKey(blockHash))
if err != nil || len(data) != common.HashLength {
return common.Hash{}
}
return common.BytesToHash(data)
}

124
docker-compose.yml Normal file
View file

@ -0,0 +1,124 @@
version: '3.8'
services:
xdc-mainnet:
build:
context: .
dockerfile: Dockerfile
container_name: xdc-mainnet
restart: unless-stopped
ports:
- "30303:30303/tcp"
- "30303:30303/udp"
- "8545:8545"
- "8546:8546"
volumes:
- xdc-mainnet-data:/data/xdc
environment:
- NETWORK=mainnet
command: >
--datadir /data/xdc
--networkid 50
--http --http.addr 0.0.0.0 --http.port 8545
--http.api eth,net,web3,xdpos,xdcx
--http.corsdomain *
--ws --ws.addr 0.0.0.0 --ws.port 8546
--ws.api eth,net,web3
--syncmode fast
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8545", "-X", "POST", "-H", "Content-Type: application/json", "--data", '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}']
interval: 30s
timeout: 30s
retries: 3
xdc-testnet:
build:
context: .
dockerfile: Dockerfile.testnet
container_name: xdc-testnet
restart: unless-stopped
ports:
- "30304:30303/tcp"
- "30304:30303/udp"
- "8547:8545"
- "8548:8546"
volumes:
- xdc-testnet-data:/data/xdc
environment:
- NETWORK=testnet
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8545", "-X", "POST", "-H", "Content-Type: application/json", "--data", '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}']
interval: 30s
timeout: 30s
retries: 3
xdc-dev:
build:
context: .
dockerfile: Dockerfile.dev
container_name: xdc-dev
restart: unless-stopped
ports:
- "30305:30303/tcp"
- "30305:30303/udp"
- "8549:8545"
- "8550:8546"
- "40000:40000"
volumes:
- xdc-dev-data:/data/xdc
- ./:/go/src/github.com/ethereum/go-ethereum:cached
environment:
- NETWORK=devnet
xdc-bootnode:
build:
context: .
dockerfile: Dockerfile.bootnode
container_name: xdc-bootnode
restart: unless-stopped
ports:
- "30301:30301/udp"
volumes:
- xdc-bootnode-data:/data/bootnode
command: >
--nodekey /data/bootnode/nodekey
--addr :30301
prometheus:
image: prom/prometheus:latest
container_name: xdc-prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
grafana:
image: grafana/grafana:latest
container_name: xdc-grafana
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
depends_on:
- prometheus
volumes:
xdc-mainnet-data:
xdc-testnet-data:
xdc-dev-data:
xdc-bootnode-data:
prometheus-data:
grafana-data:
networks:
default:
name: xdc-network

266
docs/SYNC.md Normal file
View file

@ -0,0 +1,266 @@
# XDC Network Synchronization Guide
## Overview
This guide covers the various synchronization modes available for the XDC Network and how to optimize sync performance.
## Sync Modes
### 1. Full Sync
Downloads and verifies every block from genesis.
```bash
./XDC --syncmode full
```
**Pros:**
- Complete blockchain history
- Can serve historical data
- Most secure
**Cons:**
- Longest sync time
- Highest disk usage
### 2. Fast Sync (Default)
Downloads blocks and state at a recent checkpoint.
```bash
./XDC --syncmode fast
```
**Pros:**
- Faster initial sync
- Lower disk usage during sync
- Good for most users
**Cons:**
- No historical state
- Depends on checkpoint validity
### 3. Snap Sync
Uses snapshot data for rapid synchronization.
```bash
./XDC --xdc.snapsync --xdc.snapshot.block 50000000
```
**Pros:**
- Fastest sync method
- Minimal bandwidth
- Ideal for new nodes
**Cons:**
- Requires trusted snapshot
- No pre-snapshot history
## XDC-Specific Sync Features
### Checkpoint Verification
XDC uses checkpoints for additional security.
```bash
./XDC --xdc.checkpoint.interval 900
```
### Validator Set Sync
Synchronizes the current validator set.
```go
// Automatically syncs validator set at epoch boundaries
```
### Trading State Sync (XDCx)
Syncs order book state if XDCx is enabled.
```bash
./XDC --xdcx
```
### Lending State Sync
Syncs lending state if XDCxLending is enabled.
```bash
./XDC --xdcxlending
```
## Performance Optimization
### Hardware Requirements
| Component | Minimum | Recommended |
|-----------|---------|-------------|
| CPU | 4 cores | 8+ cores |
| RAM | 16 GB | 32+ GB |
| Disk | 500 GB SSD | 1+ TB NVMe |
| Network | 25 Mbps | 100+ Mbps |
### Configuration Tuning
#### Cache Settings
```bash
./XDC --cache 4096 --cache.gc 50
```
#### Database Settings
```bash
./XDC --db.engine pebble
```
#### Network Settings
```bash
./XDC --maxpeers 50 --maxpendpeers 25
```
## Sync Progress Monitoring
### RPC Methods
```javascript
// Get sync status
eth.syncing
// Get current block
eth.blockNumber
// Get peer count
net.peerCount
```
### Log Monitoring
```bash
tail -f /var/log/xdc/node.log | grep -i sync
```
### Expected Sync Times
| Mode | Mainnet | Testnet |
|------|---------|---------|
| Full | 3-7 days | 1-2 days |
| Fast | 6-24 hours | 2-6 hours |
| Snap | 1-4 hours | 30-60 min |
## Troubleshooting
### Sync Stuck
1. Check peer connections
```bash
./XDC attach --exec "admin.peers.length"
```
2. Add bootnodes
```bash
./XDC --bootnodes "enode://..."
```
3. Clear bad peers
```bash
./XDC attach --exec "admin.peers"
```
### Low Peer Count
1. Check firewall rules
2. Open P2P ports (30303)
3. Enable UPnP
```bash
./XDC --nat extip:YOUR_IP
```
### Disk Space Issues
1. Enable pruning
```bash
./XDC --gcmode archive
```
2. Use snap sync
3. Resize disk
### Memory Issues
1. Reduce cache
```bash
./XDC --cache 2048
```
2. Limit peers
```bash
./XDC --maxpeers 25
```
## Network-Specific Settings
### Mainnet
```bash
./XDC --xdc.mainnet \
--datadir /data/xdc/mainnet \
--syncmode fast
```
### Testnet (Apothem)
```bash
./XDC --xdc.testnet \
--datadir /data/xdc/testnet \
--syncmode fast
```
### Devnet
```bash
./XDC --xdc.devnet \
--datadir /data/xdc/devnet \
--syncmode full
```
## State Pruning
### Enable Pruning
```bash
./XDC --gcmode full --state.gc.percent 25
```
### Manual Pruning
```bash
./XDC snapshot prune-state --datadir /data/xdc
```
## Backup and Recovery
### Create Backup
```bash
# Stop node first
./XDC copydb /data/xdc /backup/xdc
```
### Restore Backup
```bash
cp -r /backup/xdc /data/xdc
./XDC --datadir /data/xdc
```
## Monitoring Tools
### Prometheus Metrics
```bash
./XDC --metrics --metrics.addr 0.0.0.0 --metrics.port 6060
```
### Key Metrics
- `chain_head_block`: Current block number
- `p2p_peers`: Connected peer count
- `chain_sync_mode`: Current sync mode
## Best Practices
1. **Use SSDs**: Much faster than HDDs
2. **Adequate RAM**: Prevents swap usage
3. **Stable Network**: Reduces peer churn
4. **Regular Backups**: Protect against corruption
5. **Monitor Disk**: Prevent space exhaustion
## Further Reading
- [Node Deployment Guide](./DEPLOYMENT.md)
- [Network Configuration](./NETWORK.md)
- [Performance Tuning](./PERFORMANCE.md)

187
docs/XDCx.md Normal file
View file

@ -0,0 +1,187 @@
# XDCx - Decentralized Exchange Protocol
## Overview
XDCx is a decentralized exchange (DEX) protocol built into the XDC Network at the protocol level. It enables trustless trading of XRC20 tokens with on-chain order matching and settlement.
## Key Features
- **On-chain Order Book**: All orders stored on blockchain
- **Atomic Swaps**: Instant settlement without counterparty risk
- **Low Fees**: Transaction costs only
- **Relayer Network**: Decentralized order relay
- **Cross-chain Ready**: Bridge support for multi-chain assets
## Architecture
```
┌─────────────────────────────────────────────────────────┐
│ XDCx │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Order │ │ Trading │ │ Matcher │ │
│ │ Processor │ │ State │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Relayer │ │ API │ │ Events │ │
│ │ Registry │ │ Layer │ │ Emitter │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Order Types
### Limit Orders
```json
{
"baseToken": "0x...",
"quoteToken": "0x...",
"side": "buy",
"price": "1000000000000000000",
"quantity": "5000000000000000000"
}
```
### Market Orders
- Execute at best available price
- Fill-or-kill semantics
## Order Lifecycle
1. **Creation**: User creates and signs order
2. **Submission**: Order submitted to relayer
3. **Matching**: Engine matches against order book
4. **Settlement**: Tokens transferred atomically
5. **Confirmation**: Trade recorded on chain
## Trading Pairs
### Adding a Trading Pair
1. Register through XDCx listing contract
2. Provide initial liquidity
3. Set trading parameters
### Pair Requirements
- Both tokens must be XRC20 compliant
- Minimum liquidity thresholds
- Registration fee
## Relayer System
### Becoming a Relayer
1. Stake required XDC amount
2. Deploy relayer infrastructure
3. Register in relayer contract
4. Start accepting orders
### Relayer Fees
- Configurable by relayer
- Typical: 0.1% maker, 0.2% taker
- Fee split with protocol
## API Reference
### Submit Order
```javascript
xdcx.sendOrder({
baseToken: "0x...",
quoteToken: "0x...",
side: "buy",
type: "limit",
price: "1000000000000000000",
quantity: "5000000000000000000",
nonce: 1,
signature: "0x..."
})
```
### Cancel Order
```javascript
xdcx.cancelOrder(orderId)
```
### Get Order Book
```javascript
xdcx.getOrderBook(baseToken, quoteToken)
```
### Get Trades
```javascript
xdcx.getTrades(baseToken, quoteToken, limit)
```
## Smart Contracts
### XDCx Listing
- Token pair registration
- Listing fee management
- Parameter configuration
### Relayer Registration
- Relayer stake management
- Fee configuration
- Status tracking
## Configuration
### Enable XDCx
```bash
./XDC --xdcx --xdcx.datadir /path/to/xdcx/data
```
### Node Configuration
```toml
[XDCx]
Enabled = true
DataDir = "/data/xdcx"
```
## Events
### Order Events
- `OrderCreated`
- `OrderMatched`
- `OrderCancelled`
- `OrderFilled`
### Trade Events
- `TradeExecuted`
- `TradeSettled`
## Security
1. **Signature Verification**: All orders cryptographically signed
2. **Replay Protection**: Nonce-based order uniqueness
3. **Balance Checks**: Real-time balance verification
4. **Rate Limiting**: Prevent spam attacks
## Best Practices
### For Traders
- Use limit orders for better prices
- Monitor order book depth
- Keep private keys secure
### For Relayers
- Maintain high uptime
- Competitive fee structure
- Fast order matching
## Troubleshooting
### Order Not Matching
- Check balance sufficient
- Verify price format
- Confirm token approval
### Settlement Failed
- Insufficient gas
- Token transfer restriction
- Price moved significantly
## Further Reading
- [XDCx Technical Specification](https://docs.xinfin.org/xdcx)
- [Relayer Setup Guide](https://docs.xinfin.org/relayer)
- [API Documentation](https://docs.xinfin.org/api/xdcx)

243
docs/XDCxLending.md Normal file
View file

@ -0,0 +1,243 @@
# XDCxLending - Decentralized Lending Protocol
## Overview
XDCxLending is a decentralized lending protocol built into the XDC Network. It enables peer-to-peer lending and borrowing of XRC20 tokens with collateralized loans.
## Key Features
- **Collateralized Loans**: Secure lending with over-collateralization
- **Variable Interest Rates**: Market-driven rates
- **Automatic Liquidation**: Protect lenders from defaults
- **Multiple Terms**: Flexible loan durations
- **On-chain Settlement**: Trustless execution
## Architecture
```
┌─────────────────────────────────────────────────────────┐
│ XDCxLending │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Order │ │ Lending │ │ Liquidator │ │
│ │ Processor │ │ State │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Loan │ │ Interest │ │ Collateral │ │
│ │ Manager │ │ Calculator │ │ Manager │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Loan Types
### Borrow Order
Request to borrow tokens at a specific interest rate.
```json
{
"lendingToken": "0x...",
"collateralToken": "0x...",
"side": "borrow",
"interestRate": "500",
"term": "2592000",
"quantity": "1000000000000000000"
}
```
### Lend Order
Offer to lend tokens at a specific interest rate.
```json
{
"lendingToken": "0x...",
"collateralToken": "0x...",
"side": "lend",
"interestRate": "300",
"term": "2592000",
"quantity": "10000000000000000000"
}
```
## Loan Lifecycle
1. **Order Creation**: User creates borrow/lend order
2. **Matching**: Orders matched by interest rate
3. **Collateral Lock**: Borrower's collateral locked
4. **Disbursement**: Lending tokens transferred
5. **Active Loan**: Interest accrues
6. **Repayment**: Borrower repays principal + interest
7. **Collateral Release**: Collateral returned
## Collateral Management
### Collateral Ratio
- Minimum: 150%
- Liquidation: 110%
### Topup
Borrowers can add collateral to avoid liquidation.
### Liquidation
When collateral ratio falls below threshold:
1. Loan marked for liquidation
2. Collateral sold to repay lender
3. Penalty applied to borrower
## Interest Calculation
### Simple Interest
```
Interest = Principal × Rate × Time / (365 × 100)
```
### Example
- Principal: 1000 XDC
- Rate: 5% annual
- Term: 30 days
- Interest: 1000 × 5 × 30 / (365 × 100) = 4.11 XDC
## API Reference
### Create Lending Order
```javascript
xdcxlending.sendLendingOrder({
lendingToken: "0x...",
collateralToken: "0x...",
side: "borrow",
type: "limit",
interestRate: "500",
term: 2592000,
quantity: "1000000000000000000",
nonce: 1,
signature: "0x..."
})
```
### Topup Collateral
```javascript
xdcxlending.topup(loanId, amount)
```
### Repay Loan
```javascript
xdcxlending.repay(loanId)
```
### Get Loan
```javascript
xdcxlending.getLoan(loanId)
```
### Get Order Book
```javascript
xdcxlending.getLendingOrderBook(lendingToken, collateralToken, term)
```
## Smart Contracts
### Lending Registration
- Relayer registration
- Fee configuration
- Term management
### Collateral Contract
- Collateral deposit/withdrawal
- Ratio calculation
- Liquidation trigger
## Configuration
### Enable XDCxLending
```bash
./XDC --xdcxlending --xdcxlending.datadir /path/to/lending/data
```
### Parameters
```go
type Config struct {
DefaultTerm uint64 // 30 days
MinCollateral *big.Int // 150%
LiquidationRate *big.Int // 110%
}
```
## Events
### Lending Events
- `LendingOrderCreated`
- `LendingOrderMatched`
- `LendingOrderCancelled`
### Loan Events
- `LoanCreated`
- `LoanTopup`
- `LoanRepaid`
- `LoanLiquidated`
## Risk Management
### For Lenders
- Collateral protects against default
- Automatic liquidation mechanism
- Interest rate based on risk
### For Borrowers
- Monitor collateral ratio
- Topup before liquidation
- Choose appropriate term
## Security Considerations
1. **Price Oracle**: Collateral valuation
2. **Liquidation Delay**: Time buffer
3. **Rate Limits**: Prevent manipulation
4. **Emergency Pause**: Protocol safety
## Best Practices
### For Borrowers
- Maintain healthy collateral ratio
- Set alerts for low collateral
- Repay before term expiry
### For Lenders
- Diversify lending portfolio
- Choose appropriate interest rates
- Monitor loan status
## Troubleshooting
### Order Not Matching
- Check interest rate competitive
- Verify collateral sufficient
- Confirm token balances
### Liquidation Concerns
- Add collateral (topup)
- Monitor market prices
- Set up alerts
## Example Workflow
### Borrower
1. Deposit collateral (150% of loan)
2. Create borrow order
3. Receive lending tokens
4. Use tokens as needed
5. Repay before expiry
6. Receive collateral back
### Lender
1. Have lending tokens available
2. Create lend order
3. Tokens transferred on match
4. Receive interest during term
5. Get principal + interest on repay
## Further Reading
- [XDCxLending Specification](https://docs.xinfin.org/lending)
- [Risk Parameters](https://docs.xinfin.org/lending/risk)
- [API Documentation](https://docs.xinfin.org/api/lending)

177
docs/XDPoS.md Normal file
View file

@ -0,0 +1,177 @@
# XDPoS Consensus Mechanism
## Overview
XDPoS (XinFin Delegated Proof of Stake) is a consensus mechanism designed for the XDC Network. It combines the benefits of Delegated Proof of Stake (DPoS) with practical Byzantine Fault Tolerance (pBFT) to achieve high throughput, low latency, and energy efficiency.
## Key Features
### 1. Validator Selection
- 108 Masternodes participate in block validation
- Validators are selected based on stake and voting
- Minimum stake requirement: 10,000,000 XDC
- Epoch-based validator set updates (900 blocks)
### 2. Block Production
- 2-second block time
- Round-robin block production among validators
- Block finality achieved through validator signatures
### 3. Consensus Versions
#### XDPoS v1 (Legacy)
- Simple round-robin block production
- Snapshot-based validator management
- Used until block X (network upgrade)
#### XDPoS v2 (Current)
- Enhanced BFT-style consensus
- Gap blocks for improved finality
- Vote aggregation for efficiency
- Penalty mechanism for misbehavior
## Architecture
```
┌─────────────────────────────────────────────────────────┐
│ XDPoS Engine │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Engine V1 │ │ Engine V2 │ │ Snapshot │ │
│ │ (Legacy) │ │ (Current) │ │ Manager │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Validator │ │ Reward │ │ Penalty │ │
│ │ Selection │ │ Calculator │ │ Handler │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Configuration
### Chain Config Parameters
```go
type XDPoSConfig struct {
Period uint64 // Block time in seconds (default: 2)
Epoch uint64 // Epoch length in blocks (default: 900)
Gap uint64 // Gap for snapshots (default: 450)
V2 *XDPoSV2Config
}
```
### Command Line Flags
```bash
--xdpos.rewards Enable block rewards
--xdpos.slashing Enable slashing for misbehavior
--xdpos.epoch Epoch length (default: 900)
--xdpos.gap Gap for snapshots (default: 450)
```
## Validator Operations
### Becoming a Masternode
1. Stake minimum 10,000,000 XDC
2. Run a full node with masternode configuration
3. Register through the validator contract
### Voting
- XDC holders can vote for validators
- Votes are weighted by stake
- Vote changes take effect at epoch boundaries
### Rewards
- Block rewards distributed to validators
- Rewards proportional to stake and participation
- Foundation wallet receives protocol fees
## Penalty System
### Penalties Applied For:
- Missing block production slot
- Double signing
- Invalid block proposals
- Extended downtime
### Penalty Amounts:
- Minor offense: Warning
- Repeated offense: Stake slash
- Major offense: Removal from validator set
## Network Synchronization
### Fast Sync
1. Download block headers
2. Verify validator signatures
3. Download state at checkpoint
4. Continue from checkpoint
### Full Sync
1. Download all blocks
2. Verify all transactions
3. Replay all state changes
## API Reference
### RPC Methods
```javascript
// Get current validators
xdpos.getValidators()
// Get validator info
xdpos.getValidatorInfo(address)
// Get snapshot at block
xdpos.getSnapshot(blockNumber)
// Get rewards for epoch
xdpos.getEpochRewards(epoch)
```
## Smart Contracts
### Validator Contract
- Address: `0x0000000000000000000000000000000000000088`
- Functions: register, vote, withdraw, getValidators
### Block Signer Contract
- Address: `0x0000000000000000000000000000000000000089`
- Functions: sign, getSigners, getRewardPercent
## Security Considerations
1. **Validator Key Security**: Keep masternode keys secure
2. **Network Security**: Use firewalls, restrict P2P ports
3. **Update Regularly**: Apply security patches promptly
4. **Monitor**: Watch for unusual block production patterns
## Troubleshooting
### Common Issues
1. **Not producing blocks**
- Check if node is synced
- Verify validator registration
- Check stake requirement
2. **Sync issues**
- Verify peer connections
- Check network connectivity
- Try different bootnodes
3. **Penalties received**
- Check node uptime
- Verify clock synchronization
- Review logs for errors
## Further Reading
- [XDC Network Documentation](https://docs.xinfin.org)
- [Masternode Setup Guide](https://docs.xinfin.org/docs/masternode)
- [XDPoS Technical Paper](https://xinfin.org/xdpos)

View file

@ -0,0 +1,300 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package downloader
import (
"errors"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
var (
// ErrXDCSnapshotRequired indicates XDC snapshot is needed
ErrXDCSnapshotRequired = errors.New("XDC snapshot required for sync")
// ErrXDCValidatorSyncFailed indicates validator sync failed
ErrXDCValidatorSyncFailed = errors.New("XDC validator set sync failed")
)
// XDCDownloaderConfig holds XDC-specific downloader configuration
type XDCDownloaderConfig struct {
// SnapShotBlockHash is the hash of the snapshot block to sync from
SnapShotBlockHash common.Hash
// SnapShotBlockNumber is the block number of the snapshot
SnapShotBlockNumber uint64
// ValidatorSetURL is the URL to fetch the initial validator set
ValidatorSetURL string
// EnableFastSync enables fast sync mode
EnableFastSync bool
// CheckpointInterval is the interval between checkpoints
CheckpointInterval uint64
}
// DefaultXDCDownloaderConfig returns the default XDC downloader config
func DefaultXDCDownloaderConfig() *XDCDownloaderConfig {
return &XDCDownloaderConfig{
EnableFastSync: true,
CheckpointInterval: 900, // ~15 minutes at 1s blocks
}
}
// XDCSyncState represents the XDC sync state
type XDCSyncState struct {
Mode string
CurrentBlock uint64
HighestBlock uint64
StartingBlock uint64
ValidatorCount int
Syncing bool
Error error
}
// XDCDownloaderExtension extends the downloader with XDC-specific functionality
type XDCDownloaderExtension struct {
downloader *Downloader
config *XDCDownloaderConfig
// Validator set sync
validatorSetSync *ValidatorSetSyncer
// Checkpoint management
checkpoints map[uint64]common.Hash
checkpointsLock sync.RWMutex
// Sync state
syncState XDCSyncState
syncStateLock sync.RWMutex
// Channels
quitCh chan struct{}
}
// NewXDCDownloaderExtension creates a new XDC downloader extension
func NewXDCDownloaderExtension(dl *Downloader, config *XDCDownloaderConfig) *XDCDownloaderExtension {
if config == nil {
config = DefaultXDCDownloaderConfig()
}
ext := &XDCDownloaderExtension{
downloader: dl,
config: config,
checkpoints: make(map[uint64]common.Hash),
validatorSetSync: NewValidatorSetSyncer(),
quitCh: make(chan struct{}),
}
return ext
}
// Start starts the XDC extension
func (x *XDCDownloaderExtension) Start() error {
log.Info("Starting XDC downloader extension")
go x.syncLoop()
return nil
}
// Stop stops the XDC extension
func (x *XDCDownloaderExtension) Stop() {
close(x.quitCh)
}
// syncLoop handles XDC-specific sync operations
func (x *XDCDownloaderExtension) syncLoop() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
x.updateSyncState()
x.checkCheckpoints()
case <-x.quitCh:
return
}
}
}
// updateSyncState updates the sync state
func (x *XDCDownloaderExtension) updateSyncState() {
x.syncStateLock.Lock()
defer x.syncStateLock.Unlock()
// Update sync state from downloader
progress := x.downloader.Progress()
x.syncState.CurrentBlock = progress.CurrentBlock
x.syncState.HighestBlock = progress.HighestBlock
x.syncState.StartingBlock = progress.StartingBlock
x.syncState.Syncing = x.syncState.CurrentBlock < x.syncState.HighestBlock
}
// checkCheckpoints checks for new checkpoints
func (x *XDCDownloaderExtension) checkCheckpoints() {
x.checkpointsLock.Lock()
defer x.checkpointsLock.Unlock()
// Add checkpoint logic here
}
// GetSyncState returns the current sync state
func (x *XDCDownloaderExtension) GetSyncState() XDCSyncState {
x.syncStateLock.RLock()
defer x.syncStateLock.RUnlock()
return x.syncState
}
// AddCheckpoint adds a checkpoint
func (x *XDCDownloaderExtension) AddCheckpoint(blockNumber uint64, hash common.Hash) {
x.checkpointsLock.Lock()
defer x.checkpointsLock.Unlock()
x.checkpoints[blockNumber] = hash
}
// GetCheckpoint returns a checkpoint hash
func (x *XDCDownloaderExtension) GetCheckpoint(blockNumber uint64) (common.Hash, bool) {
x.checkpointsLock.RLock()
defer x.checkpointsLock.RUnlock()
hash, ok := x.checkpoints[blockNumber]
return hash, ok
}
// VerifyCheckpoint verifies a block against a checkpoint
func (x *XDCDownloaderExtension) VerifyCheckpoint(block *types.Block) bool {
hash, ok := x.GetCheckpoint(block.NumberU64())
if !ok {
return true // No checkpoint to verify
}
return block.Hash() == hash
}
// ValidatorSetSyncer handles validator set synchronization
type ValidatorSetSyncer struct {
validators []common.Address
lock sync.RWMutex
}
// NewValidatorSetSyncer creates a new validator set syncer
func NewValidatorSetSyncer() *ValidatorSetSyncer {
return &ValidatorSetSyncer{
validators: make([]common.Address, 0),
}
}
// SetValidators sets the validator set
func (v *ValidatorSetSyncer) SetValidators(validators []common.Address) {
v.lock.Lock()
defer v.lock.Unlock()
v.validators = make([]common.Address, len(validators))
copy(v.validators, validators)
}
// GetValidators returns the validator set
func (v *ValidatorSetSyncer) GetValidators() []common.Address {
v.lock.RLock()
defer v.lock.RUnlock()
result := make([]common.Address, len(v.validators))
copy(result, v.validators)
return result
}
// IsValidator checks if an address is a validator
func (v *ValidatorSetSyncer) IsValidator(addr common.Address) bool {
v.lock.RLock()
defer v.lock.RUnlock()
for _, validator := range v.validators {
if validator == addr {
return true
}
}
return false
}
// SyncValidatorSet syncs the validator set from the network
func (v *ValidatorSetSyncer) SyncValidatorSet() error {
// This would sync from the network or snapshot
return nil
}
// XDCBlockValidator validates blocks with XDC consensus rules
type XDCBlockValidator struct {
validatorSet *ValidatorSetSyncer
}
// NewXDCBlockValidator creates a new block validator
func NewXDCBlockValidator(validatorSet *ValidatorSetSyncer) *XDCBlockValidator {
return &XDCBlockValidator{
validatorSet: validatorSet,
}
}
// ValidateBlock validates a block
func (v *XDCBlockValidator) ValidateBlock(block *types.Block) error {
// Validate coinbase is a validator
if !v.validatorSet.IsValidator(block.Coinbase()) {
return errors.New("block miner is not a validator")
}
// Additional XDPoS validation
if err := v.validateXDPoSSignature(block); err != nil {
return err
}
return nil
}
// validateXDPoSSignature validates the XDPoS signature in the block
func (v *XDCBlockValidator) validateXDPoSSignature(block *types.Block) error {
// Signature validation logic
return nil
}
// ProcessXDCBlock processes XDC-specific block data during sync
func ProcessXDCBlock(block *types.Block) error {
// Process validator set updates
// Process penalty transactions
// Process reward distribution
return nil
}
// XDCStateSync represents XDC state sync
type XDCStateSync struct {
downloader *Downloader
startBlock uint64
targetBlock uint64
progress float64
lock sync.Mutex
}
// NewXDCStateSync creates a new state sync
func NewXDCStateSync(dl *Downloader, start, target uint64) *XDCStateSync {
return &XDCStateSync{
downloader: dl,
startBlock: start,
targetBlock: target,
}
}
// Progress returns the sync progress (0-100)
func (s *XDCStateSync) Progress() float64 {
s.lock.Lock()
defer s.lock.Unlock()
return s.progress
}
// UpdateProgress updates the sync progress
func (s *XDCStateSync) UpdateProgress(currentBlock uint64) {
s.lock.Lock()
defer s.lock.Unlock()
if s.targetBlock <= s.startBlock {
s.progress = 100.0
return
}
total := float64(s.targetBlock - s.startBlock)
current := float64(currentBlock - s.startBlock)
s.progress = (current / total) * 100.0
}

258
eth/downloader/queue_xdc.go Normal file
View file

@ -0,0 +1,258 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package downloader
import (
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// XDCQueue extends the download queue with XDC-specific functionality
type XDCQueue struct {
queue *queue
// XDC-specific queues
validatorSetQueue chan *ValidatorSetRequest
penaltyQueue chan *PenaltyRequest
rewardQueue chan *RewardRequest
// Priority blocks (checkpoints, epoch blocks)
priorityBlocks map[uint64]bool
priorityLock sync.RWMutex
// Epoch tracking
currentEpoch uint64
epochBlocks map[uint64]common.Hash
epochLock sync.RWMutex
}
// ValidatorSetRequest represents a request for validator set data
type ValidatorSetRequest struct {
BlockNumber uint64
BlockHash common.Hash
Validators []common.Address
}
// PenaltyRequest represents a penalty data request
type PenaltyRequest struct {
BlockNumber uint64
Validator common.Address
Amount uint64
}
// RewardRequest represents a reward data request
type RewardRequest struct {
BlockNumber uint64
Validator common.Address
Amount uint64
}
// NewXDCQueue creates a new XDC queue
func NewXDCQueue(q *queue) *XDCQueue {
return &XDCQueue{
queue: q,
validatorSetQueue: make(chan *ValidatorSetRequest, 1000),
penaltyQueue: make(chan *PenaltyRequest, 1000),
rewardQueue: make(chan *RewardRequest, 1000),
priorityBlocks: make(map[uint64]bool),
epochBlocks: make(map[uint64]common.Hash),
}
}
// MarkPriorityBlock marks a block as priority
func (xq *XDCQueue) MarkPriorityBlock(blockNumber uint64) {
xq.priorityLock.Lock()
defer xq.priorityLock.Unlock()
xq.priorityBlocks[blockNumber] = true
log.Debug("Marked priority block", "number", blockNumber)
}
// IsPriorityBlock checks if a block is a priority block
func (xq *XDCQueue) IsPriorityBlock(blockNumber uint64) bool {
xq.priorityLock.RLock()
defer xq.priorityLock.RUnlock()
return xq.priorityBlocks[blockNumber]
}
// UnmarkPriorityBlock removes the priority mark from a block
func (xq *XDCQueue) UnmarkPriorityBlock(blockNumber uint64) {
xq.priorityLock.Lock()
defer xq.priorityLock.Unlock()
delete(xq.priorityBlocks, blockNumber)
}
// SetEpochBlock sets the block hash for an epoch
func (xq *XDCQueue) SetEpochBlock(epoch uint64, hash common.Hash) {
xq.epochLock.Lock()
defer xq.epochLock.Unlock()
xq.epochBlocks[epoch] = hash
}
// GetEpochBlock gets the block hash for an epoch
func (xq *XDCQueue) GetEpochBlock(epoch uint64) (common.Hash, bool) {
xq.epochLock.RLock()
defer xq.epochLock.RUnlock()
hash, ok := xq.epochBlocks[epoch]
return hash, ok
}
// QueueValidatorSetRequest queues a validator set request
func (xq *XDCQueue) QueueValidatorSetRequest(req *ValidatorSetRequest) {
select {
case xq.validatorSetQueue <- req:
default:
log.Warn("Validator set queue full, dropping request")
}
}
// QueuePenaltyRequest queues a penalty request
func (xq *XDCQueue) QueuePenaltyRequest(req *PenaltyRequest) {
select {
case xq.penaltyQueue <- req:
default:
log.Warn("Penalty queue full, dropping request")
}
}
// QueueRewardRequest queues a reward request
func (xq *XDCQueue) QueueRewardRequest(req *RewardRequest) {
select {
case xq.rewardQueue <- req:
default:
log.Warn("Reward queue full, dropping request")
}
}
// ProcessValidatorSetQueue processes validator set requests
func (xq *XDCQueue) ProcessValidatorSetQueue() {
for req := range xq.validatorSetQueue {
xq.processValidatorSetRequest(req)
}
}
// processValidatorSetRequest processes a single validator set request
func (xq *XDCQueue) processValidatorSetRequest(req *ValidatorSetRequest) {
// Process validator set update
log.Debug("Processing validator set request", "block", req.BlockNumber, "validators", len(req.Validators))
}
// PrioritizeEpochBlocks ensures epoch transition blocks are prioritized
func (xq *XDCQueue) PrioritizeEpochBlocks(epochLength uint64, startBlock, endBlock uint64) {
for block := startBlock; block <= endBlock; block++ {
if block%epochLength == 0 {
xq.MarkPriorityBlock(block)
}
}
}
// XDCBlockFetcher handles XDC-specific block fetching
type XDCBlockFetcher struct {
queue *XDCQueue
pendingOps map[common.Hash]*fetchOp
lock sync.Mutex
}
// fetchOp represents a fetch operation
type fetchOp struct {
hash common.Hash
number uint64
priority bool
timestamp int64
}
// NewXDCBlockFetcher creates a new block fetcher
func NewXDCBlockFetcher(queue *XDCQueue) *XDCBlockFetcher {
return &XDCBlockFetcher{
queue: queue,
pendingOps: make(map[common.Hash]*fetchOp),
}
}
// ScheduleFetch schedules a block fetch
func (f *XDCBlockFetcher) ScheduleFetch(hash common.Hash, number uint64) {
f.lock.Lock()
defer f.lock.Unlock()
priority := f.queue.IsPriorityBlock(number)
f.pendingOps[hash] = &fetchOp{
hash: hash,
number: number,
priority: priority,
}
}
// GetPendingCount returns the number of pending fetch operations
func (f *XDCBlockFetcher) GetPendingCount() int {
f.lock.Lock()
defer f.lock.Unlock()
return len(f.pendingOps)
}
// CompleteFetch marks a fetch as complete
func (f *XDCBlockFetcher) CompleteFetch(hash common.Hash, block *types.Block) {
f.lock.Lock()
defer f.lock.Unlock()
op, ok := f.pendingOps[hash]
if !ok {
return
}
delete(f.pendingOps, hash)
if op.priority {
f.queue.UnmarkPriorityBlock(op.number)
}
}
// XDCDataProcessor processes XDC-specific data from downloaded blocks
type XDCDataProcessor struct {
queue *XDCQueue
}
// NewXDCDataProcessor creates a new data processor
func NewXDCDataProcessor(queue *XDCQueue) *XDCDataProcessor {
return &XDCDataProcessor{queue: queue}
}
// ProcessBlock processes XDC data from a block
func (p *XDCDataProcessor) ProcessBlock(block *types.Block) error {
// Extract validator set changes
if err := p.processValidatorSetChanges(block); err != nil {
return err
}
// Extract penalties
if err := p.processPenalties(block); err != nil {
return err
}
// Extract rewards
if err := p.processRewards(block); err != nil {
return err
}
return nil
}
// processValidatorSetChanges processes validator set changes
func (p *XDCDataProcessor) processValidatorSetChanges(block *types.Block) error {
// Parse block extra data for validator set changes
return nil
}
// processPenalties processes penalty transactions
func (p *XDCDataProcessor) processPenalties(block *types.Block) error {
// Parse penalty transactions
return nil
}
// processRewards processes reward distribution
func (p *XDCDataProcessor) processRewards(block *types.Block) error {
// Parse reward distribution
return nil
}

View file

@ -0,0 +1,341 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package downloader
import (
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
)
// XDCStateSync handles XDC-specific state synchronization
type XDCStateSyncer struct {
db ethdb.Database
downloader *Downloader
// Validator state sync
validatorStateRoot common.Hash
validatorStateDone bool
// Consensus state sync
consensusStateRoot common.Hash
consensusStateDone bool
// Trading state sync (XDCx)
tradingStateRoot common.Hash
tradingStateDone bool
// Lending state sync (XDCxlending)
lendingStateRoot common.Hash
lendingStateDone bool
// Progress tracking
progress XDCStateSyncProgress
lock sync.RWMutex
// Channels
quitCh chan struct{}
doneCh chan struct{}
}
// XDCStateSyncProgress represents state sync progress
type XDCStateSyncProgress struct {
AccountsTotal uint64
AccountsDone uint64
StorageTotal uint64
StorageDone uint64
BytesTotal uint64
BytesDone uint64
ValidatorsDone bool
ConsensusDone bool
TradingDone bool
LendingDone bool
StartTime time.Time
EstimatedFinish time.Time
}
// NewXDCStateSyncer creates a new XDC state syncer
func NewXDCStateSyncer(db ethdb.Database, dl *Downloader) *XDCStateSyncer {
return &XDCStateSyncer{
db: db,
downloader: dl,
quitCh: make(chan struct{}),
doneCh: make(chan struct{}),
}
}
// Start starts the state sync
func (s *XDCStateSyncer) Start(stateRoot common.Hash) error {
s.lock.Lock()
s.progress.StartTime = time.Now()
s.lock.Unlock()
log.Info("Starting XDC state sync", "root", stateRoot.Hex())
go s.syncLoop(stateRoot)
return nil
}
// Stop stops the state sync
func (s *XDCStateSyncer) Stop() {
close(s.quitCh)
}
// Wait waits for sync to complete
func (s *XDCStateSyncer) Wait() {
<-s.doneCh
}
// Progress returns the current sync progress
func (s *XDCStateSyncer) Progress() XDCStateSyncProgress {
s.lock.RLock()
defer s.lock.RUnlock()
return s.progress
}
// syncLoop runs the main sync loop
func (s *XDCStateSyncer) syncLoop(stateRoot common.Hash) {
defer close(s.doneCh)
// Sync validator state
if err := s.syncValidatorState(); err != nil {
log.Error("Failed to sync validator state", "error", err)
return
}
// Sync consensus state
if err := s.syncConsensusState(); err != nil {
log.Error("Failed to sync consensus state", "error", err)
return
}
// Sync trading state if XDCx is enabled
if err := s.syncTradingState(); err != nil {
log.Error("Failed to sync trading state", "error", err)
// Non-fatal, continue
}
// Sync lending state if XDCxlending is enabled
if err := s.syncLendingState(); err != nil {
log.Error("Failed to sync lending state", "error", err)
// Non-fatal, continue
}
log.Info("XDC state sync completed")
}
// syncValidatorState syncs the validator state
func (s *XDCStateSyncer) syncValidatorState() error {
log.Debug("Syncing validator state")
// Fetch validator set from network or snapshot
validators := s.fetchValidatorSet()
// Store validators
for _, validator := range validators {
if err := s.storeValidator(validator); err != nil {
return err
}
}
s.lock.Lock()
s.progress.ValidatorsDone = true
s.lock.Unlock()
log.Info("Validator state synced", "count", len(validators))
return nil
}
// syncConsensusState syncs the consensus state
func (s *XDCStateSyncer) syncConsensusState() error {
log.Debug("Syncing consensus state")
// Fetch snapshot data
// Store epoch information
// Store checkpoint data
s.lock.Lock()
s.progress.ConsensusDone = true
s.lock.Unlock()
log.Info("Consensus state synced")
return nil
}
// syncTradingState syncs the XDCx trading state
func (s *XDCStateSyncer) syncTradingState() error {
log.Debug("Syncing trading state")
// Trading state is synced if XDCx is enabled
// This includes order books, pending orders, etc.
s.lock.Lock()
s.progress.TradingDone = true
s.lock.Unlock()
log.Info("Trading state synced")
return nil
}
// syncLendingState syncs the XDCxlending state
func (s *XDCStateSyncer) syncLendingState() error {
log.Debug("Syncing lending state")
// Lending state is synced if XDCxlending is enabled
// This includes lending orders, active loans, etc.
s.lock.Lock()
s.progress.LendingDone = true
s.lock.Unlock()
log.Info("Lending state synced")
return nil
}
// fetchValidatorSet fetches the validator set
func (s *XDCStateSyncer) fetchValidatorSet() []common.Address {
// This would fetch from the network
return make([]common.Address, 0)
}
// storeValidator stores a validator
func (s *XDCStateSyncer) storeValidator(validator common.Address) error {
// Store validator in database
return nil
}
// UpdateProgress updates the sync progress
func (s *XDCStateSyncer) UpdateProgress(accounts, storage, bytes uint64) {
s.lock.Lock()
defer s.lock.Unlock()
s.progress.AccountsDone = accounts
s.progress.StorageDone = storage
s.progress.BytesDone = bytes
// Estimate finish time
if s.progress.BytesTotal > 0 && s.progress.BytesDone > 0 {
elapsed := time.Since(s.progress.StartTime)
rate := float64(s.progress.BytesDone) / elapsed.Seconds()
remaining := float64(s.progress.BytesTotal - s.progress.BytesDone)
if rate > 0 {
s.progress.EstimatedFinish = time.Now().Add(time.Duration(remaining/rate) * time.Second)
}
}
}
// XDCSnapshotSyncer handles snapshot-based sync
type XDCSnapshotSyncer struct {
db ethdb.Database
downloader *Downloader
}
// NewXDCSnapshotSyncer creates a new snapshot syncer
func NewXDCSnapshotSyncer(db ethdb.Database, dl *Downloader) *XDCSnapshotSyncer {
return &XDCSnapshotSyncer{
db: db,
downloader: dl,
}
}
// SyncFromSnapshot syncs state from a snapshot
func (s *XDCSnapshotSyncer) SyncFromSnapshot(snapshotBlock *types.Block) error {
log.Info("Syncing from snapshot", "block", snapshotBlock.NumberU64())
// Download snapshot data
if err := s.downloadSnapshot(snapshotBlock); err != nil {
return err
}
// Verify snapshot integrity
if err := s.verifySnapshot(snapshotBlock); err != nil {
return err
}
// Import snapshot
if err := s.importSnapshot(snapshotBlock); err != nil {
return err
}
return nil
}
// downloadSnapshot downloads a snapshot
func (s *XDCSnapshotSyncer) downloadSnapshot(block *types.Block) error {
// Download snapshot files
return nil
}
// verifySnapshot verifies a snapshot
func (s *XDCSnapshotSyncer) verifySnapshot(block *types.Block) error {
// Verify snapshot integrity and signatures
return nil
}
// importSnapshot imports a snapshot
func (s *XDCSnapshotSyncer) importSnapshot(block *types.Block) error {
// Import snapshot into database
return nil
}
// GetAvailableSnapshots returns available snapshots
func (s *XDCSnapshotSyncer) GetAvailableSnapshots() []SnapshotInfo {
// Query available snapshots
return make([]SnapshotInfo, 0)
}
// SnapshotInfo represents snapshot information
type SnapshotInfo struct {
BlockNumber uint64
BlockHash common.Hash
StateRoot common.Hash
Size uint64
Timestamp uint64
URL string
}
// XDCChainDataWriter writes XDC chain data during sync
type XDCChainDataWriter struct {
db ethdb.Database
}
// NewXDCChainDataWriter creates a new chain data writer
func NewXDCChainDataWriter(db ethdb.Database) *XDCChainDataWriter {
return &XDCChainDataWriter{db: db}
}
// WriteValidatorSet writes the validator set
func (w *XDCChainDataWriter) WriteValidatorSet(blockNumber uint64, validators []common.Address) error {
batch := w.db.NewBatch()
// Write validator set
rawdb.WriteValidatorSet(batch, blockNumber, validators)
return batch.Write()
}
// WriteEpochData writes epoch data
func (w *XDCChainDataWriter) WriteEpochData(epoch uint64, data []byte) error {
batch := w.db.NewBatch()
// Write epoch data
rawdb.WriteEpochData(batch, epoch, data)
return batch.Write()
}
// WritePenalty writes a penalty
func (w *XDCChainDataWriter) WritePenalty(validator common.Address, blockNumber uint64, amount uint64) error {
batch := w.db.NewBatch()
// Write penalty
rawdb.WritePenalty(batch, validator, blockNumber, amount)
return batch.Write()
}

208
eth/ethconfig/config_xdc.go Normal file
View file

@ -0,0 +1,208 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package ethconfig
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// XDCConfig holds XDC-specific configuration
type XDCConfig struct {
// XDPoS configuration
XDPoSRewards bool
XDPoSSlashing bool
XDPoSGap uint64
XDPoSEpoch uint64
XDPoSRewardFraction int
XDPoSFoundationWallet common.Address
// XDCx configuration
XDCxEnabled bool
XDCxDataDir string
XDCxReplicasEnabled bool
// XDCxLending configuration
XDCxLendingEnabled bool
XDCxLendingDataDir string
// Sync configuration
XDCSnapSync bool
XDCSnapShotBlock uint64
XDCCheckpointInterval uint64
// Masternode configuration
MasternodeEnabled bool
MasternodeKey string
MasternodeCoinbase common.Address
}
// DefaultXDCConfig returns the default XDC configuration
func DefaultXDCConfig() *XDCConfig {
return &XDCConfig{
XDPoSRewards: true,
XDPoSSlashing: true,
XDPoSGap: 450,
XDPoSEpoch: 900,
XDPoSRewardFraction: 88,
XDPoSFoundationWallet: common.HexToAddress("0x0000000000000000000000000000000000000068"),
XDCxEnabled: false,
XDCxDataDir: "",
XDCxReplicasEnabled: false,
XDCxLendingEnabled: false,
XDCxLendingDataDir: "",
XDCSnapSync: false,
XDCSnapShotBlock: 0,
XDCCheckpointInterval: 900,
MasternodeEnabled: false,
}
}
// XDCRewardConfig holds reward configuration
type XDCRewardConfig struct {
// Block reward in wei
BlockReward *big.Int
// Foundation reward percentage (out of 100)
FoundationPercent int
// Masternode reward percentage (out of 100)
MasternodePercent int
// Voter reward percentage (out of 100)
VoterPercent int
}
// DefaultRewardConfig returns the default reward configuration
func DefaultRewardConfig() *XDCRewardConfig {
return &XDCRewardConfig{
BlockReward: big.NewInt(0), // XDC doesn't have traditional block rewards
FoundationPercent: 12,
MasternodePercent: 60,
VoterPercent: 28,
}
}
// XDCSyncConfig holds sync-specific configuration
type XDCSyncConfig struct {
// Snapshot URL for initial sync
SnapshotURL string
// Checkpoint block numbers and hashes
Checkpoints map[uint64]common.Hash
// Whether to verify checkpoints
VerifyCheckpoints bool
// Fast sync pivot point
PivotBlock uint64
}
// DefaultSyncConfig returns the default sync configuration
func DefaultSyncConfig() *XDCSyncConfig {
return &XDCSyncConfig{
SnapshotURL: "",
Checkpoints: make(map[uint64]common.Hash),
VerifyCheckpoints: true,
PivotBlock: 0,
}
}
// XDCNetworkConfig holds network-specific configuration
type XDCNetworkConfig struct {
// Network ID
NetworkID uint64
// Network name
NetworkName string
// Chain ID
ChainID *big.Int
// Genesis hash
GenesisHash common.Hash
// Bootnodes
Bootnodes []string
// Static nodes
StaticNodes []string
}
// MainnetConfig returns mainnet configuration
func MainnetConfig() *XDCNetworkConfig {
return &XDCNetworkConfig{
NetworkID: 50,
NetworkName: "xdc-mainnet",
ChainID: big.NewInt(50),
}
}
// TestnetConfig returns Apothem testnet configuration
func TestnetConfig() *XDCNetworkConfig {
return &XDCNetworkConfig{
NetworkID: 51,
NetworkName: "xdc-apothem",
ChainID: big.NewInt(51),
}
}
// DevnetConfig returns devnet configuration
func DevnetConfig() *XDCNetworkConfig {
return &XDCNetworkConfig{
NetworkID: 551,
NetworkName: "xdc-devnet",
ChainID: big.NewInt(551),
}
}
// ValidateXDCConfig validates XDC configuration
func ValidateXDCConfig(cfg *XDCConfig) error {
if cfg.XDPoSEpoch == 0 {
return &configError{"XDPoS epoch cannot be zero"}
}
if cfg.XDPoSGap == 0 {
return &configError{"XDPoS gap cannot be zero"}
}
if cfg.XDPoSGap >= cfg.XDPoSEpoch {
return &configError{"XDPoS gap must be less than epoch"}
}
if cfg.MasternodeEnabled && cfg.MasternodeCoinbase == (common.Address{}) {
return &configError{"Masternode coinbase required when masternode is enabled"}
}
return nil
}
// configError represents a configuration error
type configError struct {
message string
}
func (e *configError) Error() string {
return e.message
}
// IsXDCNetworkID checks if the network ID is an XDC network
func IsXDCNetworkID(networkID uint64) bool {
return networkID == 50 || networkID == 51 || networkID == 551
}
// GetXDCNetworkName returns the network name for a network ID
func GetXDCNetworkName(networkID uint64) string {
switch networkID {
case 50:
return "mainnet"
case 51:
return "apothem"
case 551:
return "devnet"
default:
return "unknown"
}
}

231
eth/tracers/api_xdc.go Normal file
View file

@ -0,0 +1,231 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package tracers
import (
"context"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
)
// XDCTraceConfig holds additional tracer options for XDC specific tracing
type XDCTraceConfig struct {
*TraceConfig
IncludeRewards bool `json:"includeRewards"`
IncludePenalties bool `json:"includePenalties"`
IncludeVotes bool `json:"includeVotes"`
}
// XDCBlockTraceResult represents the result of tracing an XDC block
type XDCBlockTraceResult struct {
Transactions []*TxTraceResult `json:"transactions"`
Rewards []*RewardResult `json:"rewards,omitempty"`
Penalties []*PenaltyResult `json:"penalties,omitempty"`
Votes []*VoteResult `json:"votes,omitempty"`
ValidatorSet []common.Address `json:"validatorSet,omitempty"`
}
// RewardResult represents a validator reward
type RewardResult struct {
Validator common.Address `json:"validator"`
Amount *hexutil.Big `json:"amount"`
Type string `json:"type"`
}
// PenaltyResult represents a validator penalty
type PenaltyResult struct {
Validator common.Address `json:"validator"`
Amount *hexutil.Big `json:"amount"`
Reason string `json:"reason"`
Block uint64 `json:"block"`
}
// VoteResult represents a masternode vote
type VoteResult struct {
Signer common.Address `json:"signer"`
Candidate common.Address `json:"candidate"`
Cap *hexutil.Big `json:"cap"`
Block uint64 `json:"block"`
}
// TxTraceResult represents the result of tracing a transaction
type TxTraceResult struct {
TxHash common.Hash `json:"txHash"`
Result interface{} `json:"result"`
Error string `json:"error,omitempty"`
}
// TraceXDCBlock traces an XDC block with XDC-specific options
func (api *API) TraceXDCBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, config *XDCTraceConfig) (*XDCBlockTraceResult, error) {
block, err := api.blockByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return nil, err
}
if block == nil {
return nil, errors.New("block not found")
}
result := &XDCBlockTraceResult{
Transactions: make([]*TxTraceResult, 0),
}
// Trace transactions
for _, tx := range block.Transactions() {
txResult, err := api.traceTransaction(ctx, tx.Hash(), config.TraceConfig)
if err != nil {
result.Transactions = append(result.Transactions, &TxTraceResult{
TxHash: tx.Hash(),
Error: err.Error(),
})
} else {
result.Transactions = append(result.Transactions, &TxTraceResult{
TxHash: tx.Hash(),
Result: txResult,
})
}
}
// Include rewards if requested
if config != nil && config.IncludeRewards {
rewards := api.getBlockRewards(ctx, block)
result.Rewards = rewards
}
// Include penalties if requested
if config != nil && config.IncludePenalties {
penalties := api.getBlockPenalties(ctx, block)
result.Penalties = penalties
}
// Include votes if requested
if config != nil && config.IncludeVotes {
votes := api.getBlockVotes(ctx, block)
result.Votes = votes
}
return result, nil
}
// TraceXDCTransaction traces a transaction with XDC-specific context
func (api *API) TraceXDCTransaction(ctx context.Context, hash common.Hash, config *XDCTraceConfig) (interface{}, error) {
return api.traceTransaction(ctx, hash, config.TraceConfig)
}
// GetXDCConsensusTrace returns consensus-related trace information
func (api *API) GetXDCConsensusTrace(ctx context.Context, blockNr rpc.BlockNumber) (interface{}, error) {
block, err := api.blockByNumber(ctx, blockNr)
if err != nil {
return nil, err
}
if block == nil {
return nil, errors.New("block not found")
}
header := block.Header()
consensusTrace := map[string]interface{}{
"blockNumber": block.NumberU64(),
"blockHash": block.Hash(),
"miner": header.Coinbase,
"difficulty": header.Difficulty,
"gasUsed": header.GasUsed,
"gasLimit": header.GasLimit,
"timestamp": header.Time,
}
// Add XDPoS specific fields
if len(header.Extra) > 0 {
consensusTrace["extraData"] = hexutil.Bytes(header.Extra)
}
return consensusTrace, nil
}
// getBlockRewards extracts rewards from a block
func (api *API) getBlockRewards(ctx context.Context, block *types.Block) []*RewardResult {
rewards := make([]*RewardResult, 0)
// Block reward to miner
rewards = append(rewards, &RewardResult{
Validator: block.Coinbase(),
Amount: (*hexutil.Big)(big.NewInt(0)), // Calculated based on consensus
Type: "block",
})
return rewards
}
// getBlockPenalties extracts penalties from a block
func (api *API) getBlockPenalties(ctx context.Context, block *types.Block) []*PenaltyResult {
penalties := make([]*PenaltyResult, 0)
// Extract penalties from block transactions or state
return penalties
}
// getBlockVotes extracts votes from a block
func (api *API) getBlockVotes(ctx context.Context, block *types.Block) []*VoteResult {
votes := make([]*VoteResult, 0)
// Extract votes from block transactions
return votes
}
// traceTransaction traces a single transaction
func (api *API) traceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
// This is a placeholder - actual implementation would use the full tracer
return nil, nil
}
// blockByNumber retrieves a block by number
func (api *API) blockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) {
// Implementation depends on backend
return nil, nil
}
// blockByNumberOrHash retrieves a block by number or hash
func (api *API) blockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
// Implementation depends on backend
return nil, nil
}
// XDCInternalTxTracer traces internal transactions in XDC blocks
type XDCInternalTxTracer struct {
backend ethapi.Backend
chainConfig *core.ChainConfig
}
// NewXDCInternalTxTracer creates a new internal transaction tracer
func NewXDCInternalTxTracer(backend ethapi.Backend) *XDCInternalTxTracer {
return &XDCInternalTxTracer{
backend: backend,
}
}
// TraceInternalTransactions traces internal transactions for a block
func (t *XDCInternalTxTracer) TraceInternalTransactions(ctx context.Context, blockNr rpc.BlockNumber) ([]InternalTx, error) {
internalTxs := make([]InternalTx, 0)
// Implementation for tracing internal transactions
return internalTxs, nil
}
// InternalTx represents an internal transaction
type InternalTx struct {
ParentTxHash common.Hash `json:"parentTxHash"`
Type string `json:"type"`
From common.Address `json:"from"`
To common.Address `json:"to"`
Value *hexutil.Big `json:"value"`
Gas uint64 `json:"gas"`
GasUsed uint64 `json:"gasUsed"`
Input hexutil.Bytes `json:"input"`
Output hexutil.Bytes `json:"output"`
Error string `json:"error,omitempty"`
Depth int `json:"depth"`
}

View file

@ -0,0 +1,252 @@
// Copyright 2021 XDC Network
// This file is part of the XDC library.
package native
import (
"encoding/json"
"math/big"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
)
func init() {
tracers.DefaultDirectory.Register("xdcTracer", newXDCTracer, false)
}
// xdcTracer is a native tracer for XDC-specific operations
type xdcTracer struct {
env *vm.EVM
config xdcTracerConfig
gasLimit uint64
interrupt atomic.Bool
reason error
// Results
result *XDCTraceResult
callStack []*xdcCall
}
// xdcTracerConfig holds configuration for the XDC tracer
type xdcTracerConfig struct {
TraceInternalCalls bool `json:"traceInternalCalls"`
TraceStorage bool `json:"traceStorage"`
TraceRewards bool `json:"traceRewards"`
}
// XDCTraceResult holds the trace result
type XDCTraceResult struct {
Type string `json:"type"`
From common.Address `json:"from"`
To common.Address `json:"to"`
Value *big.Int `json:"value"`
Gas uint64 `json:"gas"`
GasUsed uint64 `json:"gasUsed"`
Input []byte `json:"input"`
Output []byte `json:"output"`
Error string `json:"error,omitempty"`
RevertReason string `json:"revertReason,omitempty"`
Calls []*xdcCall `json:"calls,omitempty"`
}
// xdcCall represents an internal call
type xdcCall struct {
Type string `json:"type"`
From common.Address `json:"from"`
To common.Address `json:"to"`
Value *big.Int `json:"value,omitempty"`
Gas uint64 `json:"gas"`
GasUsed uint64 `json:"gasUsed"`
Input []byte `json:"input,omitempty"`
Output []byte `json:"output,omitempty"`
Error string `json:"error,omitempty"`
Calls []*xdcCall `json:"calls,omitempty"`
}
// newXDCTracer creates a new XDC tracer
func newXDCTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
var config xdcTracerConfig
if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err
}
}
return &xdcTracer{
config: config,
result: &XDCTraceResult{},
}, nil
}
// CaptureStart implements the EVMLogger interface
func (t *xdcTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.env = env
t.gasLimit = gas
t.result.From = from
t.result.To = to
t.result.Input = input
t.result.Gas = gas
if value != nil {
t.result.Value = new(big.Int).Set(value)
}
if create {
t.result.Type = "CREATE"
} else {
t.result.Type = "CALL"
}
}
// CaptureEnd implements the EVMLogger interface
func (t *xdcTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.result.Output = output
t.result.GasUsed = gasUsed
if err != nil {
t.result.Error = err.Error()
}
}
// CaptureState implements the EVMLogger interface
func (t *xdcTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
// Not tracking individual opcodes in this tracer
}
// CaptureFault implements the EVMLogger interface
func (t *xdcTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
// Capture fault information
}
// CaptureEnter implements the EVMLogger interface
func (t *xdcTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if !t.config.TraceInternalCalls {
return
}
call := &xdcCall{
Type: typ.String(),
From: from,
To: to,
Gas: gas,
Input: input,
}
if value != nil {
call.Value = new(big.Int).Set(value)
}
t.callStack = append(t.callStack, call)
}
// CaptureExit implements the EVMLogger interface
func (t *xdcTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
if !t.config.TraceInternalCalls || len(t.callStack) == 0 {
return
}
// Pop the last call from the stack
size := len(t.callStack)
call := t.callStack[size-1]
t.callStack = t.callStack[:size-1]
call.Output = output
call.GasUsed = gasUsed
if err != nil {
call.Error = err.Error()
}
// Add to parent or result
if len(t.callStack) > 0 {
parent := t.callStack[len(t.callStack)-1]
parent.Calls = append(parent.Calls, call)
} else {
t.result.Calls = append(t.result.Calls, call)
}
}
// CaptureTxStart implements the EVMLogger interface
func (t *xdcTracer) CaptureTxStart(gasLimit uint64) {
t.gasLimit = gasLimit
}
// CaptureTxEnd implements the EVMLogger interface
func (t *xdcTracer) CaptureTxEnd(restGas uint64) {
t.result.GasUsed = t.gasLimit - restGas
}
// GetResult returns the trace result
func (t *xdcTracer) GetResult() (json.RawMessage, error) {
return json.Marshal(t.result)
}
// Stop terminates the tracer
func (t *xdcTracer) Stop(err error) {
t.reason = err
t.interrupt.Store(true)
}
// XDCRewardTracer traces validator rewards
type XDCRewardTracer struct {
rewards map[common.Address]*big.Int
}
// NewXDCRewardTracer creates a new reward tracer
func NewXDCRewardTracer() *XDCRewardTracer {
return &XDCRewardTracer{
rewards: make(map[common.Address]*big.Int),
}
}
// AddReward adds a reward for a validator
func (t *XDCRewardTracer) AddReward(validator common.Address, amount *big.Int) {
if existing, ok := t.rewards[validator]; ok {
t.rewards[validator] = new(big.Int).Add(existing, amount)
} else {
t.rewards[validator] = new(big.Int).Set(amount)
}
}
// GetRewards returns all rewards
func (t *XDCRewardTracer) GetRewards() map[common.Address]*big.Int {
result := make(map[common.Address]*big.Int)
for addr, amount := range t.rewards {
result[addr] = new(big.Int).Set(amount)
}
return result
}
// XDCPenaltyTracer traces validator penalties
type XDCPenaltyTracer struct {
penalties []XDCPenalty
}
// XDCPenalty represents a penalty
type XDCPenalty struct {
Validator common.Address
Amount *big.Int
Reason string
Block uint64
}
// NewXDCPenaltyTracer creates a new penalty tracer
func NewXDCPenaltyTracer() *XDCPenaltyTracer {
return &XDCPenaltyTracer{
penalties: make([]XDCPenalty, 0),
}
}
// AddPenalty adds a penalty
func (t *XDCPenaltyTracer) AddPenalty(validator common.Address, amount *big.Int, reason string, block uint64) {
t.penalties = append(t.penalties, XDCPenalty{
Validator: validator,
Amount: new(big.Int).Set(amount),
Reason: reason,
Block: block,
})
}
// GetPenalties returns all penalties
func (t *XDCPenaltyTracer) GetPenalties() []XDCPenalty {
return t.penalties
}

52
prometheus.yml Normal file
View file

@ -0,0 +1,52 @@
# Prometheus configuration for XDC Network monitoring
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets: []
rule_files: []
scrape_configs:
# XDC Mainnet Node
- job_name: 'xdc-mainnet'
static_configs:
- targets: ['xdc-mainnet:6060']
metrics_path: /debug/metrics/prometheus
# XDC Testnet Node
- job_name: 'xdc-testnet'
static_configs:
- targets: ['xdc-testnet:6060']
metrics_path: /debug/metrics/prometheus
# XDC Dev Node
- job_name: 'xdc-dev'
static_configs:
- targets: ['xdc-dev:6060']
metrics_path: /debug/metrics/prometheus
# Prometheus self-monitoring
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Recording rules for XDC-specific metrics
recording_rules:
- name: xdc_rules
rules:
# Block production rate
- record: xdc:block_production_rate
expr: rate(chain_head_block[5m])
# Peer count average
- record: xdc:peer_count_avg
expr: avg_over_time(p2p_peers[5m])
# Sync progress
- record: xdc:sync_progress
expr: (chain_head_block / chain_highest_block) * 100

63
scripts/init_genesis.sh Executable file
View file

@ -0,0 +1,63 @@
#!/bin/bash
# XDC Genesis Initialization Script
# Initializes a node with the appropriate genesis file
set -e
# Configuration
DATADIR="${DATADIR:-$HOME/.xdc}"
NETWORK="${NETWORK:-mainnet}"
FORCE="${FORCE:-false}"
# Find script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GENESIS_DIR="$(dirname "$SCRIPT_DIR")/genesis"
# Select genesis file
case "$NETWORK" in
mainnet)
GENESIS_FILE="$GENESIS_DIR/xdc_mainnet.json"
;;
testnet|apothem)
GENESIS_FILE="$GENESIS_DIR/xdc_apothem.json"
;;
devnet)
GENESIS_FILE="$GENESIS_DIR/devnet.json"
;;
*)
echo "Unknown network: $NETWORK"
echo "Supported networks: mainnet, testnet, apothem, devnet"
exit 1
;;
esac
# Check if genesis file exists
if [ ! -f "$GENESIS_FILE" ]; then
echo "Genesis file not found: $GENESIS_FILE"
exit 1
fi
# Check if already initialized
if [ -d "$DATADIR/XDC/chaindata" ] && [ "$FORCE" != "true" ]; then
echo "Node already initialized at $DATADIR"
echo "Use FORCE=true to reinitialize (WARNING: This will delete existing data)"
exit 1
fi
# Remove existing data if force is set
if [ "$FORCE" = "true" ] && [ -d "$DATADIR/XDC" ]; then
echo "Removing existing data..."
rm -rf "$DATADIR/XDC"
fi
# Create data directory
mkdir -p "$DATADIR"
# Initialize genesis
echo "Initializing $NETWORK genesis..."
XDC init --datadir "$DATADIR" "$GENESIS_FILE"
echo "Genesis initialization complete!"
echo "Data directory: $DATADIR"
echo "Network: $NETWORK"

98
scripts/run_xdc_node.sh Executable file
View file

@ -0,0 +1,98 @@
#!/bin/bash
# XDC Node Runner Script
# This script simplifies running an XDC node
set -e
# Configuration
DATADIR="${DATADIR:-$HOME/.xdc}"
NETWORK="${NETWORK:-mainnet}"
SYNCMODE="${SYNCMODE:-fast}"
HTTP_PORT="${HTTP_PORT:-8545}"
WS_PORT="${WS_PORT:-8546}"
P2P_PORT="${P2P_PORT:-30303}"
# Network-specific settings
case "$NETWORK" in
mainnet)
NETWORK_ID=50
CHAIN_ID=50
;;
testnet|apothem)
NETWORK_ID=51
CHAIN_ID=51
;;
devnet)
NETWORK_ID=551
CHAIN_ID=551
;;
*)
echo "Unknown network: $NETWORK"
echo "Supported networks: mainnet, testnet, apothem, devnet"
exit 1
;;
esac
# Create data directory
mkdir -p "$DATADIR"
# Build node command
NODE_CMD="XDC \
--datadir $DATADIR \
--networkid $NETWORK_ID \
--syncmode $SYNCMODE \
--http \
--http.addr 0.0.0.0 \
--http.port $HTTP_PORT \
--http.api eth,net,web3,xdpos \
--http.corsdomain '*' \
--ws \
--ws.addr 0.0.0.0 \
--ws.port $WS_PORT \
--ws.api eth,net,web3 \
--port $P2P_PORT"
# Add optional flags
if [ -n "$MASTERNODE" ]; then
NODE_CMD="$NODE_CMD --masternode"
if [ -n "$COINBASE" ]; then
NODE_CMD="$NODE_CMD --masternode.coinbase $COINBASE"
fi
fi
if [ -n "$VERBOSITY" ]; then
NODE_CMD="$NODE_CMD --verbosity $VERBOSITY"
fi
if [ -n "$BOOTNODES" ]; then
NODE_CMD="$NODE_CMD --bootnodes $BOOTNODES"
fi
# Enable XDCx if requested
if [ "$XDCX_ENABLED" = "true" ]; then
NODE_CMD="$NODE_CMD --xdcx"
fi
# Enable XDCx Lending if requested
if [ "$LENDING_ENABLED" = "true" ]; then
NODE_CMD="$NODE_CMD --xdcxlending"
fi
# Print configuration
echo "==================================="
echo "XDC Node Configuration"
echo "==================================="
echo "Network: $NETWORK"
echo "Network ID: $NETWORK_ID"
echo "Data Dir: $DATADIR"
echo "Sync Mode: $SYNCMODE"
echo "HTTP Port: $HTTP_PORT"
echo "WS Port: $WS_PORT"
echo "P2P Port: $P2P_PORT"
echo "==================================="
# Run node
echo "Starting XDC node..."
exec $NODE_CMD "$@"