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 }