Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion internal/quaiapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,12 @@ func RPCMarshalETHBlock(block *types.WorkObject, inclTx bool, fullTx bool, nodeL
return newRPCTransactionFromBlockHash(block, tx.Hash(), false, nodeLocation), nil
}
}
txs := block.Transactions()
txs := make([]*types.Transaction, 0)
for _, tx := range block.Transactions() {
if tx.Type() == types.QuaiTxType {
txs = append(txs, tx)
}
}
transactions := make([]interface{}, len(txs))
var err error
for i, tx := range txs {
Expand Down
89 changes: 89 additions & 0 deletions internal/quaiapi/quai_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1585,3 +1585,92 @@ func (s *PublicBlockChainQuaiAPI) GetTransactionReceipt(ctx context.Context, has
}
return fields, nil
}

// GetBlockRewardInQuai returns the block reward in Quai for a given block
func (s *PublicBlockChainQuaiAPI) GetBlockRewardInQuai(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
// Get the block
block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
if block == nil || err != nil {
return nil, err
}

// Get the work object header which contains difficulty
woHeader := block.WorkObjectHeader()
if woHeader == nil {
return nil, errors.New("work object header not found")
}

// Get difficulty and exchange rate
difficulty := woHeader.Difficulty()
exchangeRate := block.ExchangeRate()

if difficulty == nil || exchangeRate == nil {
return nil, errors.New("difficulty or exchange rate not available")
}

// Calculate Quai reward using the misc package
quaiReward := misc.CalculateQuaiReward(difficulty, exchangeRate)

return (*hexutil.Big)(quaiReward), nil
}

// GetBlockRewardInQi returns the block reward in Qi for a given block
func (s *PublicBlockChainQuaiAPI) GetBlockRewardInQi(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
// Get the block
block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
if block == nil || err != nil {
return nil, err
}

// Get the work object header which contains difficulty
woHeader := block.WorkObjectHeader()
if woHeader == nil {
return nil, errors.New("work object header not found")
}

// Get difficulty
difficulty := woHeader.Difficulty()
if difficulty == nil {
return nil, errors.New("difficulty not available")
}

// Calculate Qi reward using the misc package
qiReward := misc.CalculateQiReward(woHeader, difficulty)

return (*hexutil.Big)(qiReward), nil
}

// HashesPerQits returns the number of hashes needed to mine the given number of Qits at a given block.
// This is calculated by multiplying the number of Qits by OneOverKqi (hashes per Qit).
//
// Parameters:
// - qiAmount: The number of Qits (note: 1 Qi = 1000 Qit)
// - blockNrOrHash: The block number or hash to query
//
// Returns:
// - The total number of hashes required
//
// Example:
//
// // How many hashes to mine 1 Qi (1000 Qit)?
// hashes := HashesPerQits(ctx, 1000, "latest")
//
// // How many hashes to mine 0.001 Qi (1 Qit)?
// hashes := HashesPerQits(ctx, 1, "latest")
func (s *PublicBlockChainQuaiAPI) HashesPerQits(ctx context.Context, qiAmount hexutil.Big, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
// Handle zero or negative amounts
if qiAmount.ToInt().Sign() <= 0 {
return (*hexutil.Big)(big.NewInt(0)), nil
}
blockNumber, ok := blockNrOrHash.Number()
if !ok {
return nil, errors.New("invalid block number or hash")
}

// Calculate: qiAmount * OneOverKqi(blockNumber)
// OneOverKqi returns hashes per 1 Qit
hashesPerQit := params.OneOverKqi(uint64(blockNumber))
totalHashes := new(big.Int).Mul(qiAmount.ToInt(), hashesPerQit)

return (*hexutil.Big)(totalHashes), nil
}
3 changes: 3 additions & 0 deletions internal/quaiapi/transaction_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ func (args *TransactionArgs) CalculateQiTxGas(qiScalingFactor float64, location
}
}
for i, out := range args.TxOut {
if len(out.Address.Address().Bytes()) != 20 {
return 0, fmt.Errorf("Qi transaction has an output with an invalid address: %s", out.Address.Address().Bytes())
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message formats bytes as a string using %s, which will not display the address in a readable format. Consider using %x for hex formatting or converting to a proper address string representation.

Suggested change
return 0, fmt.Errorf("Qi transaction has an output with an invalid address: %s", out.Address.Address().Bytes())
return 0, fmt.Errorf("Qi transaction has an output with an invalid address: %x", out.Address.Address().Bytes())

Copilot uses AI. Check for mistakes.
}
outs[i] = types.TxOut{
Denomination: uint8(out.Denomination),
Address: out.Address.Address().Bytes(),
Expand Down
22 changes: 16 additions & 6 deletions quai/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,25 @@ func (b *QuaiAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*t

func (b *QuaiAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.WorkObject, error) {
// Pending block is only known by the miner
if number == rpc.PendingBlockNumber {
switch number {
case rpc.PendingBlockNumber:
block := b.quai.core.PendingBlock()
return block, nil
case rpc.SafeBlockNumber:
latestNumber := rpc.BlockNumber(b.quai.core.CurrentHeader().NumberU64(b.NodeCtx()))
safeNumber := rpc.BlockNumber(latestNumber - 5) // TODO: make this a constant param
return b.quai.core.GetBlockByNumber(uint64(safeNumber)), nil
case rpc.FinalizedBlockNumber:
latestNumber := rpc.BlockNumber(b.quai.core.CurrentHeader().NumberU64(b.NodeCtx()))
finalizedNumber := rpc.BlockNumber(latestNumber - 5) // TODO: make this a constant param
Comment on lines +128 to +132
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 5 for calculating safe block number should be defined as a named constant to improve maintainability and make the offset configurable.

Suggested change
safeNumber := rpc.BlockNumber(latestNumber - 5) // TODO: make this a constant param
return b.quai.core.GetBlockByNumber(uint64(safeNumber)), nil
case rpc.FinalizedBlockNumber:
latestNumber := rpc.BlockNumber(b.quai.core.CurrentHeader().NumberU64(b.NodeCtx()))
finalizedNumber := rpc.BlockNumber(latestNumber - 5) // TODO: make this a constant param
safeNumber := rpc.BlockNumber(latestNumber - SafeBlockOffset)
return b.quai.core.GetBlockByNumber(uint64(safeNumber)), nil
case rpc.FinalizedBlockNumber:
latestNumber := rpc.BlockNumber(b.quai.core.CurrentHeader().NumberU64(b.NodeCtx()))
finalizedNumber := rpc.BlockNumber(latestNumber - SafeBlockOffset)

Copilot uses AI. Check for mistakes.
Comment on lines +128 to +132
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 5 for calculating finalized block number should be defined as a named constant. Additionally, safe and finalized blocks having the same offset seems incorrect - finalized blocks should typically be further back than safe blocks.

Suggested change
safeNumber := rpc.BlockNumber(latestNumber - 5) // TODO: make this a constant param
return b.quai.core.GetBlockByNumber(uint64(safeNumber)), nil
case rpc.FinalizedBlockNumber:
latestNumber := rpc.BlockNumber(b.quai.core.CurrentHeader().NumberU64(b.NodeCtx()))
finalizedNumber := rpc.BlockNumber(latestNumber - 5) // TODO: make this a constant param
safeNumber := rpc.BlockNumber(latestNumber - SafeBlockOffset)
return b.quai.core.GetBlockByNumber(uint64(safeNumber)), nil
case rpc.FinalizedBlockNumber:
latestNumber := rpc.BlockNumber(b.quai.core.CurrentHeader().NumberU64(b.NodeCtx()))
finalizedNumber := rpc.BlockNumber(latestNumber - FinalizedBlockOffset)

Copilot uses AI. Check for mistakes.
return b.quai.core.GetBlockByNumber(uint64(finalizedNumber)), nil
case rpc.LatestBlockNumber:
latestNumber := rpc.BlockNumber(b.quai.core.CurrentHeader().NumberU64(b.NodeCtx()))
return b.quai.core.GetBlockByNumber(uint64(latestNumber)), nil
default:
// Otherwise resolve and return the block
return b.quai.core.GetBlockByNumber(uint64(number)), nil
}
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
number = rpc.BlockNumber(b.quai.core.CurrentHeader().NumberU64(b.NodeCtx()))
}
return b.quai.core.GetBlockByNumber(uint64(number)), nil
}

func (b *QuaiAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.WorkObject, error) {
Expand Down
4 changes: 2 additions & 2 deletions quai/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,12 @@ func (s *Quai) APIs() []rpc.API {
}, {
Namespace: "eth",
Version: "1.0",
Service: filters.NewPublicFilterAPI(s.APIBackend, 5*time.Minute, s.maxWsSubs),
Service: filters.NewPublicFilterAPI(s.APIBackend, 5*time.Minute, s.maxWsSubs, "eth"),
Public: true,
}, {
Namespace: "quai",
Version: "1.0",
Service: filters.NewPublicFilterAPI(s.APIBackend, 5*time.Minute, s.maxWsSubs),
Service: filters.NewPublicFilterAPI(s.APIBackend, 5*time.Minute, s.maxWsSubs, "quai"),
Public: true,
}, {
Namespace: "admin",
Expand Down
16 changes: 13 additions & 3 deletions quai/filters/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/dominant-strategies/go-quai/core/types"
"github.com/dominant-strategies/go-quai/ethdb"
"github.com/dominant-strategies/go-quai/event"
"github.com/dominant-strategies/go-quai/internal/quaiapi"
"github.com/dominant-strategies/go-quai/log"
"github.com/dominant-strategies/go-quai/rpc"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -67,10 +68,11 @@ type PublicFilterAPI struct {
timeout time.Duration
subscriptionLimit int
activeSubscriptions int
namespace string
}

// NewPublicFilterAPI returns a new PublicFilterAPI instance.
func NewPublicFilterAPI(backend Backend, timeout time.Duration, subscriptionLimit int) *PublicFilterAPI {
func NewPublicFilterAPI(backend Backend, timeout time.Duration, subscriptionLimit int, namespace string) *PublicFilterAPI {
api := &PublicFilterAPI{
backend: backend,
chainDb: backend.ChainDb(),
Expand All @@ -79,6 +81,7 @@ func NewPublicFilterAPI(backend Backend, timeout time.Duration, subscriptionLimi
timeout: timeout,
subscriptionLimit: subscriptionLimit,
activeSubscriptions: 0,
namespace: namespace,
}
go api.timeoutLoop(timeout)

Expand Down Expand Up @@ -291,8 +294,15 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er
for {
select {
case h := <-headers:
// Marshal the header data
marshalHeader := h.RPCMarshalWorkObject()
// Marshal the header data based on namespace
var marshalHeader interface{}
if api.namespace == "eth" {
// Use ETH-compatible header format for eth namespace
marshalHeader = quaiapi.RPCMarshalETHHeader(h.Header(), h.WorkObjectHeader())
} else {
// Use native Quai format for quai namespace
marshalHeader = h.RPCMarshalWorkObject()
}
notifier.Notify(rpcSub.ID, marshalHeader)
case <-rpcSub.Err():
headersSub.Unsubscribe()
Expand Down
12 changes: 6 additions & 6 deletions quai/filters/filter_system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func TestPendingTxFilter(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase(log.Global)
backend = &testBackend{db: db}
api = NewPublicFilterAPI(backend, deadline, 1)
api = NewPublicFilterAPI(backend, deadline, 1, "quai")

to = common.HexToAddress("0x0094f5ea0ba39494ce83a213fffba74279579268", common.Location{0, 0})

Expand Down Expand Up @@ -316,7 +316,7 @@ func TestLogFilterCreation(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase(log.Global)
backend = &testBackend{db: db}
api = NewPublicFilterAPI(backend, deadline, 1)
api = NewPublicFilterAPI(backend, deadline, 1, "quai")

testCases = []struct {
crit FilterCriteria
Expand Down Expand Up @@ -359,7 +359,7 @@ func TestInvalidLogFilterCreation(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase(log.Global)
backend = &testBackend{db: db}
api = NewPublicFilterAPI(backend, deadline, 1)
api = NewPublicFilterAPI(backend, deadline, 1, "test")
)

// different situations where log filter creation should fail.
Expand All @@ -381,7 +381,7 @@ func TestInvalidGetLogsRequest(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase(log.Global)
backend = &testBackend{db: db}
api = NewPublicFilterAPI(backend, deadline, 1)
api = NewPublicFilterAPI(backend, deadline, 1, "quai")
blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
)

Expand All @@ -406,7 +406,7 @@ func TestLogFilter(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase(log.Global)
backend = &testBackend{db: db}
api = NewPublicFilterAPI(backend, deadline, 1)
api = NewPublicFilterAPI(backend, deadline, 1, "quai")

firstAddr = common.HexToAddressBytes("0x0011111111111111111111111111111111111111")
secondAddr = common.HexToAddressBytes("0x0022222222222222222222222222222222222222")
Expand Down Expand Up @@ -519,7 +519,7 @@ func TestPendingLogsSubscription(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase(log.Global)
backend = &testBackend{db: db}
api = NewPublicFilterAPI(backend, deadline, 1)
api = NewPublicFilterAPI(backend, deadline, 1, "quai")

firstAddr = common.HexToAddressBytes("0x0011111111111111111111111111111111111111")
secondAddr = common.HexToAddressBytes("0x0022222222222222222222222222222222222222")
Expand Down
14 changes: 11 additions & 3 deletions rpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ type jsonWriter interface {
type BlockNumber int64

const (
PendingBlockNumber = BlockNumber(-2)
LatestBlockNumber = BlockNumber(-1)
EarliestBlockNumber = BlockNumber(0)
SafeBlockNumber = BlockNumber(-4) // Safe block number
FinalizedBlockNumber = BlockNumber(-3) // Finalized block number
PendingBlockNumber = BlockNumber(-2) // Pending block number
LatestBlockNumber = BlockNumber(-1) // Latest block number
EarliestBlockNumber = BlockNumber(0) // Earliest block number
)

// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
Expand All @@ -76,6 +78,12 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
}

switch input {
case "safe":
*bn = SafeBlockNumber
return nil
case "finalized":
*bn = FinalizedBlockNumber
return nil
case "earliest":
*bn = EarliestBlockNumber
return nil
Expand Down
Loading