-
Notifications
You must be signed in to change notification settings - Fork 5
feat(syscoin): add compact tx summary history #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5600693
2bae226
62d6318
f63cc7c
05085d7
ce952d2
9bd76b8
4a9a3c1
075f957
a202905
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1462,6 +1462,138 @@ func (t *Tx) getAddrVoutValue(addrDesc bchain.AddressDescriptor) *big.Int { | |
| return &val | ||
| } | ||
|
|
||
| // SYSCOIN | ||
| type accountAssetTransferSummary struct { | ||
| value big.Int | ||
| symbol string | ||
| decimals int | ||
| } | ||
|
|
||
| // SYSCOIN | ||
| func addrDescInSet(addrDesc bchain.AddressDescriptor, addrDescs map[string]struct{}) bool { | ||
| if len(addrDescs) == 0 { | ||
| return false | ||
| } | ||
| _, found := addrDescs[string(addrDesc)] | ||
| return found | ||
| } | ||
|
|
||
| // SYSCOIN | ||
| func (w *Worker) addAccountAssetTransfer(m map[string]*accountAssetTransferSummary, assetInfo *AssetInfo, sign int) { | ||
| if assetInfo == nil || assetInfo.AssetGuid == "" || assetInfo.ValueSat == nil { | ||
| return | ||
| } | ||
| summary := m[assetInfo.AssetGuid] | ||
| if summary == nil { | ||
| summary = &accountAssetTransferSummary{ | ||
| symbol: assetInfo.Symbol, | ||
| decimals: w.chainParser.AmountDecimals(), | ||
| } | ||
| if guid, err := strconv.ParseUint(assetInfo.AssetGuid, 10, 64); err == nil { | ||
| if dbAsset, err := w.db.GetAsset(guid, nil); err == nil { | ||
| summary.symbol = string(dbAsset.AssetObj.Symbol) | ||
| summary.decimals = int(dbAsset.AssetObj.Precision) | ||
| } | ||
| } | ||
| m[assetInfo.AssetGuid] = summary | ||
| } | ||
| if summary.symbol == "" { | ||
| summary.symbol = assetInfo.Symbol | ||
| } | ||
| delta := new(big.Int).Set((*big.Int)(assetInfo.ValueSat)) | ||
| if sign < 0 { | ||
| delta.Neg(delta) | ||
| } | ||
| summary.value.Add(&summary.value, delta) | ||
| } | ||
|
|
||
| // SYSCOIN | ||
| func accountAssetTransfersFromSummary(m map[string]*accountAssetTransferSummary) []TokenTransfer { | ||
| if len(m) == 0 { | ||
| return nil | ||
| } | ||
| keys := make([]string, 0, len(m)) | ||
| for guid, summary := range m { | ||
| if summary.value.Sign() != 0 { | ||
| keys = append(keys, guid) | ||
| } | ||
| } | ||
| if len(keys) == 0 { | ||
| return nil | ||
| } | ||
| sort.Strings(keys) | ||
| transfers := make([]TokenTransfer, 0, len(keys)) | ||
| for _, guid := range keys { | ||
| summary := m[guid] | ||
| value := new(big.Int).Abs(&summary.value) | ||
| transfers = append(transfers, TokenTransfer{ | ||
| Type: bchain.TokenStandardName("SPT"), | ||
| Standard: bchain.TokenStandardName("SPT"), | ||
| Contract: guid, | ||
| AssetGuid: guid, | ||
| Symbol: summary.symbol, | ||
| Decimals: summary.decimals, | ||
| Value: (*Amount)(value), | ||
| }) | ||
| } | ||
| return transfers | ||
| } | ||
|
|
||
| // SYSCOIN | ||
| func (w *Worker) summarizeTxForAccount(tx *Tx, addrDescs map[string]struct{}) *Tx { | ||
| if tx == nil { | ||
| return nil | ||
| } | ||
| var valueIn, valueOut big.Int | ||
| assetTransfers := make(map[string]*accountAssetTransferSummary) | ||
| for i := range tx.Vin { | ||
| vin := &tx.Vin[i] | ||
| if !addrDescInSet(vin.AddrDesc, addrDescs) { | ||
| continue | ||
| } | ||
| if vin.ValueSat != nil { | ||
| valueIn.Add(&valueIn, (*big.Int)(vin.ValueSat)) | ||
| } | ||
| w.addAccountAssetTransfer(assetTransfers, vin.AssetInfo, -1) | ||
| } | ||
| for i := range tx.Vout { | ||
| vout := &tx.Vout[i] | ||
| if !addrDescInSet(vout.AddrDesc, addrDescs) { | ||
| continue | ||
| } | ||
| if vout.ValueSat != nil { | ||
| valueOut.Add(&valueOut, (*big.Int)(vout.ValueSat)) | ||
| } | ||
| w.addAccountAssetTransfer(assetTransfers, vout.AssetInfo, 1) | ||
| } | ||
| direction := "received" | ||
| if valueIn.Cmp(&valueOut) > 0 { | ||
| direction = "sent" | ||
| } | ||
| accountAssetTransfers := accountAssetTransfersFromSummary(assetTransfers) | ||
| return &Tx{ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an account or xpub request uses Useful? React with 👍 / 👎. |
||
| Txid: tx.Txid, | ||
| Vin: []Vin{}, | ||
| Vout: []Vout{}, | ||
| Blockhash: tx.Blockhash, | ||
| Blockheight: tx.Blockheight, | ||
| Confirmations: tx.Confirmations, | ||
| ConfirmationETABlocks: tx.ConfirmationETABlocks, | ||
| ConfirmationETASeconds: tx.ConfirmationETASeconds, | ||
| Blocktime: tx.Blocktime, | ||
| ValueInSat: (*Amount)(&valueIn), | ||
| ValueOutSat: (*Amount)(&valueOut), | ||
| FeesSat: tx.FeesSat, | ||
| TokenTransfers: accountAssetTransfers, | ||
| Direction: direction, | ||
| AddressValueInSat: (*Amount)(&valueIn), | ||
| AddressValueOutSat: (*Amount)(&valueOut), | ||
| AccountAssetTransfers: accountAssetTransfers, | ||
| TokenType: tx.TokenType, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For Syscoin SPT transactions that carry a decoded OP_RETURN memo, Useful? React with 👍 / 👎. |
||
| Memo: tx.Memo, | ||
| } | ||
| } | ||
|
|
||
| type syscoinTokenMempoolInfo struct { | ||
| used bool | ||
| unconfirmedTxs int | ||
|
|
@@ -1574,6 +1706,7 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn | |
| vin.ValueSat = (*Amount)(&tai.ValueSat) | ||
| valInSat.Add(&valInSat, &tai.ValueSat) | ||
| vin.AssetInfo = w.assetInfoToAPI(tai.AssetInfo) // SYSCOIN | ||
| vin.AddrDesc = tai.AddrDesc // SYSCOIN | ||
| vin.Addresses, vin.IsAddress, err = tai.Addresses(w.chainParser) | ||
| if err != nil { | ||
| glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai) | ||
|
|
@@ -1592,6 +1725,7 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn | |
| vout.ValueSat = (*Amount)(&tao.ValueSat) | ||
| valOutSat.Add(&valOutSat, &tao.ValueSat) | ||
| vout.AssetInfo = w.assetInfoToAPI(tao.AssetInfo) // SYSCOIN | ||
| vout.AddrDesc = tao.AddrDesc // SYSCOIN | ||
| vout.Addresses, vout.IsAddress, err = tao.Addresses(w.chainParser) | ||
| if err != nil { | ||
| glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao) | ||
|
|
@@ -1679,6 +1813,36 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) { | |
| }, from, to, page | ||
| } | ||
|
|
||
| // SYSCOIN | ||
| func computePagingWithFirstPageMempool(confirmedCount, mempoolCount, page, itemsOnPage int) (Paging, int, int, int) { | ||
| if itemsOnPage <= 0 { | ||
| itemsOnPage = 1 | ||
| } | ||
| mempoolSlots := mempoolCount | ||
| if mempoolSlots > itemsOnPage { | ||
| mempoolSlots = itemsOnPage | ||
| } | ||
| if mempoolSlots < 0 { | ||
| mempoolSlots = 0 | ||
| } | ||
| pg, from, to, page := computePaging(confirmedCount+mempoolSlots, page, itemsOnPage) | ||
| from -= mempoolSlots | ||
| if from < 0 { | ||
| from = 0 | ||
| } | ||
| to -= mempoolSlots | ||
| if to < 0 { | ||
| to = 0 | ||
| } | ||
| if from > confirmedCount { | ||
| from = confirmedCount | ||
| } | ||
| if to > confirmedCount { | ||
| to = confirmedCount | ||
| } | ||
| return pg, from, to, page | ||
| } | ||
|
|
||
| func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, index int, c *db.AddrContract, details AccountDetails, ticker *common.CurrencyRatesTicker, secondaryCoin string, erc20Balance *big.Int, erc20Batched bool) (*Token, error) { | ||
| standard := bchain.EthereumTokenStandardMap[c.Standard] | ||
| ci, validContract, err := w.getContractDescriptorInfo(c.Contract, standard) | ||
|
|
@@ -2047,8 +2211,8 @@ func (w *Worker) getStakingPoolsData(addrDesc bchain.AddressDescriptor) ([]Staki | |
| func (w *Worker) txFromTxid(txid string, bestHeight uint32, option AccountDetails, blockInfo *db.BlockInfo, addresses map[string]struct{}) (*Tx, error) { | ||
| var tx *Tx | ||
| var err error | ||
| // only ChainBitcoinType supports TxHistoryLight | ||
| if option == AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType { | ||
| // SYSCOIN: summary history shares the indexed Bitcoin-type light path. | ||
| if option >= AccountDetailsTxHistorySummary && option <= AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| ta, err := w.db.GetTxAddresses(txid) | ||
| if err != nil { | ||
| return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) | ||
|
|
@@ -2184,6 +2348,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco | |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| accountAddrDescs := map[string]struct{}{string(addrDesc): {}} // SYSCOIN | ||
| accountChainExtraData, err = w.getAccountChainExtraData(addrDesc) | ||
| if err != nil { | ||
| glog.Warningf("GetAccountChainExtraData error %v, %v", err, address) | ||
|
|
@@ -2250,6 +2415,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco | |
| if page == 0 { | ||
| if option == AccountDetailsTxidHistory { | ||
| txids = append(txids, tx.Txid) | ||
| } else if option == AccountDetailsTxHistorySummary { | ||
| txs = append(txs, w.summarizeTxForAccount(tx, accountAddrDescs)) // SYSCOIN | ||
| } else if option >= AccountDetailsTxHistoryLight { | ||
| setIsOwnAddress(tx, address) | ||
| txs = append(txs, tx) | ||
|
|
@@ -2271,12 +2438,21 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco | |
| return nil, errors.Annotatef(err, "GetBestBlock") | ||
| } | ||
| var from, to int | ||
| pg, from, to, page = computePaging(len(txc), page, txsOnPage) | ||
| if option == AccountDetailsTxHistorySummary { | ||
| pg, from, to, page = computePagingWithFirstPageMempool(len(txc), unconfirmedTxs, page, txsOnPage) // SYSCOIN | ||
| } else { | ||
| pg, from, to, page = computePaging(len(txc), page, txsOnPage) | ||
| } | ||
| if len(txc) >= txsOnPage { | ||
| if totalResults < 0 { | ||
| pg.TotalPages = -1 | ||
| } else { | ||
| pg, _, _, _ = computePaging(totalResults, page, txsOnPage) | ||
| // SYSCOIN | ||
| if option == AccountDetailsTxHistorySummary { | ||
| pg, _, _, _ = computePagingWithFirstPageMempool(totalResults, unconfirmedTxs, page, txsOnPage) | ||
| } else { | ||
| pg, _, _, _ = computePaging(totalResults, page, txsOnPage) | ||
| } | ||
| } | ||
| } | ||
| for i := from; i < to; i++ { | ||
|
|
@@ -2288,8 +2464,12 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco | |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| setIsOwnAddress(tx, address) | ||
| txs = append(txs, tx) | ||
| if option == AccountDetailsTxHistorySummary { | ||
| txs = append(txs, w.summarizeTxForAccount(tx, accountAddrDescs)) // SYSCOIN | ||
|
Comment on lines
+2467
to
+2468
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an address has unconfirmed transactions, page 1 prepends their summaries above and then this branch adds confirmed rows using Useful? React with 👍 / 👎. |
||
| } else { | ||
| setIsOwnAddress(tx, address) | ||
| txs = append(txs, tx) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -2298,7 +2478,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco | |
| if page == 0 && txsOnPage > 0 { | ||
| if option == AccountDetailsTxidHistory && len(txids) > txsOnPage { | ||
| txids = txids[:txsOnPage] | ||
| } else if option >= AccountDetailsTxHistoryLight && len(txs) > txsOnPage { | ||
| } else if option >= AccountDetailsTxHistorySummary && len(txs) > txsOnPage { | ||
| // SYSCOIN | ||
| txs = txs[:txsOnPage] | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -546,6 +546,12 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc | |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| accountAddrDescs := make(map[string]struct{}) // SYSCOIN | ||
| for _, da := range data.addresses { | ||
| for i := range da { | ||
| accountAddrDescs[string(da[i].addrDesc)] = struct{}{} | ||
| } | ||
| } | ||
| // setup filtering of txids | ||
| var txidFilter func(txid *xpubTxid, ad *xpubAddress) bool | ||
| if !(filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff && filter.AssetsMask == bchain.AllMask) { | ||
|
|
@@ -592,6 +598,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc | |
| } | ||
| addresses := w.newAddressesMapForAliases() | ||
| xpubAssetMempool := make(map[string]map[string]*syscoinTokenMempoolInfo) // SYSCOIN | ||
| mempoolEntryCount := 0 // SYSCOIN | ||
| // process mempool, only if ToHeight is not specified | ||
| if filter.ToHeight == 0 && !filter.OnlyConfirmed { | ||
| txmMap = make(map[string]*Tx) | ||
|
|
@@ -654,22 +661,31 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc | |
| tx.addAddrVinAssetMempool(ad.addrDesc, assetMempool) | ||
| } | ||
| // mempool txs are returned only on the first page, uniquely and filtered | ||
| if page == 0 && xpubMempoolTxidFilter(&txid) { | ||
| if xpubMempoolTxidFilter(&txid) { | ||
| if _, added := addedMempoolEntries[txid.txid]; added { | ||
| continue | ||
| } | ||
| addedMempoolEntries[txid.txid] = struct{}{} | ||
| mempoolEntries = append(mempoolEntries, bchain.MempoolTxidEntry{Txid: txid.txid, Time: uint32(tx.Blocktime)}) | ||
| mempoolEntryCount++ // SYSCOIN | ||
| if page == 0 { | ||
| mempoolEntries = append(mempoolEntries, bchain.MempoolTxidEntry{Txid: txid.txid, Time: uint32(tx.Blocktime)}) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // sort the entries by time descending | ||
| sort.Sort(mempoolEntries) | ||
| for _, entry := range mempoolEntries { | ||
| mempoolEntriesToReturn := mempoolEntries | ||
| if option == AccountDetailsTxHistorySummary && txsOnPage > 0 && len(mempoolEntriesToReturn) > txsOnPage { | ||
| mempoolEntriesToReturn = mempoolEntriesToReturn[:txsOnPage] // SYSCOIN | ||
| } | ||
| for _, entry := range mempoolEntriesToReturn { | ||
| if option == AccountDetailsTxidHistory { | ||
| txids = append(txids, entry.Txid) | ||
| } else if option == AccountDetailsTxHistorySummary { | ||
| txs = append(txs, w.summarizeTxForAccount(txmMap[entry.Txid], accountAddrDescs)) // SYSCOIN | ||
|
Comment on lines
+687
to
+688
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For xpubs on the first page with unconfirmed transactions, this appends every mempool summary and the confirmed loop below still appends a full page of confirmed transactions, with no later trim in this path. As a result Useful? React with 👍 / 👎. |
||
| } else if option >= AccountDetailsTxHistoryLight { | ||
| txs = append(txs, txmMap[entry.Txid]) | ||
| } | ||
|
|
@@ -710,12 +726,21 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc | |
| totalResults = -1 | ||
| } | ||
| var from, to int | ||
| pg, from, to, page = computePaging(len(txc), page, txsOnPage) | ||
| if option == AccountDetailsTxHistorySummary { | ||
| pg, from, to, page = computePagingWithFirstPageMempool(len(txc), mempoolEntryCount, page, txsOnPage) // SYSCOIN | ||
| } else { | ||
| pg, from, to, page = computePaging(len(txc), page, txsOnPage) | ||
| } | ||
| if len(txc) >= txsOnPage { | ||
| if totalResults < 0 { | ||
| pg.TotalPages = -1 | ||
| } else { | ||
| pg, _, _, _ = computePaging(totalResults, page, txsOnPage) | ||
| // SYSCOIN | ||
| if option == AccountDetailsTxHistorySummary { | ||
| pg, _, _, _ = computePagingWithFirstPageMempool(totalResults, mempoolEntryCount, page, txsOnPage) | ||
| } else { | ||
| pg, _, _, _ = computePaging(totalResults, page, txsOnPage) | ||
| } | ||
| } | ||
| } | ||
| // get confirmed transactions | ||
|
|
@@ -728,6 +753,9 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc | |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if option == AccountDetailsTxHistorySummary { | ||
| tx = w.summarizeTxForAccount(tx, accountAddrDescs) // SYSCOIN | ||
| } | ||
| txs = append(txs, tx) | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These summary transfers are returned both as
accountAssetTransfersand asTokenTransfers, but the literal leaves the non-omitemptytype,standard, andcontractfields at empty strings. Existing Syscoin transfer rows fromgetSyscoinAssetTransfersidentify SPT transfers withSPTand the asset GUID as the contract; without the same fields here, clients consuming the normal token-transfer shape cannot reliably classify or key summary SPT movements.Useful? React with 👍 / 👎.