Skip to content

Commit

Permalink
Added quai_getOutpointDeltasForAddressesInRange API
Browse files Browse the repository at this point in the history
  • Loading branch information
jdowning100 committed Nov 12, 2024
1 parent e0a9f36 commit 44adfd4
Showing 1 changed file with 194 additions and 0 deletions.
194 changes: 194 additions & 0 deletions internal/quaiapi/quai_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/crypto"
"github.com/dominant-strategies/go-quai/log"
"github.com/dominant-strategies/go-quai/metrics_config"
"github.com/dominant-strategies/go-quai/params"
"github.com/dominant-strategies/go-quai/rpc"
"github.com/dominant-strategies/go-quai/trie"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -301,6 +302,199 @@ func (s *PublicBlockChainQuaiAPI) GetOutPointsByAddressAndRange(ctx context.Cont
return txHashToOutpointsJson, nil
}

func (s *PublicBlockChainQuaiAPI) GetOutpointDeltasForAddressesInRange(ctx context.Context, addresses []common.Address, from, to common.Hash) (map[string]map[string]map[string][]interface{}, error) {
if s.b.NodeCtx() != common.ZONE_CTX {
return nil, errors.New("getOutpointDeltasForAddressesInRange can only be called in a zone chain")
}
nodeCtx := common.ZONE_CTX
if len(addresses) == 0 {
return nil, fmt.Errorf("addresses cannot be empty")
}
blockFrom := s.b.BlockOrCandidateByHash(from)
if blockFrom == nil {
return nil, fmt.Errorf("block %s not found", from.Hex())
}
blockTo := s.b.BlockOrCandidateByHash(to)
if blockTo == nil {
return nil, fmt.Errorf("block %s not found", to.Hex())
}
if blockFrom.NumberU64(nodeCtx) > blockTo.NumberU64(nodeCtx) {
return nil, fmt.Errorf("from block number is greater than to block number")
}
if uint32(blockTo.NumberU64(nodeCtx))-uint32(blockFrom.NumberU64(nodeCtx)) > maxOutpointsRange {
return nil, fmt.Errorf("range is too large, max range is %d", maxOutpointsRange)
}
blockFromCanonical := true
if blockFrom.Hash() != rawdb.ReadCanonicalHash(s.b.Database(), blockFrom.NumberU64(nodeCtx)) {
blockFromCanonical = false
}
blockToCanonical := true
if blockTo.Hash() != rawdb.ReadCanonicalHash(s.b.Database(), blockTo.NumberU64(nodeCtx)) {
blockToCanonical = false
}
addressMap := make(map[common.AddressBytes]struct{})
for _, address := range addresses {
addressMap[address.Bytes20()] = struct{}{}
}
addressToCreatedDeletedToTxHashToOutputs := make(map[string]map[string]map[string][]interface{})
for _, address := range addresses {
addressToCreatedDeletedToTxHashToOutputs[address.String()] = make(map[string]map[string][]interface{})
addressToCreatedDeletedToTxHashToOutputs[address.String()]["created"] = make(map[string][]interface{})
addressToCreatedDeletedToTxHashToOutputs[address.String()]["deleted"] = make(map[string][]interface{})
}
if !blockFromCanonical || !blockToCanonical {
commonAncestor, err := rawdb.FindCommonAncestor(s.b.Database(), blockFrom, blockTo, nodeCtx)
if err != nil {
return nil, err
}
if commonAncestor == nil {
return nil, fmt.Errorf("no common ancestor found")
}
blockFrom = commonAncestor
}
currentBlock := blockTo
for currentBlock.Hash() != blockFrom.Hash() {
// Grab spent UTXOs for this block
// Eventually spent UTXOs should be pruned, so this data might not be available
sutxos, err := rawdb.ReadSpentUTXOs(s.b.Database(), currentBlock.Hash())
if err != nil {
return nil, err
}
trimmedUtxos, err := rawdb.ReadTrimmedUTXOs(s.b.Database(), currentBlock.Hash())
if err != nil {
return nil, err
}
sutxos = append(sutxos, trimmedUtxos...)
for _, sutxo := range sutxos {
if _, ok := addressMap[common.AddressBytes(sutxo.Address)]; ok {
lock := big.NewInt(0)
if sutxo.Lock != nil {
lock = sutxo.Lock
}
addressToCreatedDeletedToTxHashToOutputs[common.AddressBytes(sutxo.Address).String()]["deleted"][sutxo.TxHash.String()] =
append(addressToCreatedDeletedToTxHashToOutputs[common.AddressBytes(sutxo.Address).String()]["deleted"][sutxo.TxHash.String()], map[string]interface{}{
"index": hexutil.Uint64(sutxo.Index),
"denomination": hexutil.Uint64(sutxo.Denomination),
"lock": hexutil.Big(*lock),
})
}
}

for _, tx := range currentBlock.Transactions() {
if tx.Type() == types.QiTxType {
for i, out := range tx.TxOut() {
if common.BytesToAddress(out.Address, common.Location{0, 0}).IsInQuaiLedgerScope() {
// This is a conversion output
continue
}
if _, ok := addressMap[common.AddressBytes(out.Address)]; !ok {
continue
}
lock := big.NewInt(0)
if out.Lock != nil {
lock = out.Lock
}
addressToCreatedDeletedToTxHashToOutputs[common.AddressBytes(out.Address).String()]["created"][tx.Hash().String()] =
append(addressToCreatedDeletedToTxHashToOutputs[common.AddressBytes(out.Address).String()]["created"][tx.Hash().String()], map[string]interface{}{
"index": hexutil.Uint64(i),
"denomination": hexutil.Uint64(out.Denomination),
"lock": hexutil.Big(*lock),
})
}
} else if tx.Type() == types.ExternalTxType && tx.EtxType() == types.CoinbaseType && tx.To().IsInQiLedgerScope() {
if len(tx.Data()) == 0 {
continue
}
if _, ok := addressMap[common.AddressBytes(tx.To().Bytes20())]; !ok {
continue
}
lockupByte := tx.Data()[0]
// After the BigSporkFork the minimum conversion period changes to 7200 blocks
var lockup *big.Int
if lockupByte == 0 {
if currentBlock.NumberU64(nodeCtx) < params.GoldenAgeForkNumberV1 {
lockup = new(big.Int).SetUint64(params.OldConversionLockPeriod)
} else {
lockup = new(big.Int).SetUint64(params.NewConversionLockPeriod)
}
} else {
lockup = new(big.Int).SetUint64(params.LockupByteToBlockDepth[lockupByte])
}
lockup.Add(lockup, currentBlock.Number(nodeCtx))
value := params.CalculateCoinbaseValueWithLockup(tx.Value(), lockupByte)
denominations := misc.FindMinDenominations(value)
outputIndex := uint16(0)
// Iterate over the denominations in descending order
for denomination := types.MaxDenomination; denomination >= 0; denomination-- {
// If the denomination count is zero, skip it
if denominations[uint8(denomination)] == 0 {
continue
}
for j := uint64(0); j < denominations[uint8(denomination)]; j++ {
if outputIndex >= types.MaxOutputIndex {
// No more gas, the rest of the denominations are lost but the tx is still valid
break
}

addressToCreatedDeletedToTxHashToOutputs[tx.To().String()]["created"][tx.Hash().String()] =
append(addressToCreatedDeletedToTxHashToOutputs[tx.To().String()]["created"][tx.Hash().String()], map[string]interface{}{
"index": hexutil.Uint64(outputIndex),
"denomination": hexutil.Uint64(uint8(denomination)),
"lock": hexutil.Big(*lockup),
})
outputIndex++
}
}
} else if tx.Type() == types.ExternalTxType && tx.EtxType() == types.ConversionType && tx.To().IsInQiLedgerScope() {
if _, ok := addressMap[common.AddressBytes(tx.To().Bytes20())]; !ok {
continue
}
var lockup *big.Int
if currentBlock.NumberU64(nodeCtx) < params.GoldenAgeForkNumberV1 {
lockup = new(big.Int).SetUint64(params.OldConversionLockPeriod)
} else {
lockup = new(big.Int).SetUint64(params.NewConversionLockPeriod)
}
lockup.Add(lockup, currentBlock.Number(nodeCtx))
value := tx.Value()
txGas := tx.Gas()
if txGas < params.TxGas {
continue
}
txGas -= params.TxGas
denominations := misc.FindMinDenominations(value)
outputIndex := uint16(0)
// Iterate over the denominations in descending order
for denomination := types.MaxDenomination; denomination >= 0; denomination-- {
// If the denomination count is zero, skip it
if denominations[uint8(denomination)] == 0 {
continue
}
for j := uint64(0); j < denominations[uint8(denomination)]; j++ {
if txGas < params.CallValueTransferGas || outputIndex >= types.MaxOutputIndex {
// No more gas, the rest of the denominations are lost but the tx is still valid
break
}
txGas -= params.CallValueTransferGas
// the ETX hash is guaranteed to be unique

addressToCreatedDeletedToTxHashToOutputs[tx.To().String()]["created"][tx.Hash().String()] =
append(addressToCreatedDeletedToTxHashToOutputs[tx.To().String()]["created"][tx.Hash().String()], map[string]interface{}{
"index": hexutil.Uint64(outputIndex),
"denomination": hexutil.Uint64(uint8(denomination)),
"lock": hexutil.Big(*lockup),
})

outputIndex++
}
}
}
}
currentBlock = s.b.BlockOrCandidateByHash(currentBlock.ParentHash(nodeCtx))
}
return addressToCreatedDeletedToTxHashToOutputs, nil
}

func (s *PublicBlockChainQuaiAPI) GetUTXO(ctx context.Context, txHash common.Hash, index hexutil.Uint64) (map[string]interface{}, error) {
utxo := rawdb.GetUTXO(s.b.Database(), txHash, uint16(index))
if utxo == nil {
Expand Down

0 comments on commit 44adfd4

Please sign in to comment.