OSDN Git Service

add query
[bytom/bytom.git] / blockchain / account / accounts.go
1 // Package account stores and tracks accounts within a Chain Core.
2 package account
3
4 import (
5         "context"
6 //      stdsql "database/sql"
7 //      "encoding/json"
8         "sync"
9         "time"
10     "fmt"
11
12         "github.com/golang/groupcache/lru"
13         "github.com/lib/pq"
14
15 //      "chain/core/pin"
16         "github.com/bytom/blockchain/signers"
17         "github.com/bytom/blockchain/txbuilder"
18         "github.com/bytom/crypto/ed25519/chainkd"
19 //      "chain/database/pg"
20      dbm "github.com/tendermint/tmlibs/db"
21         "github.com/bytom/errors"
22         "github.com/bytom/log"
23         "github.com/bytom/protocol"
24         "github.com/bytom/protocol/vm/vmutil"
25 )
26
27 const maxAccountCache = 1000
28
29 var (
30         ErrDuplicateAlias = errors.New("duplicate account alias")
31         ErrBadIdentifier  = errors.New("either ID or alias must be specified, and not both")
32 )
33
34 func NewManager(db dbm.DB, chain *protocol.Chain/*, pinStore *pin.Store*/) *Manager {
35         return &Manager{
36                 db:          db,
37                 chain:       chain,
38                 utxoDB:      newReserver(db, chain/*, pinStore*/),
39 //              pinStore:    pinStore,
40                 cache:       lru.New(maxAccountCache),
41                 aliasCache:  lru.New(maxAccountCache),
42                 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*controlProgram),
43         }
44 }
45
46 // Manager stores accounts and their associated control programs.
47 type Manager struct {
48         db       dbm.DB
49         chain    *protocol.Chain
50         utxoDB   *reserver
51         indexer  Saver
52 //      pinStore *pin.Store
53
54         cacheMu    sync.Mutex
55         cache      *lru.Cache
56         aliasCache *lru.Cache
57
58         delayedACPsMu sync.Mutex
59         delayedACPs   map[*txbuilder.TemplateBuilder][]*controlProgram
60
61         acpMu        sync.Mutex
62         acpIndexNext uint64 // next acp index in our block
63         acpIndexCap  uint64 // points to end of block
64 }
65
66 func (m *Manager) IndexAccounts(indexer Saver) {
67         m.indexer = indexer
68 }
69
70 // ExpireReservations removes reservations that have expired periodically.
71 // It blocks until the context is canceled.
72 func (m *Manager) ExpireReservations(ctx context.Context, period time.Duration) {
73         ticks := time.Tick(period)
74         for {
75                 select {
76                 case <-ctx.Done():
77                         log.Printf(ctx, "Deposed, ExpireReservations exiting")
78                         return
79                 case <-ticks:
80                         err := m.utxoDB.ExpireReservations(ctx)
81                         if err != nil {
82                                 log.Error(ctx, err)
83                         }
84                 }
85         }
86 }
87
88 type Account struct {
89         *signers.Signer
90         Alias string
91         Tags  map[string]interface{}
92 }
93
94 // Create creates a new Account.
95 func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string, tags map[string]interface{}, clientToken string) (*Account, error) {
96         signer, err := signers.Create(ctx, m.db, "account", xpubs, quorum, clientToken)
97         if err != nil {
98                 return nil, errors.Wrap(err)
99         }
100 /*
101         tagsParam , err := tagsToNullString(tags)
102         if err != nil {
103                 return nil, err
104         }
105 */
106     var tagsParam []byte
107
108 /*      aliasSQL := stdsql.NullString{
109                 String: alias,
110                 Valid:  alias != "",
111         }
112
113         const q = `
114                 INSERT INTO accounts (account_id, alias, tags) VALUES ($1, $2, $3)
115                 ON CONFLICT (account_id) DO UPDATE SET alias = $2, tags = $3
116         `
117         _, err = m.db.ExecContext(ctx, q, signer.ID, aliasSQL, tagsParam)
118         if pg.IsUniqueViolation(err) {
119                 return nil, errors.WithDetail(ErrDuplicateAlias, "an account with the provided alias already exists")
120         } else if err != nil {
121                 return nil, errors.Wrap(err)
122         }*/
123     account_alias := []byte(fmt.Sprintf("account_alias:%v", signer.ID))
124     account_tags := []byte(fmt.Sprintf("account_tags:%v", signer.ID))
125     m.db.Set(account_alias, []byte(alias))
126     m.db.Set(account_tags, []byte(tagsParam))
127     alias_account := []byte(fmt.Sprintf("alias_account:%v", alias))
128     m.db.Set(alias_account, []byte(signer.ID))
129
130         account := &Account{
131                 Signer: signer,
132                 Alias:  alias,
133                 Tags:   tags,
134         }
135
136         err = m.indexAnnotatedAccount(ctx, account)
137         if err != nil {
138                 return nil, errors.Wrap(err, "indexing annotated account")
139         }
140
141         return account, nil
142 }
143
144 // UpdateTags modifies the tags of the specified account. The account may be
145 // identified either by ID or Alias, but not both.
146 func (m *Manager) UpdateTags(ctx context.Context, id, alias *string, tags map[string]interface{}) error {
147         if (id == nil) == (alias == nil) {
148                 return errors.Wrap(ErrBadIdentifier)
149         }
150 /*
151         tagsParam , err := tagsToNullString(tags)
152         if err != nil {
153                 return errors.Wrap(err, "convert tags")
154         }
155 */
156         var (
157                 signer   *signers.Signer
158                 aliasStr string
159         )
160
161 /*      if id != nil {
162                 signer, err = m.findByID(ctx, *id)
163                 if err != nil {
164                         return errors.Wrap(err, "get account by ID")
165                 }
166
167                 // An alias is required by indexAnnotatedAccount. The latter is a somewhat
168                 // complex function, so in the interest of not making a near-duplicate,
169                 // we'll satisfy its contract and provide an alias.
170                 const q = `SELECT alias FROM accounts WHERE account_id = $1`
171                 var a stdsql.NullString
172                 err := m.db.QueryRowContext(ctx, q, *id).Scan(&a)
173                 if err != nil {
174                         return errors.Wrap(err, "alias lookup")
175                 }
176                 if a.Valid {
177                         aliasStr = a.String
178                 }
179         } else { // alias is guaranteed to be not nil due to bad identifier check
180                 aliasStr = *alias
181                 signer, err = m.FindByAlias(ctx, aliasStr)
182                 if err != nil {
183                         return errors.Wrap(err, "get account by alias")
184                 }
185         }
186
187         const q = `
188                 UPDATE accounts
189                 SET tags = $1
190                 WHERE account_id = $2
191         `
192         _, err = m.db.ExecContext(ctx, q, tagsParam, signer.ID)
193         if err != nil {
194                 return errors.Wrap(err, "update entry in accounts table")
195         }
196 */
197         return errors.Wrap(m.indexAnnotatedAccount(ctx, &Account{
198                 Signer: signer,
199                 Alias:  aliasStr,
200                 Tags:   tags,
201         }), "update account index")
202 }
203
204
205 // FindByAlias retrieves an account's Signer record by its alias
206 func (m *Manager) FindByAlias(ctx context.Context, alias string) (*signers.Signer, error) {
207         var accountID string
208
209         m.cacheMu.Lock()
210         cachedID, ok := m.aliasCache.Get(alias)
211         m.cacheMu.Unlock()
212         if ok {
213                 accountID = cachedID.(string)
214         } else {
215                 /*const q = `SELECT account_id FROM accounts WHERE alias=$1`
216                 err := m.db.QueryRowContext(ctx, q, alias).Scan(&accountID)
217                 if err == stdsql.ErrNoRows {
218                         return nil, errors.WithDetailf(pg.ErrUserInputNotFound, "alias: %s", alias)
219                 }
220                 if err != nil {
221                         return nil, errors.Wrap(err)
222                 }*/
223         bytez := m.db.Get([]byte(fmt.Sprintf("alias_account:%v", alias)))
224         accountID = string(bytez[:])
225                 m.cacheMu.Lock()
226                 m.aliasCache.Add(alias, accountID)
227                 m.cacheMu.Unlock()
228         }
229         return m.findByID(ctx, accountID)
230 }
231
232 // findByID returns an account's Signer record by its ID.
233 func (m *Manager) findByID(ctx context.Context, id string) (*signers.Signer, error) {
234         m.cacheMu.Lock()
235         cached, ok := m.cache.Get(id)
236         m.cacheMu.Unlock()
237         if ok {
238                 return cached.(*signers.Signer), nil
239         }
240         account, err := signers.Find(ctx, m.db, "account", id)
241         if err != nil {
242                 return nil, err
243         }
244         m.cacheMu.Lock()
245         m.cache.Add(id, account)
246         m.cacheMu.Unlock()
247         return account, nil
248 }
249
250 type controlProgram struct {
251         accountID      string
252         keyIndex       uint64
253         controlProgram []byte
254         change         bool
255         expiresAt      time.Time
256 }
257
258 func (m *Manager) createControlProgram(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*controlProgram, error) {
259         account, err := m.findByID(ctx, accountID)
260         if err != nil {
261                 return nil, err
262         }
263
264         idx, err := m.nextIndex(ctx)
265         if err != nil {
266                 return nil, err
267         }
268
269         path := signers.Path(account, signers.AccountKeySpace, idx)
270         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
271         derivedPKs := chainkd.XPubKeys(derivedXPubs)
272         control, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
273         if err != nil {
274                 return nil, err
275         }
276         return &controlProgram{
277                 accountID:      account.ID,
278                 keyIndex:       idx,
279                 controlProgram: control,
280                 change:         change,
281                 expiresAt:      expiresAt,
282         }, nil
283 }
284
285 // CreateControlProgram creates a control program
286 // that is tied to the Account and stores it in the database.
287 func (m *Manager) CreateControlProgram(ctx context.Context, accountID string, change bool, expiresAt time.Time) ([]byte, error) {
288         cp, err := m.createControlProgram(ctx, accountID, change, expiresAt)
289         if err != nil {
290                 return nil, err
291         }
292         err = m.insertAccountControlProgram(ctx, cp)
293         if err != nil {
294                 return nil, err
295         }
296         return cp.controlProgram, nil
297 }
298
299
300 func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*controlProgram) error {
301         const q = `
302                 INSERT INTO account_control_programs (signer_id, key_index, control_program, change, expires_at)
303                 SELECT unnest($1::text[]), unnest($2::bigint[]), unnest($3::bytea[]), unnest($4::boolean[]),
304                         unnest($5::timestamp with time zone[])
305         `
306         var (
307                 accountIDs   pq.StringArray
308                 keyIndexes   pq.Int64Array
309                 controlProgs pq.ByteaArray
310                 change       pq.BoolArray
311         //      expirations  []stdsql.NullString
312         )
313         for _, p := range progs {
314                 accountIDs = append(accountIDs, p.accountID)
315                 keyIndexes = append(keyIndexes, int64(p.keyIndex))
316                 controlProgs = append(controlProgs, p.controlProgram)
317                 change = append(change, p.change)
318         /*      expirations = append(expirations, stdsql.NullString{
319                         String: p.expiresAt.Format(time.RFC3339),
320                         Valid:  !p.expiresAt.IsZero(),
321                 })*/
322         }
323
324 //      _, err := m.dbm.ExecContext(ctx, q, accountIDs, keyIndexes, controlProgs, change, pq.Array(expirations))
325         return errors.Wrap(nil)
326 }
327
328
329 func (m *Manager) nextIndex(ctx context.Context) (uint64, error) {
330         m.acpMu.Lock()
331         defer m.acpMu.Unlock()
332
333         if m.acpIndexNext >= m.acpIndexCap {
334                 /*var cap uint64
335                 const incrby = 10000 // account_control_program_seq increments by 10,000
336                 const q = `SELECT nextval('account_control_program_seq')`
337                 err := m.db.QueryRowContext(ctx, q).Scan(&cap)
338                 if err != nil {
339                         return 0, errors.Wrap(err, "scan")
340                 }
341                 m.acpIndexCap = cap
342                 m.acpIndexNext = cap - incrby*/
343         }
344
345         n := m.acpIndexNext
346         m.acpIndexNext++
347         return n, nil
348 }
349
350 /*
351 func tagsToNullString(tags map[string]interface{}) (*stdsql.NullString, error) {
352         var tagsJSON []byte
353         if len(tags) != 0 {
354                 var err error
355                 tagsJSON, err = json.Marshal(tags)
356                 if err != nil {
357                         return nil, errors.Wrap(err)
358                 }
359         }
360         return &stdsql.NullString{String: string(tagsJSON), Valid: len(tagsJSON) > 0}, nil
361 }
362 */