import (
"encoding/json"
"fmt"
- "time"
+ "sort"
log "github.com/sirupsen/logrus"
"github.com/tendermint/tmlibs/db"
- "github.com/bytom/blockchain/account"
- "github.com/bytom/blockchain/asset"
+ "github.com/bytom/account"
+ "github.com/bytom/asset"
"github.com/bytom/blockchain/query"
"github.com/bytom/consensus"
+ "github.com/bytom/consensus/segwit"
"github.com/bytom/crypto/sha3pool"
chainjson "github.com/bytom/encoding/json"
"github.com/bytom/errors"
"github.com/bytom/protocol/bc"
- "github.com/bytom/protocol/bc/legacy"
+ "github.com/bytom/protocol/bc/types"
)
type rawOutput struct {
outputIndex uint32
sourceID bc.Hash
sourcePos uint64
- refData bc.Hash
ValidHeight uint64
}
return []byte(TxIndexPrefix + txID)
}
-//deleteTransaction delete transactions when orphan block rollback
+// deleteTransaction delete transactions when orphan block rollback
func (w *Wallet) deleteTransactions(batch db.Batch, height uint64) {
tmpTx := query.AnnotatedTx{}
for txIter.Next() {
if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
- //delete index
+ // delete index
batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
}
}
}
-//ReverseAccountUTXOs process the invalid blocks when orphan block rollback
-func (w *Wallet) reverseAccountUTXOs(batch db.Batch, b *legacy.Block) {
+// ReverseAccountUTXOs process the invalid blocks when orphan block rollback
+func (w *Wallet) reverseAccountUTXOs(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
var err error
- //unknow how many spent and retire outputs
- reverseOuts := make([]*rawOutput, 0)
+ // unknow how many spent and retire outputs
+ reverseOuts := []*rawOutput{}
- //handle spent UTXOs
+ // handle spent UTXOs
for txIndex, tx := range b.Transactions {
for _, inpID := range tx.Tx.InputIDs {
- //spend and retire
+ // spend and retire
sp, err := tx.Spend(inpID)
if err != nil {
continue
continue
}
- statusFail, _ := b.TransactionStatus.GetStatus(txIndex)
+ statusFail, _ := txStatus.GetStatus(txIndex)
if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
continue
}
txHash: tx.ID,
sourceID: *resOut.Source.Ref,
sourcePos: resOut.Source.Position,
- refData: *resOut.Data,
}
reverseOuts = append(reverseOuts, out)
}
return
}
- //handle new UTXOs
+ // handle new UTXOs
for _, tx := range b.Transactions {
for j := range tx.Outputs {
resOutID := tx.ResultIds[j]
- if _, ok := tx.Entries[*resOutID].(*bc.Output); !ok {
- //retirement
+ resOut, ok := tx.Entries[*resOutID].(*bc.Output)
+ if !ok {
+ // retirement
continue
}
- //delete new UTXOs
- batch.Delete(account.UTXOKey(*resOutID))
+
+ if segwit.IsP2WScript(resOut.ControlProgram.Code) {
+ // delete standard UTXOs
+ batch.Delete(account.StandardUTXOKey(*resOutID))
+ } else {
+ // delete contract UTXOs
+ batch.Delete(account.ContractUTXOKey(*resOutID))
+ }
}
}
}
-//save external and local assets definition,
-//when query ,query local first and if have no then query external
-//details see getAliasDefinition
-func saveExternalAssetDefinition(b *legacy.Block, walletDB db.DB) {
+// saveExternalAssetDefinition save external and local assets definition,
+// when query ,query local first and if have no then query external
+// details see getAliasDefinition
+func saveExternalAssetDefinition(b *types.Block, walletDB db.DB) {
storeBatch := walletDB.NewBatch()
defer storeBatch.Write()
for _, tx := range b.Transactions {
for _, orig := range tx.Inputs {
- if ii, ok := orig.TypedInput.(*legacy.IssuanceInput); ok {
+ if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
if isValidJSON(ii.AssetDefinition) {
assetID := ii.AssetID()
if assetExist := walletDB.Get(asset.CalcExtAssetKey(&assetID)); assetExist != nil {
}
}
+// Summary is the struct of transaction's input and output summary
type Summary struct {
Type string `json:"type"`
AssetID bc.AssetID `json:"asset_id,omitempty"`
Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
}
+// TxSummary is the struct of transaction summary
type TxSummary struct {
- ID bc.Hash `json:"id"`
- Timestamp time.Time `json:"timestamp"`
+ ID bc.Hash `json:"tx_id"`
+ Timestamp uint64 `json:"block_time"`
Inputs []Summary `json:"inputs"`
Outputs []Summary `json:"outputs"`
}
-//indexTransactions saves all annotated transactions to the database.
-func (w *Wallet) indexTransactions(batch db.Batch, b *legacy.Block) error {
- annotatedTxs := filterAccountTxs(b, w)
+// indexTransactions saves all annotated transactions to the database.
+func (w *Wallet) indexTransactions(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
+ annotatedTxs := w.filterAccountTxs(b, txStatus)
saveExternalAssetDefinition(b, w.DB)
annotateTxsAsset(w, annotatedTxs)
annotateTxsAccount(annotatedTxs, w.DB)
return nil
}
-//buildAccountUTXOs process valid blocks to build account unspent outputs db
-func (w *Wallet) buildAccountUTXOs(batch db.Batch, b *legacy.Block) {
- var err error
-
- //handle spent UTXOs
- delOutputIDs := prevoutDBKeys(b)
- for _, delOutputID := range delOutputIDs {
- batch.Delete(account.UTXOKey(delOutputID))
- }
+// buildAccountUTXOs process valid blocks to build account unspent outputs db
+func (w *Wallet) buildAccountUTXOs(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
+ // get the spent UTXOs and delete the UTXOs from DB
+ prevoutDBKeys(batch, b, txStatus)
- //handle new UTXOs
+ // handle new UTXOs
outs := make([]*rawOutput, 0, len(b.Transactions))
for txIndex, tx := range b.Transactions {
for j, out := range tx.Outputs {
if !ok {
continue
}
- statusFail, _ := b.TransactionStatus.GetStatus(txIndex)
+ statusFail, _ := txStatus.GetStatus(txIndex)
if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
continue
}
outputIndex: uint32(j),
sourceID: *resOut.Source.Ref,
sourcePos: resOut.Source.Position,
- refData: *resOut.Data,
}
- //coinbase utxo valid height
+ // coinbase utxo valid height
if txIndex == 0 {
out.ValidHeight = b.Height + consensus.CoinbasePendingBlockNumber
}
}
accOuts := loadAccountInfo(outs, w)
- if err = upsertConfirmedAccountOutputs(accOuts, batch); err != nil {
+ if err := upsertConfirmedAccountOutputs(accOuts, batch); err != nil {
log.WithField("err", err).Error("building new account outputs")
return
}
}
-func prevoutDBKeys(b *legacy.Block) (outputIDs []bc.Hash) {
+func prevoutDBKeys(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
for txIndex, tx := range b.Transactions {
for _, inpID := range tx.Tx.InputIDs {
- if sp, err := tx.Spend(inpID); err == nil {
- statusFail, _ := b.TransactionStatus.GetStatus(txIndex)
- if statusFail && *sp.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
- continue
- }
- outputIDs = append(outputIDs, *sp.SpentOutputId)
+ sp, err := tx.Spend(inpID)
+ if err != nil {
+ continue
+ }
+
+ statusFail, _ := txStatus.GetStatus(txIndex)
+ if statusFail && *sp.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
+ continue
+ }
+
+ resOut, ok := tx.Entries[*sp.SpentOutputId].(*bc.Output)
+ if !ok {
+ // retirement
+ log.WithField("SpentOutputId", *sp.SpentOutputId).Info("the OutputId is retirement")
+ continue
+ }
+
+ if segwit.IsP2WScript(resOut.ControlProgram.Code) {
+ // delete standard UTXOs
+ batch.Delete(account.StandardUTXOKey(*sp.SpentOutputId))
+ } else {
+ // delete contract UTXOs
+ batch.Delete(account.ContractUTXOKey(*sp.SpentOutputId))
}
}
}
var hash [32]byte
for s := range outsByScript {
+ // smart contract UTXO
+ if !segwit.IsP2WScript([]byte(s)) {
+ for _, out := range outsByScript[s] {
+ newOut := &accountOutput{
+ rawOutput: *out,
+ change: false,
+ }
+ result = append(result, newOut)
+ }
+
+ continue
+ }
+
sha3pool.Sum256(hash[:], []byte(s))
bytes := w.DB.Get(account.CPKey(hash))
if bytes == nil {
Amount: out.Amount,
SourcePos: out.sourcePos,
ControlProgram: out.ControlProgram,
- RefDataHash: out.refData,
ControlProgramIndex: out.keyIndex,
AccountID: out.AccountID,
Address: out.Address,
if err != nil {
return errors.Wrap(err, "failed marshal accountutxo")
}
- batch.Set(account.UTXOKey(out.OutputID), data)
+
+ if segwit.IsP2WScript(out.ControlProgram) {
+ // standard UTXOs
+ batch.Set(account.StandardUTXOKey(out.OutputID), data)
+ } else {
+ // contract UTXOs
+ batch.Set(account.ContractUTXOKey(out.OutputID), data)
+ }
+
}
return nil
}
-// filt related and build the fully annotated transactions.
-func filterAccountTxs(b *legacy.Block, w *Wallet) []*query.AnnotatedTx {
+// filterAccountTxs related and build the fully annotated transactions.
+func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
for pos, tx := range b.Transactions {
+ statusFail, _ := txStatus.GetStatus(pos)
local := false
for _, v := range tx.Outputs {
var hash [32]byte
sha3pool.Sum256(hash[:], v.ControlProgram)
if bytes := w.DB.Get(account.CPKey(hash)); bytes != nil {
- annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, pos))
+ annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, statusFail, pos))
local = true
break
}
if err != nil {
continue
}
- if bytes := w.DB.Get(account.UTXOKey(outid)); bytes != nil {
- annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, pos))
+ if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
+ annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, statusFail, pos))
break
}
}
return annotatedTxs
}
-//GetTransactionsByTxID get account txs by account tx ID
+// GetTransactionByTxID get transaction by txID
+func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
+ formatKey := w.DB.Get(calcTxIndexKey(txID))
+ if formatKey == nil {
+ return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
+ }
+
+ annotatedTx := &query.AnnotatedTx{}
+ txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
+ if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
+ return nil, err
+ }
+
+ return annotatedTx, nil
+}
+
+// GetTransactionsByTxID get account txs by account tx ID
func (w *Wallet) GetTransactionsByTxID(txID string) ([]*query.AnnotatedTx, error) {
annotatedTxs := []*query.AnnotatedTx{}
formatKey := ""
return annotatedTxs, nil
}
-//GetTransactionsSummary get transactions summary
+// GetTransactionsSummary get transactions summary
func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
- Txs := make([]TxSummary, 0)
+ Txs := []TxSummary{}
for _, annotatedTx := range transactions {
tmpTxSummary := TxSummary{
return false
}
-//GetTransactionsByAccountID get account txs by account ID
+// GetTransactionsByAccountID get account txs by account ID
func (w *Wallet) GetTransactionsByAccountID(accountID string) ([]*query.AnnotatedTx, error) {
annotatedTxs := []*query.AnnotatedTx{}
return annotatedTxs, nil
}
-//GetAccountUTXOs return all account unspent outputs
+// GetAccountUTXOs return all account unspent outputs
func (w *Wallet) GetAccountUTXOs(id string) ([]account.UTXO, error) {
accountUTXO := account.UTXO{}
- accountUTXOs := make([]account.UTXO, 0)
+ accountUTXOs := []account.UTXO{}
accountUTXOIter := w.DB.IteratorPrefix([]byte(account.UTXOPreFix + id))
defer accountUTXOIter.Release()
return accountUTXOs, nil
}
+
+func (w *Wallet) GetAccountBalances(id string) ([]accountBalance, error) {
+ accountUTXOs, err := w.GetAccountUTXOs("")
+ if err != nil {
+ return nil, err
+ }
+
+ return w.indexBalances(accountUTXOs), nil
+}
+
+type accountBalance struct {
+ AccountID string `json:"account_id"`
+ Alias string `json:"account_alias"`
+ AssetAlias string `json:"asset_alias"`
+ AssetID string `json:"asset_id"`
+ Amount uint64 `json:"amount"`
+}
+
+func (w *Wallet) indexBalances(accountUTXOs []account.UTXO) []accountBalance {
+ accBalance := make(map[string]map[string]uint64)
+ balances := make([]accountBalance, 0)
+ tmpBalance := accountBalance{}
+
+ for _, accountUTXO := range accountUTXOs {
+ assetID := accountUTXO.AssetID.String()
+ if _, ok := accBalance[accountUTXO.AccountID]; ok {
+ if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
+ accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
+ } else {
+ accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
+ }
+ } else {
+ accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
+ }
+ }
+
+ var sortedAccount []string
+ for k := range accBalance {
+ sortedAccount = append(sortedAccount, k)
+ }
+ sort.Strings(sortedAccount)
+
+ for _, id := range sortedAccount {
+ var sortedAsset []string
+ for k := range accBalance[id] {
+ sortedAsset = append(sortedAsset, k)
+ }
+ sort.Strings(sortedAsset)
+
+ for _, assetID := range sortedAsset {
+ alias := w.AccountMgr.GetAliasByID(id)
+ assetAlias := w.AssetReg.GetAliasByID(assetID)
+ tmpBalance.Alias = alias
+ tmpBalance.AccountID = id
+ tmpBalance.AssetID = assetID
+ tmpBalance.AssetAlias = assetAlias
+ tmpBalance.Amount = accBalance[id][assetID]
+ balances = append(balances, tmpBalance)
+ }
+ }
+
+ return balances
+}