8 "github.com/bytom/blockchain/query"
9 "github.com/bytom/blockchain/signers"
10 "github.com/bytom/crypto/sha3pool"
11 "github.com/bytom/errors"
12 "github.com/bytom/protocol/bc"
13 "github.com/bytom/protocol/bc/legacy"
15 chainjson "github.com/bytom/encoding/json"
19 // PinName is used to identify the pin associated with
20 // the account indexer block processor.
22 // ExpirePinName is used to identify the pin associated
23 // with the account control program expiration processor.
24 ExpirePinName = "expire-control-programs"
25 // DeleteSpentsPinName is used to identify the pin associated
26 // with the processor that deletes spent account UTXOs.
27 DeleteSpentsPinName = "delete-account-spents"
30 type AccountUTXOs struct {
44 var emptyJSONObject = json.RawMessage(`{}`)
46 // A Saver is responsible for saving an annotated account object.
47 // for indexing and retrieval.
48 // If the Core is configured not to provide search services,
49 // SaveAnnotatedAccount can be a no-op.
50 type Saver interface {
51 SaveAnnotatedAccount(context.Context, *query.AnnotatedAccount) error
54 func Annotated(a *Account) (*query.AnnotatedAccount, error) {
55 aa := &query.AnnotatedAccount{
59 Tags: &emptyJSONObject,
62 tags, err := json.Marshal(a.Tags)
67 rawTags := json.RawMessage(tags)
71 path := signers.Path(a.Signer, signers.AccountKeySpace)
72 var jsonPath []chainjson.HexBytes
73 for _, p := range path {
74 jsonPath = append(jsonPath, p)
76 for _, xpub := range a.XPubs {
77 aa.Keys = append(aa.Keys, &query.AccountKey{
79 AccountXPub: xpub.Derive(path),
80 AccountDerivationPath: jsonPath,
86 func (m *Manager) indexAnnotatedAccount(ctx context.Context, a *Account) error {
90 aa, err := Annotated(a)
94 return m.indexer.SaveAnnotatedAccount(ctx, aa)
97 type rawOutput struct {
100 ControlProgram []byte
108 type accountOutput struct {
115 func (m *Manager) ProcessBlocks(ctx context.Context) {
116 if m.pinStore == nil {
120 go m.pinStore.ProcessBlocks(ctx, m.chain, DeleteSpentsPinName, func(ctx context.Context, b *legacy.Block) error {
121 <-m.pinStore.PinWaiter(PinName, b.Height)
122 return m.deleteSpentOutputs(ctx, b)
124 m.pinStore.ProcessBlocks(ctx, m.chain, PinName, m.indexAccountUTXOs)
128 func (m *Manager) deleteSpentOutputs(ctx context.Context, b *legacy.Block) error {
129 // Delete consumed account UTXOs.
130 delOutputIDs := prevoutDBKeys(b.Transactions...)
131 for _, delOutputID := range delOutputIDs {
132 m.pinStore.DB.Delete(json.RawMessage("acu" + string(delOutputID.Bytes())))
135 return errors.Wrap(nil, "deleting spent account utxos")
138 func (m *Manager) indexAccountUTXOs(ctx context.Context, b *legacy.Block) error {
139 // Upsert any UTXOs belonging to accounts managed by this Core.
140 outs := make([]*rawOutput, 0, len(b.Transactions))
141 blockPositions := make(map[bc.Hash]uint32, len(b.Transactions))
142 for i, tx := range b.Transactions {
143 blockPositions[tx.ID] = uint32(i)
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 := m.loadAccountInfo(ctx, outs)
165 err := m.upsertConfirmedAccountOutputs(ctx, accOuts, blockPositions, b)
166 return errors.Wrap(err, "upserting confirmed account utxos")
169 func prevoutDBKeys(txs ...*legacy.Tx) (outputIDs []bc.Hash) {
170 for _, tx := range txs {
171 for _, inpID := range tx.Tx.InputIDs {
172 if sp, err := tx.Spend(inpID); err == nil {
173 outputIDs = append(outputIDs, *sp.SpentOutputId)
180 // loadAccountInfo turns a set of output IDs into a set of
181 // outputs by adding account annotations. Outputs that can't be
182 // annotated are excluded from the result.
183 func (m *Manager) loadAccountInfo(ctx context.Context, outs []*rawOutput) []*accountOutput {
184 outsByScript := make(map[string][]*rawOutput, len(outs))
185 for _, out := range outs {
186 scriptStr := string(out.ControlProgram)
187 outsByScript[scriptStr] = append(outsByScript[scriptStr], out)
190 result := make([]*accountOutput, 0, len(outs))
194 ControlProgram []byte
200 for s := range outsByScript {
201 sha3pool.Sum256(b32[:], []byte(s))
202 bytes := m.db.Get(json.RawMessage("acp" + string(b32[:])))
207 err := json.Unmarshal(bytes, &cp)
212 //filte the accounts which exists in accountdb with wallet enabled
213 isExist := m.db.Get(json.RawMessage(cp.AccountID))
214 if len(isExist) == 0 {
218 for _, out := range outsByScript[s] {
219 newOut := &accountOutput{
221 AccountID: cp.AccountID,
222 keyIndex: cp.KeyIndex,
225 result = append(result, newOut)
232 // upsertConfirmedAccountOutputs records the account data for confirmed utxos.
233 // If the account utxo already exists (because it's from a local tx), the
234 // block confirmation data will in the row will be updated.
235 func (m *Manager) upsertConfirmedAccountOutputs(ctx context.Context,
236 outs []*accountOutput,
237 pos map[bc.Hash]uint32,
238 block *legacy.Block) error {
241 for _, out := range outs {
242 au = &AccountUTXOs{OutputID: out.OutputID.Bytes(),
243 AssetID: out.AssetId.Bytes(),
245 AccountID: out.AccountID,
246 CpIndex: out.keyIndex,
247 Program: out.ControlProgram,
248 InBlock: block.Height,
249 SourceID: out.sourceID.Bytes(),
250 SourcePos: out.sourcePos,
251 RefData: out.refData.Bytes(),
254 accountutxo, err := json.Marshal(au)
256 return errors.Wrap(err, "failed marshal accountutxo")
259 if len(accountutxo) > 0 {
260 m.pinStore.DB.Set(json.RawMessage("acu"+string(au.OutputID)), accountutxo)