Skip to content

Commit

Permalink
SDK APIs and other misc. changes (#87)
Browse files Browse the repository at this point in the history
* Add GetTransactionStatus API

* Filter limit orders in GetOpenOrders

* Tx failure log

* Precompile logs as hubble logs

* Fix buildBlock ticker

* Stop spamming logs during bootstrap

* Add trader feed API

* Review fixes

* Review fixes
  • Loading branch information
lumos42 authored and atvanguard committed Aug 2, 2023
1 parent f9fc002 commit 2ba429b
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 22 deletions.
44 changes: 44 additions & 0 deletions eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,50 @@ func (api *EthereumAPI) Coinbase() (common.Address, error) {
return api.Etherbase()
}

func (api *EthereumAPI) GetTransactionStatus(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
currentBlock := api.e.APIBackend.CurrentBlock()
accepted := api.e.APIBackend.LastAcceptedBlock()

// first check if the tx is accepted
lookup := api.e.blockchain.GetTransactionLookup(hash)
if lookup != nil {
return map[string]interface{}{
"status": "ACCEPTED",
"blockNumber": lookup.BlockIndex,
}, nil
}

// iterate backwards from the current block to the accepted block and check if the tx is in any of the blocks
i := 0
for {
// limit backward lookup to 128 blocks
if currentBlock.Hash() == accepted.Hash() || i >= 128 {
return map[string]interface{}{
"status": "NOT_FOUND",
}, nil

}

for _, tx := range currentBlock.Transactions() {
if tx.Hash() == hash {
return map[string]interface{}{
"status": "HEAD_BLOCK",
"blockNumber": currentBlock.NumberU64(),
}, nil
}
}
var err error
currentBlock, err = api.e.APIBackend.BlockByHash(ctx, currentBlock.ParentHash())
if err != nil {
return map[string]interface{}{
"status": "NOT_FOUND",
}, nil
}

i += 1
}
}

// AdminAPI is the collection of Ethereum full node related APIs for node
// administration.
type AdminAPI struct {
Expand Down
8 changes: 4 additions & 4 deletions plugin/evm/block_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ const (

// Minimum amount of time to wait after building a block before attempting to build a block
// a second time without changing the contents of the mempool.
minBlockBuildingRetryDelay = 500 * time.Millisecond
minBlockBuildingRetryDelay = 50 * time.Millisecond

// ticker frequency for calling signalTxsReady
buildTickerDuration = 1 * time.Second
buildTickerDuration = 5 * time.Second
)

type blockBuilder struct {
Expand Down Expand Up @@ -132,6 +132,8 @@ func (b *blockBuilder) markBuilding() {

select {
case b.notifyBuildBlockChan <- commonEng.PendingTxs:
// signal is sent here, so the ticker should be reset
b.buildTicker.Reset(buildTickerDuration)
b.buildSent = true
default:
log.Error("Failed to push PendingTxs notification to the consensus engine.")
Expand Down Expand Up @@ -171,8 +173,6 @@ func (b *blockBuilder) awaitSubmittedTxs() {
select {
case ethTxsEvent := <-txSubmitChan:
log.Trace("New tx detected, trying to generate a block")
// signalTxsReady is being called here, so the ticker should be reset
b.buildTicker.Reset(buildTickerDuration)
b.signalTxsReady()

if b.gossiper != nil && len(ethTxsEvent.Txs) > 0 {
Expand Down
15 changes: 11 additions & 4 deletions plugin/evm/limit_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,23 +107,28 @@ func (lop *limitOrderProcesser) ListenAndProcessTransactions() {
}
}

logHandler := log.Root().GetHandler()
log.Info("ListenAndProcessTransactions - beginning sync", " till block number", lastAcceptedBlockNumber)
JUMP := big.NewInt(3999)
toBlock := utils.BigIntMin(lastAcceptedBlockNumber, big.NewInt(0).Add(fromBlock, JUMP))
for toBlock.Cmp(fromBlock) > 0 {
logs := lop.getLogs(fromBlock, toBlock)
log.Info("ListenAndProcessTransactions - fetched log chunk", "fromBlock", fromBlock.String(), "toBlock", toBlock.String(), "number of logs", len(logs))
// set the log handler to discard logs so that the ProcessEvents doesn't spam the logs
log.Root().SetHandler(log.DiscardHandler())
lop.contractEventProcessor.ProcessEvents(logs)
lop.contractEventProcessor.ProcessAcceptedEvents(logs, true)
lop.memoryDb.Accept(toBlock.Uint64(), 0) // will delete stale orders from the memorydb
log.Root().SetHandler(logHandler)
log.Info("ListenAndProcessTransactions - processed log chunk", "fromBlock", fromBlock.String(), "toBlock", toBlock.String(), "number of logs", len(logs))

fromBlock = fromBlock.Add(toBlock, big.NewInt(1))
toBlock = utils.BigIntMin(lastAcceptedBlockNumber, big.NewInt(0).Add(fromBlock, JUMP))
}
lop.memoryDb.Accept(lastAcceptedBlockNumber.Uint64(), lastAccepted.Time()) // will delete stale orders from the memorydb
log.Root().SetHandler(logHandler)

// needs to be run everytime as long as the db.UpdatePosition uses configService.GetCumulativePremiumFraction
lop.FixBuggySnapshot()
lop.UpdateLastPremiumFractionFromStorage()
}

lop.mu.Unlock()
Expand Down Expand Up @@ -159,6 +164,7 @@ func (lop *limitOrderProcesser) listenAndStoreLimitOrderTransactions() {
lop.mu.Lock()
defer lop.mu.Unlock()
lop.contractEventProcessor.ProcessEvents(logs)
go lop.contractEventProcessor.PushtoTraderFeed(logs, orderbook.ConfirmationLevelHead)
}, orderbook.HandleHubbleFeedLogsPanicMessage, orderbook.HandleHubbleFeedLogsPanicsCounter)
case <-lop.shutdownChan:
return
Expand All @@ -180,6 +186,7 @@ func (lop *limitOrderProcesser) listenAndStoreLimitOrderTransactions() {
lop.mu.Lock()
defer lop.mu.Unlock()
lop.contractEventProcessor.ProcessAcceptedEvents(logs, false)
go lop.contractEventProcessor.PushtoTraderFeed(logs, orderbook.ConfirmationLevelAccepted)
}, orderbook.HandleChainAcceptedLogsPanicMessage, orderbook.HandleChainAcceptedLogsPanicsCounter)
case <-lop.shutdownChan:
return
Expand Down Expand Up @@ -325,7 +332,7 @@ func (lop *limitOrderProcesser) getLogs(fromBlock, toBlock *big.Int) []*types.Lo
return logs
}

func (lop *limitOrderProcesser) FixBuggySnapshot() {
func (lop *limitOrderProcesser) UpdateLastPremiumFractionFromStorage() {
// This is to fix the bug that was causing the LastPremiumFraction to be set to 0 in the snapshot whenever a trader's position was updated
traderMap := lop.memoryDb.GetOrderBookData().TraderMap
count := 0
Expand All @@ -338,7 +345,7 @@ func (lop *limitOrderProcesser) FixBuggySnapshot() {
count++
}
}
log.Info("@@@@ updateLastPremiumFraction - update complete", "count", count, "time taken", time.Since(start))
log.Info("@@@@ UpdateLastPremiumFractionFromStorage - update complete", "count", count, "time taken", time.Since(start))
}

func executeFuncAndRecoverPanic(fn func(), panicMessage string, panicCounter metrics.Counter) {
Expand Down
15 changes: 13 additions & 2 deletions plugin/evm/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

const (
errorKey = "LOG15_ERROR"
timeFormat = "2006-01-02T15:04:05.000-0700"
timeFormat = "2006-01-02T15:04:05.000000-0700"
)

type SubnetEVMLogger struct {
Expand Down Expand Up @@ -102,7 +102,8 @@ func SubnetEVMJSONFormat(alias string) log.Format {
func HubbleTypeHandler(h log.Handler) log.Handler {
return log.FuncHandler(func(r *log.Record) error {
var logType string
if strings.Contains(r.Call.Frame().File, "orderbook") || strings.Contains(r.Call.Frame().File, "limit_order") { // works for evm/limit_order.go and evm/orderbook/*.go
// works for evm/limit_order.go, evm/orderbook/*.go, precompile/contracts/*
if containsAnySubstr(r.Call.Frame().File, []string{"orderbook", "limit_order", "contracts"}) {
logType = "hubble"
} else {
logType = "system"
Expand Down Expand Up @@ -151,3 +152,13 @@ func formatJSONValue(value interface{}) (result interface{}) {
return v
}
}

// containsAnySubstr checks if the string contains any of the specified substrings
func containsAnySubstr(s string, substrings []string) bool {
for _, substr := range substrings {
if strings.Contains(s, substr) {
return true
}
}
return false
}
61 changes: 61 additions & 0 deletions plugin/evm/orderbook/abis/OrderBook.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,49 @@ var OrderBookAbi = []byte(`{"abi": [
"name": "OrderCancelled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "trader",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "orderHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fillAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "price",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "openInterestNotional",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "timestamp",
"type": "uint256"
}
],
"name": "OrderMatched",
"type": "event"
},
{
"anonymous": false,
"inputs": [
Expand Down Expand Up @@ -606,6 +649,24 @@ var OrderBookAbi = []byte(`{"abi": [
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "trader",
"type": "address"
},
{
"internalType": "address",
"name": "authority",
"type": "address"
}
],
"name": "setTradingAuthority",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "settleFunding",
Expand Down
113 changes: 113 additions & 0 deletions plugin/evm/orderbook/contract_events_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/metrics"
"github.com/ava-labs/subnet-evm/plugin/evm/orderbook/abis"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
Expand Down Expand Up @@ -386,6 +387,118 @@ func (cep *ContractEventsProcessor) handleClearingHouseEvent(event *types.Log) {
}
}

type TraderEvent struct {
Trader common.Address
OrderId common.Hash
OrderType string
Removed bool
EventName string
Args map[string]interface{}
BlockNumber *big.Int
BlockStatus BlockConfirmationLevel
Timestamp *big.Int
}

type BlockConfirmationLevel string

const (
ConfirmationLevelHead BlockConfirmationLevel = "head"
ConfirmationLevelAccepted BlockConfirmationLevel = "accepted"
)

func (cep *ContractEventsProcessor) PushtoTraderFeed(events []*types.Log, blockStatus BlockConfirmationLevel) {
for _, event := range events {
removed := event.Removed
args := map[string]interface{}{}
eventName := ""
var orderId common.Hash
var orderType string
var trader common.Address
switch event.Address {
case OrderBookContractAddress:
orderType = "limit"
switch event.Topics[0] {
case cep.orderBookABI.Events["OrderPlaced"].ID:
err := cep.orderBookABI.UnpackIntoMap(args, "OrderPlaced", event.Data)
if err != nil {
log.Error("error in orderBookABI.UnpackIntoMap", "method", "OrderPlaced", "err", err)
continue
}
eventName = "OrderPlaced"
order := LimitOrder{}
order.DecodeFromRawOrder(args["order"])
args["order"] = order.Map()
orderId = event.Topics[2]
trader = getAddressFromTopicHash(event.Topics[1])

case cep.orderBookABI.Events["OrderMatched"].ID:
err := cep.orderBookABI.UnpackIntoMap(args, "OrderMatched", event.Data)
if err != nil {
log.Error("error in orderBookABI.UnpackIntoMap", "method", "OrderMatched", "err", err)
continue
}
eventName = "OrderMatched"
fillAmount := args["fillAmount"].(*big.Int)
openInterestNotional := args["openInterestNotional"].(*big.Int)
price := args["price"].(*big.Int)
args["fillAmount"] = utils.BigIntToFloat(fillAmount, 18)
args["openInterestNotional"] = utils.BigIntToFloat(openInterestNotional, 18)
args["price"] = utils.BigIntToFloat(price, 6)
orderId = event.Topics[2]
trader = getAddressFromTopicHash(event.Topics[1])

case cep.orderBookABI.Events["OrderCancelled"].ID:
err := cep.orderBookABI.UnpackIntoMap(args, "OrderCancelled", event.Data)
if err != nil {
log.Error("error in orderBookABI.UnpackIntoMap", "method", "OrderCancelled", "err", err)
continue
}
eventName = "OrderCancelled"
orderId = event.Topics[2]
trader = getAddressFromTopicHash(event.Topics[1])

default:
continue
}

case IOCOrderBookContractAddress:
orderType = "ioc"
switch event.Topics[0] {
case cep.iocOrderBookABI.Events["OrderPlaced"].ID:
err := cep.iocOrderBookABI.UnpackIntoMap(args, "OrderPlaced", event.Data)
if err != nil {
log.Error("error in iocOrderBookABI.UnpackIntoMap", "method", "OrderPlaced", "err", err)
continue
}
eventName = "OrderPlaced"
order := IOCOrder{}
order.DecodeFromRawOrder(args["order"])
args["order"] = order.Map()
orderId = event.Topics[2]
trader = getAddressFromTopicHash(event.Topics[1])
}
default:
continue
}

timestamp, _ := args["timestamp"]
timestampInt, _ := timestamp.(*big.Int)
traderEvent := TraderEvent{
Trader: trader,
Removed: removed,
EventName: eventName,
Args: args,
BlockNumber: big.NewInt(int64(event.BlockNumber)),
BlockStatus: blockStatus,
OrderId: orderId,
OrderType: orderType,
Timestamp: timestampInt,
}

traderFeed.Send(traderEvent)
}
}

func getAddressFromTopicHash(topicHash common.Hash) common.Address {
address32 := topicHash.String() // address in 32 bytes with 0 padding
return common.HexToAddress(address32[:2] + address32[26:])
Expand Down
Loading

0 comments on commit 2ba429b

Please sign in to comment.