7 log "github.com/sirupsen/logrus"
8 "github.com/tendermint/tmlibs/db"
10 "github.com/bytom/blockchain/account"
11 "github.com/bytom/blockchain/query"
12 "github.com/bytom/crypto/sha3pool"
13 "github.com/bytom/errors"
14 "github.com/bytom/protocol/bc"
15 "github.com/bytom/protocol/bc/legacy"
18 type rawOutput struct {
29 type accountOutput struct {
38 //TxPreFix is wallet database transactions prefix
42 func calcAnnotatedKey(blockHeight uint64, position uint32) []byte {
43 return []byte(fmt.Sprintf("%s%016x%08x", TxPreFix, blockHeight, position))
46 func calcDeletePreFix(blockHeight uint64) []byte {
47 return []byte(fmt.Sprintf("%s%016x", TxPreFix, blockHeight))
50 //deleteTransaction delete transactions when orphan block rollback
51 func (w *Wallet) deleteTransactions(batch db.Batch, height uint64, b *legacy.Block) {
52 txIter := w.DB.IteratorPrefix(calcDeletePreFix(height))
53 defer txIter.Release()
56 batch.Delete(txIter.Key())
60 //ReverseAccountUTXOs process the invalid blocks when orphan block rollback
61 func (w *Wallet) reverseAccountUTXOs(batch db.Batch, b *legacy.Block) {
64 //unknow how many spent and retire outputs
65 reverseOuts := make([]*rawOutput, 0)
68 for _, tx := range b.Transactions {
69 for _, inpID := range tx.Tx.InputIDs {
71 sp, err := tx.Spend(inpID)
76 resOut, ok := tx.Entries[*sp.SpentOutputId].(*bc.Output)
82 OutputID: *sp.SpentOutputId,
83 AssetAmount: *resOut.Source.Value,
84 ControlProgram: resOut.ControlProgram.Code,
86 sourceID: *resOut.Source.Ref,
87 sourcePos: resOut.Source.Position,
88 refData: *resOut.Data,
90 reverseOuts = append(reverseOuts, out)
94 accOuts := loadAccountInfo(reverseOuts, w)
95 if err = upsertConfirmedAccountOutputs(accOuts, b, batch, w); err != nil {
96 log.WithField("err", err).Error("reversing account spent and retire outputs")
101 for _, tx := range b.Transactions {
102 for j := range tx.Outputs {
103 resOutID := tx.ResultIds[j]
104 if _, ok := tx.Entries[*resOutID].(*bc.Output); !ok {
109 batch.Delete(account.UTXOKey(*resOutID))
114 //indexTransactions saves all annotated transactions to the database.
115 func (w *Wallet) indexTransactions(batch db.Batch, b *legacy.Block) error {
116 annotatedTxs := filterAccountTxs(b, w)
117 annotateTxsAsset(annotatedTxs, w.DB)
118 annotateTxsAccount(annotatedTxs, w.DB)
120 for pos, tx := range annotatedTxs {
121 rawTx, err := json.Marshal(tx)
123 return errors.Wrap(err, "inserting annotated_txs to db")
126 batch.Set(calcAnnotatedKey(b.Height, uint32(pos)), rawTx)
131 //buildAccountUTXOs process valid blocks to build account unspent outputs db
132 func (w *Wallet) buildAccountUTXOs(batch db.Batch, b *legacy.Block) {
136 delOutputIDs := prevoutDBKeys(b.Transactions...)
137 for _, delOutputID := range delOutputIDs {
138 batch.Delete(account.UTXOKey(delOutputID))
142 outs := make([]*rawOutput, 0, len(b.Transactions))
143 for _, tx := range b.Transactions {
144 for j, out := range tx.Outputs {
145 resOutID := tx.ResultIds[j]
146 resOut, ok := tx.Entries[*resOutID].(*bc.Output)
151 OutputID: *tx.OutputID(j),
152 AssetAmount: out.AssetAmount,
153 ControlProgram: out.ControlProgram,
155 outputIndex: uint32(j),
156 sourceID: *resOut.Source.Ref,
157 sourcePos: resOut.Source.Position,
158 refData: *resOut.Data,
160 outs = append(outs, out)
163 accOuts := loadAccountInfo(outs, w)
165 if err = upsertConfirmedAccountOutputs(accOuts, b, batch, w); err != nil {
166 log.WithField("err", err).Error("building new account outputs")
171 func prevoutDBKeys(txs ...*legacy.Tx) (outputIDs []bc.Hash) {
172 for _, tx := range txs {
173 for _, inpID := range tx.Tx.InputIDs {
174 if sp, err := tx.Spend(inpID); err == nil {
175 outputIDs = append(outputIDs, *sp.SpentOutputId)
182 // loadAccountInfo turns a set of output IDs into a set of
183 // outputs by adding account annotations. Outputs that can't be
184 // annotated are excluded from the result.
185 func loadAccountInfo(outs []*rawOutput, w *Wallet) []*accountOutput {
186 outsByScript := make(map[string][]*rawOutput, len(outs))
187 for _, out := range outs {
188 scriptStr := string(out.ControlProgram)
189 outsByScript[scriptStr] = append(outsByScript[scriptStr], out)
192 result := make([]*accountOutput, 0, len(outs))
193 cp := account.CtrlProgram{}
196 for s := range outsByScript {
197 sha3pool.Sum256(hash[:], []byte(s))
198 bytes := w.DB.Get(account.CPKey(hash))
203 err := json.Unmarshal(bytes, &cp)
208 isExist := w.DB.Get(account.Key(cp.AccountID))
213 for _, out := range outsByScript[s] {
214 newOut := &accountOutput{
216 AccountID: cp.AccountID,
218 keyIndex: cp.KeyIndex,
221 result = append(result, newOut)
228 // upsertConfirmedAccountOutputs records the account data for confirmed utxos.
229 // If the account utxo already exists (because it's from a local tx), the
230 // block confirmation data will in the row will be updated.
231 func upsertConfirmedAccountOutputs(outs []*accountOutput, block *legacy.Block, batch db.Batch, w *Wallet) error {
233 for _, out := range outs {
235 OutputID: out.OutputID,
236 SourceID: out.sourceID,
237 AssetID: *out.AssetId,
239 SourcePos: out.sourcePos,
240 ControlProgram: out.ControlProgram,
241 RefDataHash: out.refData,
242 ControlProgramIndex: out.keyIndex,
243 AccountID: out.AccountID,
244 Address: out.Address,
247 data, err := json.Marshal(u)
249 return errors.Wrap(err, "failed marshal accountutxo")
251 batch.Set(account.UTXOKey(out.OutputID), data)
256 // filt related and build the fully annotated transactions.
257 func filterAccountTxs(b *legacy.Block, w *Wallet) []*query.AnnotatedTx {
258 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
259 for pos, tx := range b.Transactions {
261 for _, v := range tx.Outputs {
264 sha3pool.Sum256(hash[:], v.ControlProgram)
265 if bytes := w.DB.Get(account.CPKey(hash)); bytes != nil {
266 annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, uint32(pos)))
276 for _, v := range tx.Inputs {
277 outid, err := v.SpentOutputID()
281 if bytes := w.DB.Get(account.UTXOKey(outid)); bytes != nil {
282 annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, uint32(pos)))