github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/identifiers/resolve.go (about)

     1  // Copyright 2021 The TrueBlocks Authors. All rights reserved.
     2  // Use of this source code is governed by a license that can
     3  // be found in the LICENSE file.
     4  
     5  package identifiers
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/tslib"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils"
    16  	"github.com/bykof/gostradamus"
    17  )
    18  
    19  // ResolveBlocks resolves a list of identifiers to a list of blocks (excluding the last block)
    20  func (id *Identifier) ResolveBlocks(chain string) ([]base.Blknum, error) {
    21  	bound, err := id.getBounds(chain)
    22  	if err != nil {
    23  		return []base.Blknum{}, err
    24  	}
    25  
    26  	blocks := []base.Blknum{}
    27  	current, end := bound.First, bound.Last
    28  	for current < end {
    29  		blocks = append(blocks, current)
    30  		current, err = id.nextBlock(chain, current)
    31  		if err != nil {
    32  			return []base.Blknum{}, err
    33  		}
    34  	}
    35  	return blocks, nil
    36  }
    37  
    38  // GetBounds returns the earliest and latest blocks for an array of identifiers
    39  func GetBounds(chain string, ids *[]Identifier) (ret base.BlockRange, err error) {
    40  	ret = base.BlockRange{
    41  		First: base.NOPOSN,
    42  		Last:  0,
    43  	}
    44  
    45  	for _, id := range *ids {
    46  		idRange, err := id.getBounds(chain)
    47  		if err != nil {
    48  			return ret, err
    49  		}
    50  		ret.First = base.Min(ret.First, idRange.First)
    51  		ret.Last = base.Max(ret.Last, idRange.Last)
    52  	}
    53  
    54  	return ret, nil
    55  }
    56  
    57  // getBounds returns the earliest and latest blocks for the identifier
    58  func (id *Identifier) getBounds(chain string) (ret base.BlockRange, err error) {
    59  	ret.First = id.Start.resolvePoint(chain)
    60  	switch id.ModifierType {
    61  	case Period:
    62  		ret.First, _ = snapBnToPeriod(ret.First, chain, id.Modifier.Period)
    63  	default:
    64  		// do nothing
    65  	}
    66  	ret.Last = id.End.resolvePoint(chain)
    67  	if ret.Last == base.NOPOSN || ret.Last == 0 {
    68  		ret.Last = ret.First + 1
    69  	}
    70  
    71  	return ret, nil
    72  }
    73  
    74  func snapBnToPeriod(bn base.Blknum, chain, period string) (base.Blknum, error) {
    75  	conn := rpc.TempConnection(chain)
    76  
    77  	dt, err := tslib.FromBnToDate(chain, bn)
    78  	if err != nil {
    79  		return bn, err
    80  	}
    81  
    82  	// within five minutes of the period, snap to the future, otherwise snap to the past
    83  	switch period {
    84  	case "hourly":
    85  		dt = dt.ShiftMinutes(5)
    86  		dt = dt.FloorHour()
    87  	case "daily":
    88  		dt = dt.ShiftMinutes(5)
    89  		dt = dt.FloorDay()
    90  	case "weekly":
    91  		dt = dt.ShiftMinutes(5)
    92  		dt = dt.FloorWeek()
    93  		dt = dt.ShiftDays(-1).FloorDay() // returns Monday -- we want Sunday
    94  	case "monthly":
    95  		dt = dt.ShiftMinutes(5)
    96  		dt = dt.FloorMonth()
    97  	case "quarterly": // we assume here that the data is already on the quarter
    98  		dt = dt.ShiftMinutes(5)
    99  		dt = dt.FloorMonth()
   100  		for {
   101  			if dt.Month() == 1 || dt.Month() == 4 || dt.Month() == 7 || dt.Month() == 10 {
   102  				break
   103  			}
   104  			dt = dt.ShiftMonths(1)
   105  			dt = dt.FloorMonth()
   106  		}
   107  	case "annually":
   108  		dt = dt.ShiftMinutes(5)
   109  		dt = dt.FloorYear()
   110  	}
   111  
   112  	zeroTs := conn.GetBlockTimestamp(0)
   113  	firstDate := gostradamus.FromUnixTimestamp(zeroTs.Int64())
   114  	if dt.Time().Before(firstDate.Time()) {
   115  		dt = firstDate
   116  	}
   117  
   118  	ts := dt.UnixTimestamp()
   119  	return tslib.FromTsToBn(chain, base.Timestamp(ts))
   120  }
   121  
   122  func (id *Identifier) nextBlock(chain string, current base.Blknum) (base.Blknum, error) {
   123  	bn := current
   124  
   125  	if id.ModifierType == Step {
   126  		bn = bn + base.Blknum(id.Modifier.Step)
   127  
   128  	} else if id.ModifierType == Period {
   129  		dt, err := tslib.FromBnToDate(chain, bn)
   130  		if err != nil {
   131  			return bn, err
   132  		} else {
   133  			switch id.Modifier.Period {
   134  			case "hourly":
   135  				dt = dt.ShiftMinutes(5)
   136  				dt = dt.ShiftHours(1)
   137  				dt = dt.FloorHour()
   138  			case "daily":
   139  				dt = dt.ShiftMinutes(5)
   140  				dt = dt.ShiftDays(1)
   141  				dt = dt.FloorDay()
   142  			case "weekly":
   143  				dt = dt.ShiftDays(1)
   144  				dt = dt.ShiftMinutes(5)
   145  				dt = dt.ShiftWeeks(1)
   146  				dt = dt.FloorWeek() // returns Monday -- we want Sunday
   147  				dt = dt.ShiftDays(-1).FloorDay()
   148  			case "monthly":
   149  				dt = dt.ShiftMinutes(5)
   150  				dt = dt.ShiftMonths(1)
   151  				dt = dt.FloorMonth()
   152  			case "quarterly":
   153  				dt = dt.ShiftMinutes(5)
   154  				dt = dt.ShiftMonths(3)
   155  				dt = dt.FloorMonth()
   156  			case "annually":
   157  				dt = dt.ShiftMinutes(5)
   158  				dt = dt.ShiftYears(1)
   159  				dt = dt.FloorYear()
   160  			default:
   161  				// should not happen
   162  			}
   163  
   164  			ts := dt.UnixTimestamp()
   165  			bn, err = tslib.FromTsToBn(chain, base.Timestamp(ts))
   166  			if err != nil {
   167  				return bn, err
   168  			}
   169  			if bn == current {
   170  				// might happen if the block spans the period
   171  				bn++ // ensure at least one block's advancement
   172  			}
   173  		}
   174  
   175  	} else {
   176  		bn = bn + 1
   177  	}
   178  
   179  	return bn, nil
   180  }
   181  
   182  func (p *Point) resolvePoint(chain string) base.Blknum {
   183  	conn := rpc.TempConnection(chain)
   184  
   185  	var bn base.Blknum
   186  	if p.Hash != "" {
   187  		bn, _ = conn.GetBlockNumberByHash(p.Hash)
   188  	} else if p.Date != "" {
   189  		bn, _ = tslib.FromDateToBn(chain, p.Date)
   190  	} else if p.Special != "" {
   191  		bn, _ = tslib.FromNameToBn(chain, p.Special)
   192  	} else if p.Number >= utils.EarliestEvmTs {
   193  		var err error
   194  		bn, err = tslib.FromTsToBn(chain, base.Timestamp(p.Number))
   195  		if err == tslib.ErrInTheFuture {
   196  			latest := conn.GetLatestBlockNumber()
   197  			tsFuture := conn.GetBlockTimestamp(latest)
   198  			secs := base.Blknum(tsFuture - base.Timestamp(p.Number))
   199  			blks := (secs / 13)
   200  			bn = latest + blks
   201  		}
   202  	} else {
   203  		bn = base.Blknum(p.Number)
   204  	}
   205  	return bn
   206  }
   207  
   208  func (id *Identifier) ResolveTxs(chain string) ([]types.Appearance, error) {
   209  	conn := rpc.TempConnection(chain)
   210  	txs := []types.Appearance{}
   211  
   212  	if id.StartType == BlockNumber {
   213  		if id.Modifier.Period == "all" {
   214  			cnt, err := conn.GetTransactionCountInBlock(base.Blknum(id.Start.Number))
   215  			if err != nil {
   216  				return txs, err
   217  			}
   218  			for i := uint32(0); i < uint32(cnt); i++ {
   219  				app := types.Appearance{BlockNumber: uint32(id.Start.Number), TransactionIndex: i}
   220  				txs = append(txs, app)
   221  			}
   222  			return txs, nil
   223  		}
   224  
   225  		if id.EndType == TransactionIndex {
   226  			app := types.Appearance{BlockNumber: uint32(id.Start.Number), TransactionIndex: uint32(id.End.Number)}
   227  			return append(txs, app), nil
   228  		}
   229  
   230  		msg := fmt.Sprintf("unknown transaction type: %s", id)
   231  		return txs, errors.New(msg)
   232  	}
   233  
   234  	if id.StartType == BlockHash && id.EndType == TransactionIndex {
   235  		if id.Modifier.Period == "all" {
   236  			cnt, err := conn.GetTransactionCountInBlock(base.Blknum(id.Start.resolvePoint(chain)))
   237  			if err != nil {
   238  				return txs, err
   239  			}
   240  			for i := uint32(0); i < uint32(cnt); i++ {
   241  				app := types.Appearance{BlockNumber: uint32(id.Start.resolvePoint(chain)), TransactionIndex: i}
   242  				txs = append(txs, app)
   243  			}
   244  			return txs, nil
   245  		}
   246  
   247  		app := types.Appearance{BlockNumber: uint32(id.Start.resolvePoint(chain)), TransactionIndex: uint32(id.End.Number)}
   248  		return append(txs, app), nil
   249  	}
   250  
   251  	if id.StartType == TransactionHash {
   252  		app, err := conn.GetTransactionAppByHash(id.Start.Hash)
   253  		return append(txs, app), err
   254  	}
   255  
   256  	app := types.Appearance{BlockNumber: uint32(0), TransactionIndex: uint32(0)}
   257  	msg := fmt.Sprintf("unknown transaction type %s", id)
   258  	return append(txs, app), errors.New(msg)
   259  }
   260  
   261  /*
   262  // TODO: next and previous skip markers - search for this to find other things related
   263  bool wrangleTxId(string_q& argOut, string_q& errorMsg) {
   264      if (contains(argOut, "0x"))
   265          return true;
   266  
   267      // valid args are 'latest', 'bn.txid', 'bn.txid.next', or 'bn.txid.prev'
   268      replaceReverse(argOut, ":",  ".");
   269  
   270      CStringArray parts;
   271      explode(parts, argOut, '.');
   272  
   273      if ((parts.size() == 2) && endsWith(argOut, ".*")) {
   274          CBlock block;
   275          getBlockLight(block, str_2_Uint(parts[0]));
   276          argOut = parts[0] + ".0";
   277          if (block.transactions.size() > 0)
   278              argOut += ("-to-" + uint_2_Str(block.transactions.size()));
   279          return true;
   280      }
   281  
   282      if (parts.size() == 0) {
   283          errorMsg = argOut + " does not appear to be a valid transaction index (no parts).";
   284          return false;
   285  
   286      } else if (parts.size() == 1 && (parts[0] != "latest" && parts[0] != "first")) {
   287          errorMsg = argOut + " does not appear to be a valid transaction index (one part, not first or latest).";
   288          return false;
   289  
   290      } else if (parts.size() > 3) {
   291          errorMsg = argOut + " does not appear to be a valid transaction index (too many).";
   292          return false;
   293      } else {
   294          // two or three parts...
   295          if (!isNumeral(parts[0]) || !isNumeral(parts[1])) {
   296              errorMsg = argOut + " does not appear to be a valid transaction index.";
   297              return false;
   298          }
   299      }
   300  
   301      if (parts.size() == 2)
   302          return true;
   303  
   304      // it's directional
   305      if (parts[0] == "latest") {
   306          CBlock block;
   307          getBlockLight(block, "latest");
   308          if (block.transactions.size() > 0) {
   309              ostringstream os;
   310              os << block.blockNumber << "." << (block.transactions.size() - 1);
   311              argOut = os.str();
   312              return true;
   313          }
   314          parts[0] = uint_2_Str(block.blockNumber);
   315          parts[1] = "0";
   316          parts[2] = "prev";
   317      }
   318  
   319      if (parts[0] == "first") {
   320          argOut = "46147.0";
   321          return true;
   322      }
   323  
   324      ASSERT(parts[2] == "prev" || parts[2] == "next");
   325      return getDirectionalTxId(str_2_Uint(parts[0]), str_2_Uint(parts[1]), parts[2], argOut, errorMsg);
   326  }
   327  
   328  //--------------------------------------------------------------------------------
   329  bool getDirectionalTxId(blknum_t bn, tx num_t txid, const string_q& dir, string_q& argOut, string_q& errorMsg) {
   330      blknum_t lastBlock = getLatestBlock_client();
   331  
   332      if (bn < firstTransactionBlock()) {
   333          argOut = uint_2_Str(firstTransactionBlock()) + ".0";
   334          return true;
   335      }
   336  
   337      CBlock block;
   338      getBlock(block, bn);
   339  
   340      argOut = "";
   341      tx num_t nextid = txid + 1;
   342      while (argOut.empty() && bn >= firstTransactionBlock() && bn <= lastBlock) {
   343          if (dir == "next") {
   344              if (nextid < block.transactions.size()) {
   345                  argOut = uint_2_Str(block.blockNumber) + "." + uint_2_Str(nextid);
   346                  return true;
   347              }
   348              block = CBlock();
   349              getBlock(block, ++bn);
   350              nextid = 0;
   351          } else if (dir == "prev") {
   352              if (txid > 0 && block.transactions.size() > 0) {
   353                  argOut = uint_2_Str(block.blockNumber) + "." + uint_2_Str(txid - 1);
   354                  return true;
   355              }
   356              if (bn == 0)
   357                  return true;
   358              block = CBlock();
   359              getBlock(block, --bn);
   360              txid = block.transactions.size();
   361          }
   362      }
   363      errorMsg = "Could not find " + dir + " transaction to " + uint_2_Str(bn) + "." + uint_2_Str(txid);
   364      return false;
   365  }
   366  */
   367  
   368  func GetBlockNumberMap(chain string, ids []Identifier) (map[base.Blknum]bool, error) {
   369  	numMap := make(map[base.Blknum]bool, 1000)
   370  	for _, br := range ids {
   371  		blockNums, err := br.ResolveBlocks(chain)
   372  		if err != nil {
   373  			return numMap, err
   374  		}
   375  		for _, bn := range blockNums {
   376  			numMap[bn] = true
   377  		}
   378  	}
   379  	return numMap, nil
   380  }
   381  
   382  func GetBlockNumbers(chain string, ids []Identifier) ([]base.Blknum, error) {
   383  	var nums []base.Blknum
   384  	for _, br := range ids {
   385  		blockNums, err := br.ResolveBlocks(chain)
   386  		if err != nil {
   387  			return []base.Blknum{}, err
   388  		}
   389  		nums = append(nums, blockNums...)
   390  	}
   391  	return nums, nil
   392  }
   393  
   394  func GetTransactionIds(chain string, ids []Identifier) ([]types.Appearance, error) {
   395  	var txids []types.Appearance
   396  	for _, br := range ids {
   397  		blockNums, err := br.ResolveTxs(chain)
   398  		if err != nil {
   399  			return []types.Appearance{}, err
   400  		}
   401  		txids = append(txids, blockNums...)
   402  	}
   403  	return txids, nil
   404  }