diff --git a/api/syscoin_merge_test.go b/api/syscoin_merge_test.go index 1d61ebcca7..319669d23c 100644 --- a/api/syscoin_merge_test.go +++ b/api/syscoin_merge_test.go @@ -1,5 +1,6 @@ //go:build unittest +// SYSCOIN: Regression coverage for asset filtering and summary pagination across upstream merges. package api import ( diff --git a/api/types.go b/api/types.go index 84eef1f711..3a45abb1ab 100644 --- a/api/types.go +++ b/api/types.go @@ -272,6 +272,7 @@ type ContractInfoResult struct { // Token contains info about tokens held by an address type Token struct { + // SYSCOIN: Both standard fields include the emitted SPTAllocated value. // Deprecated: Use Standard instead. Type bchain.TokenStandardName `json:"type" ts_type:"'' | 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155' | 'BEP20' | 'BEP721' | 'BEP1155' | 'TRC20' | 'TRC721' | 'TRC1155' | 'SPTAllocated'" ts_doc:"@deprecated: Use standard instead."` Standard bchain.TokenStandardName `json:"standard" ts_type:"'' | 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155' | 'BEP20' | 'BEP721' | 'BEP1155' | 'TRC20' | 'TRC721' | 'TRC1155' | 'SPTAllocated'"` @@ -328,6 +329,7 @@ func (a Tokens) Less(i, j int) bool { // TokenTransfer contains info about a token transfer done in a transaction type TokenTransfer struct { + // SYSCOIN: Both standard fields include the emitted SPT value. // Deprecated: Use Standard instead. Type bchain.TokenStandardName `json:"type" ts_type:"'' | 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155' | 'BEP20' | 'BEP721' | 'BEP1155' | 'TRC20' | 'TRC721' | 'TRC1155' | 'SPT'" ts_doc:"@deprecated: Use standard instead."` Standard bchain.TokenStandardName `json:"standard" ts_type:"'' | 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155' | 'BEP20' | 'BEP721' | 'BEP1155' | 'TRC20' | 'TRC721' | 'TRC1155' | 'SPT'"` diff --git a/api/worker.go b/api/worker.go index f3b73c1eb2..2892568dc3 100644 --- a/api/worker.go +++ b/api/worker.go @@ -1134,6 +1134,7 @@ var errFilteredScanLimit = NewAPIError("Filtered history is too large to scan, n func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, filter *AddressFilter, maxResults int) ([]string, error) { var err error txids := make([]string, 0, 4) + // SYSCOIN: Filter transaction versions against the requested asset mask. txMatchesAssetMask := func(txid string) (bool, error) { if filter.AssetsMask == bchain.AllMask { return true, nil @@ -1148,6 +1149,7 @@ func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool } return uint32(filter.AssetsMask)&uint32(mask) == uint32(mask), nil } + // SYSCOIN: Asset-mask rejections share the upstream bound for filtered scans. nonMatching := 0 skipNonMatching := func() error { nonMatching++ diff --git a/api/xpub.go b/api/xpub.go index aed6068587..3ceacb1f73 100644 --- a/api/xpub.go +++ b/api/xpub.go @@ -326,6 +326,7 @@ func (w *Worker) xpubGetAddressTxids(addrDesc bchain.AddressDescriptor, mempool return txs, complete, nil } +// SYSCOIN: An asset mask also prevents use of the unfiltered transaction cache. func isUnfilteredXpubTxidFilter(filter *AddressFilter) bool { return filter == nil || filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff && filter.AssetsMask == bchain.AllMask } @@ -728,6 +729,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc page = 0 } if filter == nil { + // SYSCOIN: An omitted filter includes every asset transaction type. filter = &AddressFilter{Vout: AddressFilterVoutOff, AssetsMask: bchain.AllMask} } type mempoolMap struct { @@ -760,7 +762,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc accountAddrDescs[string(da[i].addrDesc)] = struct{}{} } } - // setup filtering of txids + // SYSCOIN: Asset masks participate in confirmed transaction filtering. var txidFilter func(txid *xpubTxid, ad *xpubAddress) bool if !(filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff && filter.AssetsMask == bchain.AllMask) { toHeight := maxUint32 @@ -777,6 +779,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc return false } } + // SYSCOIN: Match asset transaction types from the indexed version. if filter.AssetsMask != bchain.AllMask { ta, err := w.db.GetTxAddresses(txid.txid) if err != nil || ta == nil { @@ -792,6 +795,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc } filtered = true } + // SYSCOIN: Apply mempool filters independently of confirmed transaction metadata. xpubMempoolTxidFilter := func(txid *xpubTxid) bool { if filter.FromHeight != 0 { return false @@ -809,6 +813,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc // process mempool, only if ToHeight is not specified if filter.ToHeight == 0 && !filter.OnlyConfirmed { txmMap = make(map[string]*Tx) + // SYSCOIN: Count transactions once while retaining per-address balance updates. countedMempoolTxs := make(map[string]struct{}) addedMempoolEntries := make(map[string]struct{}) mempoolEntries = make(bchain.MempoolTxidEntries, 0) @@ -822,6 +827,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc for _, txid := range newTxids { // the same tx can have multiple addresses from the same xpub, get it from backend it only once tx, foundTx := txmMap[txid.txid] + // SYSCOIN: Asset filtering needs the transaction version even for basic detail. needTx := option != AccountDetailsBasic || filter.AssetsMask != bchain.AllMask if option == AccountDetailsBasic && !needTx { // Basic detail: skip per-tx loading. Count unique mempool txids @@ -845,6 +851,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc } // skip already confirmed txs, mempool may be out of sync if tx.Confirmations == 0 { + // SYSCOIN: Exclude mempool transactions outside the requested asset mask. if filter.AssetsMask != bchain.AllMask { mask := assetMaskFromVersion(w.chainParser, tx.Version) if mask != bchain.AllMask && uint32(filter.AssetsMask)&uint32(mask) != uint32(mask) { @@ -857,6 +864,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc } uBalSat.Add(&uBalSat, tx.getAddrVoutValue(ad.addrDesc)) uBalSat.Sub(&uBalSat, tx.getAddrVinValue(ad.addrDesc)) + // SYSCOIN: Accumulate SPT mempool balances for each derived address. if option > AccountDetailsBasic { addrKey := string(ad.addrDesc) assetMempool := xpubAssetMempool[addrKey] @@ -867,7 +875,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc tx.addAddrVoutAssetMempool(ad.addrDesc, assetMempool) tx.addAddrVinAssetMempool(ad.addrDesc, assetMempool) } - // Mempool txs enter the paged sequence once, uniquely and filtered. + // SYSCOIN: Mempool txs enter the paged sequence once, uniquely and filtered. if xpubMempoolTxidFilter(&txid) { if _, added := addedMempoolEntries[txid.txid]; added { continue diff --git a/bchain/coins/blockchain_syscoin_test.go b/bchain/coins/blockchain_syscoin_test.go index 52a2fa2ef1..500a7f69d7 100644 --- a/bchain/coins/blockchain_syscoin_test.go +++ b/bchain/coins/blockchain_syscoin_test.go @@ -1,5 +1,6 @@ //go:build unittest +// SYSCOIN: Regression coverage for broadcast options and their metrics wrapper. package coins import ( diff --git a/build/tools/typescriptify/typescriptify_test.go b/build/tools/typescriptify/typescriptify_test.go index 60b9490405..047bb41caf 100644 --- a/build/tools/typescriptify/typescriptify_test.go +++ b/build/tools/typescriptify/typescriptify_test.go @@ -138,6 +138,7 @@ func TestMarkNullableIsScopedToInterface(t *testing.T) { } } +// SYSCOIN: Keep generated memo types consistent with the base64 JSON wire format. func TestTransactionMemoMatchesJSONWireType(t *testing.T) { memo := []byte{0, 1, 2, 0xff, 0x0a} encoded, err := json.Marshal(api.Tx{Memo: memo}) @@ -164,6 +165,7 @@ func TestTransactionMemoMatchesJSONWireType(t *testing.T) { } } +// SYSCOIN: Preserve the emitted SPT standards in generated TypeScript unions. func TestSyscoinTokenStandardsMatchGeneratedTypes(t *testing.T) { for _, tc := range []struct { name string diff --git a/db/rocksdb.go b/db/rocksdb.go index fc2269c5e0..72dc7a465a 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -623,7 +623,7 @@ func (d *RocksDB) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor, low if glog.V(2) { glog.Warningf("rocksdb: addrDesc %s - mixed with %s", addrDesc, hex.EncodeToString(key)) } - // Longer indexed scripts can share even a complete descriptor as a prefix. Bound the scan, but report exhaustion so callers cannot mistake partial history for a complete result. + // SYSCOIN: Longer indexed scripts can share even a complete descriptor as a prefix. Report scan exhaustion so callers cannot mistake partial history for a complete result. nonMatching++ if nonMatching > maxAddrDescScanNonMatching { return errors.Errorf("address transaction scan limit exceeded for descriptor %s after %d non-matching keys", hex.EncodeToString(addrDesc), nonMatching) @@ -1457,6 +1457,7 @@ func (d *RocksDB) GetTxAddressesOutput(txid string, vout uint32) (*TxOutput, err return nil, nil } var l int + // SYSCOIN: Our transaction-address records include a version before the height. if supportsSyscoinAssets(d.chainParser) { _, l = unpackVaruint(buf) // version } @@ -1850,6 +1851,7 @@ func (d *RocksDB) packedTxInputLen(buf []byte) int { _, n := unpackVaruint(buf[pos:]) // prev vout pos += n } + // SYSCOIN: Include the optional asset suffix when skipping an extended input. if supportsSyscoinAssets(d.chainParser) { pos += packedAssetInfoLen(buf[pos:]) } @@ -1858,6 +1860,7 @@ func (d *RocksDB) packedTxInputLen(buf []byte) int { al, pos := unpackVaruint(buf) pos += int(al) // addrDesc pos += int(buf[pos]) + 1 // value + // SYSCOIN: Basic inputs also carry the optional asset suffix. if supportsSyscoinAssets(d.chainParser) { pos += packedAssetInfoLen(buf[pos:]) } @@ -1881,6 +1884,7 @@ func (d *RocksDB) packedTxOutputLen(buf []byte) int { _, n = unpackVaruint(buf[pos:]) // spent height pos += n } + // SYSCOIN: Skip the asset suffix after the output's optional spending metadata. if supportsSyscoinAssets(d.chainParser) { pos += packedAssetInfoLen(buf[pos:]) } @@ -3197,7 +3201,7 @@ func unpackAssetInfo(buf []byte) (*bchain.AssetInfo, int) { return &bchain.AssetInfo{AssetGuid: assetGuid, ValueSat: &valueSat}, l } -// packedAssetInfoLen skips optional Syscoin metadata without allocating its value. +// SYSCOIN: packedAssetInfoLen skips optional asset metadata without allocating its value. // Keep in sync with appendAssetInfo/unpackAssetInfo. func packedAssetInfoLen(buf []byte) int { flag, pos := unpackVaruint(buf) diff --git a/db/rocksdb_syscoin_output_test.go b/db/rocksdb_syscoin_output_test.go index 820d2bc147..6b7d854a72 100644 --- a/db/rocksdb_syscoin_output_test.go +++ b/db/rocksdb_syscoin_output_test.go @@ -1,5 +1,6 @@ //go:build unittest +// SYSCOIN: Regression coverage for indexed output decoding, bulk indexing, and reorg restoration. package db import ( diff --git a/db/rocksdb_test.go b/db/rocksdb_test.go index 9c8786e65d..cdea27fc3c 100644 --- a/db/rocksdb_test.go +++ b/db/rocksdb_test.go @@ -2077,10 +2077,12 @@ func TestGetAddrDescTransactions_ScanBound(t *testing.T) { matches++ return nil }) + // SYSCOIN: Exhaustion must be reported instead of returning a successful partial history. require.ErrorContains(t, err, "address transaction scan limit exceeded") require.Equal(t, 0, matches, "scan must abort before reaching the trailing matching key") } +// SYSCOIN: A complete, indexable descriptor can still prefix unrelated scripts. func TestGetAddrDescTransactions_CompleteDescriptorScanLimit(t *testing.T) { d := setupRocksDB(t, &testBitcoinParser{BitcoinParser: bitcoinTestnetParser()}) defer closeAndDestroyRocksDB(t, d) diff --git a/server/ws_types.go b/server/ws_types.go index 4f5707a74c..829671ed7d 100644 --- a/server/ws_types.go +++ b/server/ws_types.go @@ -28,6 +28,7 @@ type resultError struct { } // WsAccountInfoReq carries parameters for the 'getAccountInfo' method. +// SYSCOIN: Details also accepts the lightweight txsummary response. type WsAccountInfoReq struct { Descriptor string `json:"descriptor" ts_doc:"Address or XPUB descriptor to query."` Details string `json:"details,omitempty" ts_type:"'basic' | 'tokens' | 'tokenBalances' | 'txids' | 'txsummary' | 'txslight' | 'txs'" ts_doc:"Level of detail to retrieve about the account."`