mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
279 lines
9.8 KiB
Go
279 lines
9.8 KiB
Go
package XDCx
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
|
|
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
|
"github.com/XinFinOrg/XDPoSChain/XDCxDAO"
|
|
"github.com/XinFinOrg/XDPoSChain/common"
|
|
"github.com/XinFinOrg/XDPoSChain/common/lru"
|
|
"github.com/XinFinOrg/XDPoSChain/common/prque"
|
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
"github.com/XinFinOrg/XDPoSChain/log"
|
|
"github.com/XinFinOrg/XDPoSChain/node"
|
|
)
|
|
|
|
const (
|
|
defaultCacheLimit = 1024
|
|
MaximumTxMatchSize = 1000
|
|
)
|
|
|
|
var (
|
|
ErrNonceTooHigh = errors.New("nonce too high")
|
|
ErrNonceTooLow = errors.New("nonce too low")
|
|
)
|
|
|
|
type Config struct {
|
|
DataDir string `toml:",omitempty"`
|
|
DBName string `toml:",omitempty"`
|
|
}
|
|
|
|
// DefaultConfig represents (shocker!) the default configuration.
|
|
var DefaultConfig = Config{
|
|
DataDir: "",
|
|
}
|
|
|
|
type XDCX struct {
|
|
// Order related
|
|
db XDCxDAO.XDCXDAO
|
|
Triegc *prque.Prque[int64, common.Hash] // Priority queue mapping block numbers to tries to gc
|
|
StateCache tradingstate.Database // State database to reuse between imports (contains state cache) *XDCx_state.TradingStateDB
|
|
|
|
sdkNode bool
|
|
tokenDecimalCache *lru.Cache[common.Address, *big.Int]
|
|
orderCache *lru.Cache[common.Hash, map[common.Hash]tradingstate.OrderHistoryItem]
|
|
}
|
|
|
|
func NewLDBEngine(cfg *Config) *XDCxDAO.BatchDatabase {
|
|
datadir := cfg.DataDir
|
|
batchDB := XDCxDAO.NewBatchDatabaseWithEncode(datadir, 0)
|
|
return batchDB
|
|
}
|
|
|
|
func New(stack *node.Node, cfg *Config) *XDCX {
|
|
XDCX := &XDCX{
|
|
Triegc: prque.New[int64, common.Hash](nil),
|
|
tokenDecimalCache: lru.NewCache[common.Address, *big.Int](defaultCacheLimit),
|
|
orderCache: lru.NewCache[common.Hash, map[common.Hash]tradingstate.OrderHistoryItem](tradingstate.OrderCacheLimit),
|
|
}
|
|
|
|
// default DBEngine: levelDB
|
|
XDCX.db = NewLDBEngine(cfg)
|
|
|
|
XDCX.StateCache = tradingstate.NewDatabase(XDCX.db)
|
|
|
|
return XDCX
|
|
}
|
|
|
|
func (XDCx *XDCX) GetLevelDB() XDCxDAO.XDCXDAO {
|
|
return XDCx.db
|
|
}
|
|
|
|
func (XDCx *XDCX) ProcessOrderPending(header *types.Header, coinbase common.Address, chain consensus.ChainContext, pending map[common.Address]types.OrderTransactions, statedb *state.StateDB, XDCXstatedb *tradingstate.TradingStateDB) ([]tradingstate.TxDataMatch, map[common.Hash]tradingstate.MatchingResult) {
|
|
txMatches := []tradingstate.TxDataMatch{}
|
|
matchingResults := map[common.Hash]tradingstate.MatchingResult{}
|
|
|
|
txs := types.NewOrderTransactionByNonce(types.OrderTxSigner{}, pending)
|
|
numberTx := 0
|
|
for {
|
|
tx := txs.Peek()
|
|
if tx == nil {
|
|
break
|
|
}
|
|
if numberTx > MaximumTxMatchSize {
|
|
break
|
|
}
|
|
numberTx++
|
|
log.Debug("ProcessOrderPending start", "len", len(pending))
|
|
log.Debug("Get pending orders to process", "address", tx.UserAddress(), "nonce", tx.Nonce())
|
|
V, R, S := tx.Signature()
|
|
|
|
bigstr := V.String()
|
|
n, e := strconv.ParseInt(bigstr, 10, 8)
|
|
if e != nil {
|
|
continue
|
|
}
|
|
|
|
order := &tradingstate.OrderItem{
|
|
Nonce: big.NewInt(int64(tx.Nonce())),
|
|
Quantity: tx.Quantity(),
|
|
Price: tx.Price(),
|
|
ExchangeAddress: tx.ExchangeAddress(),
|
|
UserAddress: tx.UserAddress(),
|
|
BaseToken: tx.BaseToken(),
|
|
QuoteToken: tx.QuoteToken(),
|
|
Status: tx.Status(),
|
|
Side: tx.Side(),
|
|
Type: tx.Type(),
|
|
Hash: tx.OrderHash(),
|
|
OrderID: tx.OrderID(),
|
|
Signature: &tradingstate.Signature{
|
|
V: byte(n),
|
|
R: common.BigToHash(R),
|
|
S: common.BigToHash(S),
|
|
},
|
|
}
|
|
|
|
log.Info("Process order pending", "orderPending", order, "BaseToken", order.BaseToken.Hex(), "QuoteToken", order.QuoteToken)
|
|
originalOrder := &tradingstate.OrderItem{}
|
|
*originalOrder = *order
|
|
originalOrder.Quantity = tradingstate.CloneBigInt(order.Quantity)
|
|
|
|
newTrades, newRejectedOrders, err := XDCx.CommitOrder(header, coinbase, chain, statedb, XDCXstatedb, tradingstate.GetTradingOrderBookHash(order.BaseToken, order.QuoteToken), order)
|
|
|
|
for _, reject := range newRejectedOrders {
|
|
log.Debug("Reject order", "reject", *reject)
|
|
}
|
|
|
|
switch err {
|
|
case ErrNonceTooLow:
|
|
// New head notification data race between the transaction pool and miner, shift
|
|
log.Debug("Skipping order with low nonce", "sender", tx.UserAddress(), "nonce", tx.Nonce())
|
|
txs.Shift()
|
|
continue
|
|
|
|
case ErrNonceTooHigh:
|
|
// Reorg notification data race between the transaction pool and miner, skip account =
|
|
log.Debug("Skipping order account with high nonce", "sender", tx.UserAddress(), "nonce", tx.Nonce())
|
|
txs.Pop()
|
|
continue
|
|
|
|
case nil:
|
|
// everything ok
|
|
txs.Shift()
|
|
|
|
default:
|
|
// Strange error, discard the transaction and get the next in line (note, the
|
|
// nonce-too-high clause will prevent us from executing in vain).
|
|
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
|
txs.Shift()
|
|
continue
|
|
}
|
|
|
|
// orderID has been updated
|
|
originalOrder.OrderID = order.OrderID
|
|
originalOrder.ExtraData = order.ExtraData
|
|
originalOrderValue, err := tradingstate.EncodeBytesItem(originalOrder)
|
|
if err != nil {
|
|
log.Error("Can't encode", "order", originalOrder, "err", err)
|
|
continue
|
|
}
|
|
txMatch := tradingstate.TxDataMatch{
|
|
Order: originalOrderValue,
|
|
}
|
|
txMatches = append(txMatches, txMatch)
|
|
matchingResults[tradingstate.GetMatchingResultCacheKey(order)] = tradingstate.MatchingResult{
|
|
Trades: newTrades,
|
|
Rejects: newRejectedOrders,
|
|
}
|
|
}
|
|
return txMatches, matchingResults
|
|
}
|
|
|
|
// return average price of the given pair in the last epoch
|
|
func (XDCx *XDCX) GetAveragePriceLastEpoch(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, baseToken common.Address, quoteToken common.Address) (*big.Int, error) {
|
|
price := tradingStateDb.GetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
|
|
if price != nil && price.Sign() > 0 {
|
|
log.Debug("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "price", price)
|
|
return price, nil
|
|
} else {
|
|
inversePrice := tradingStateDb.GetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(quoteToken, baseToken))
|
|
log.Debug("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "inversePrice", inversePrice)
|
|
if inversePrice != nil && inversePrice.Sign() > 0 {
|
|
quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, quoteToken)
|
|
if err != nil || quoteTokenDecimal.Sign() == 0 {
|
|
return nil, fmt.Errorf("fail to get tokenDecimal: Token: %v . Err: %v", quoteToken, err)
|
|
}
|
|
baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, baseToken)
|
|
if err != nil || baseTokenDecimal.Sign() == 0 {
|
|
return nil, fmt.Errorf("fail to get tokenDecimal: Token: %v . Err: %v", baseToken, err)
|
|
}
|
|
price = new(big.Int).Mul(baseTokenDecimal, quoteTokenDecimal)
|
|
price = new(big.Int).Div(price, inversePrice)
|
|
log.Debug("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "baseTokenDecimal", baseTokenDecimal, "quoteTokenDecimal", quoteTokenDecimal, "inversePrice", inversePrice)
|
|
return price, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// return tokenQuantity (after convert from XDC to token), tokenPriceInXDC, error
|
|
func (XDCx *XDCX) ConvertXDCToToken(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, token common.Address, quantity *big.Int) (*big.Int, *big.Int, error) {
|
|
if token == common.XDCNativeAddressBinary {
|
|
return quantity, common.BasePrice, nil
|
|
}
|
|
tokenPriceInXDC, err := XDCx.GetAveragePriceLastEpoch(chain, statedb, tradingStateDb, token, common.XDCNativeAddressBinary)
|
|
if err != nil || tokenPriceInXDC == nil || tokenPriceInXDC.Sign() <= 0 {
|
|
return common.Big0, common.Big0, err
|
|
}
|
|
|
|
tokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, token)
|
|
if err != nil || tokenDecimal.Sign() == 0 {
|
|
return common.Big0, common.Big0, fmt.Errorf("fail to get tokenDecimal: Token: %v . Err: %v", token, err)
|
|
}
|
|
tokenQuantity := new(big.Int).Mul(quantity, tokenDecimal)
|
|
tokenQuantity = new(big.Int).Div(tokenQuantity, tokenPriceInXDC)
|
|
return tokenQuantity, tokenPriceInXDC, nil
|
|
}
|
|
|
|
func (XDCx *XDCX) GetTradingState(block *types.Block, author common.Address) (*tradingstate.TradingStateDB, error) {
|
|
root, err := XDCx.GetTradingStateRoot(block, author)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if XDCx.StateCache == nil {
|
|
return nil, errors.New("not initialized XDCx")
|
|
}
|
|
return tradingstate.New(root, XDCx.StateCache)
|
|
}
|
|
func (XDCx *XDCX) GetEmptyTradingState() (*tradingstate.TradingStateDB, error) {
|
|
return tradingstate.New(tradingstate.EmptyRoot, XDCx.StateCache)
|
|
}
|
|
|
|
func (XDCx *XDCX) GetStateCache() tradingstate.Database {
|
|
return XDCx.StateCache
|
|
}
|
|
|
|
func (XDCx *XDCX) HasTradingState(block *types.Block, author common.Address) bool {
|
|
root, err := XDCx.GetTradingStateRoot(block, author)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
_, err = XDCx.StateCache.OpenTrie(root)
|
|
return err == nil
|
|
}
|
|
|
|
func (XDCx *XDCX) GetTriegc() *prque.Prque[int64, common.Hash] {
|
|
return XDCx.Triegc
|
|
}
|
|
|
|
func (XDCx *XDCX) GetTradingStateRoot(block *types.Block, author common.Address) (common.Hash, error) {
|
|
for _, tx := range block.Transactions() {
|
|
to := tx.To()
|
|
if to != nil && *to == common.TradingStateAddrBinary && *tx.From() == author {
|
|
data := tx.Data()
|
|
if len(data) >= 32 {
|
|
return common.BytesToHash(data[:32]), nil
|
|
}
|
|
}
|
|
}
|
|
return tradingstate.EmptyRoot, nil
|
|
}
|
|
|
|
func (XDCx *XDCX) UpdateOrderCache(baseToken, quoteToken common.Address, orderHash common.Hash, txhash common.Hash, lastState tradingstate.OrderHistoryItem) {
|
|
orderCacheAtTxHash, ok := XDCx.orderCache.Get(txhash)
|
|
if !ok || orderCacheAtTxHash == nil {
|
|
orderCacheAtTxHash = make(map[common.Hash]tradingstate.OrderHistoryItem)
|
|
}
|
|
orderKey := tradingstate.GetOrderHistoryKey(baseToken, quoteToken, orderHash)
|
|
_, ok = orderCacheAtTxHash[orderKey]
|
|
if !ok {
|
|
orderCacheAtTxHash[orderKey] = lastState
|
|
}
|
|
XDCx.orderCache.Add(txhash, orderCacheAtTxHash)
|
|
}
|