diff --git a/internal/quaiapi/quai_api.go b/internal/quaiapi/quai_api.go index 63789a850..8473a292f 100644 --- a/internal/quaiapi/quai_api.go +++ b/internal/quaiapi/quai_api.go @@ -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" @@ -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 {