diff --git a/es/ledger_serializer.go b/es/ledger_serializer.go index 1d5ca44..f2655f9 100644 --- a/es/ledger_serializer.go +++ b/es/ledger_serializer.go @@ -41,6 +41,11 @@ func (s *ledgerSerializer) serialize() { if transaction.Successful { changes := s.feeRows[transaction.Index-1].Changes s.serializeBalances(changes, transaction, nil, BalanceSourceFee) + + h := ProduceSignerHistoryFromTxMeta(transaction) + if h != nil { + SerializeForBulk(h, s.buffer) + } } s.serializeOperations(transactionRow, transaction) @@ -64,7 +69,7 @@ func (s *ledgerSerializer) serializeOperations(transactionRow db.TxHistoryRow, t s.serializeTrades(result, transaction, operation, effectsCount) - h := ProduceSignerHistory(operation) + h := ProduceSignerHistoryFromOperation(operation) if h != nil { SerializeForBulk(h, s.buffer) } diff --git a/es/signer_history.go b/es/signer_history.go index 0093b2e..becc03c 100644 --- a/es/signer_history.go +++ b/es/signer_history.go @@ -1,6 +1,7 @@ package es import ( + "github.com/stellar/go/xdr" "time" ) @@ -17,8 +18,86 @@ type SignerHistory struct { LedgerCloseTime time.Time `json:"ledger_close_time"` } -// ProduceSignerHistory creates new signer history entry -func ProduceSignerHistory(o *Operation) (h *SignerHistory) { +// ProduceSignerHistoryFromTxMeta handles pre-authorized transactions +// +// Stellar allows user to create pre-authorized transactions, which +// can be submitted later by other users. This mechanism is implemented +// using multi-sig. When such pre-authorized transaction is executed, the "signer" +// for this transaction is deleted. We should ingest this change too +// +// You can read about pre-authorized transactions process here: +// https://medium.com/@katopz/stellar-pre-signed-transaction-d93e91191c15 +func ProduceSignerHistoryFromTxMeta(tx *Transaction) (h *SignerHistory) { + currentSigners := make(map[string][]xdr.Signer) + + for _, change := range tx.meta.TxChanges { + if change.EntryType() != xdr.LedgerEntryTypeAccount { + continue + } + + switch t := change.Type; t { + case xdr.LedgerEntryChangeTypeLedgerEntryState: + accountData := change.MustState().Data.MustAccount() + accountId := accountData.AccountId.Address() + + currentSigners[accountId] = accountData.Signers + + case xdr.LedgerEntryChangeTypeLedgerEntryUpdated: + accountData := change.MustUpdated().Data.MustAccount() + accountId := accountData.AccountId.Address() + + if len(currentSigners[accountId]) == len(accountData.Signers) { + continue + } + + for _, signer := range currentSigners[accountId] { + if contains(accountData.Signers, signer) { + continue + } + + s := NewSigner(&signer) + + token := PagingToken{ + LedgerSeq: tx.Seq, + TransactionOrder: tx.Index, + } + + entry := &SignerHistory{ + PagingToken: token, + AccountID: accountId, + Signer: s.ID, + Type: s.Type, + Weight: 0, + TxIndex: tx.Index, + Index: 0, + Seq: tx.Seq, + LedgerCloseTime: tx.CloseTime, + } + + // we can return quickly, because there can be only one + // signer change in pre-auth tx + return entry + } + } + } + + return nil +} + +// contains checks, whether given array of signers contains given +// particular signer or nor +func contains(array []xdr.Signer, signer xdr.Signer) bool { + for _, s := range array { + if s.Key.Address() == signer.Key.Address() && s.Weight == signer.Weight { + return true + } + } + + return false +} + +// ProduceSignerHistoryFromOperation creates new signer history entry +func ProduceSignerHistoryFromOperation(o *Operation) (h *SignerHistory) { s := o.Signer if s == nil { diff --git a/es/transaction.go b/es/transaction.go index f67d259..5bd0168 100644 --- a/es/transaction.go +++ b/es/transaction.go @@ -21,6 +21,8 @@ type Transaction struct { ResultCode int `json:"result_code"` SourceAccountID string `json:"source_account_id"` + meta xdr.TransactionMetaV1 + *TimeBounds `json:"time_bounds,omitempty"` *Memo `json:"memo,omitempty"` } @@ -41,6 +43,7 @@ func NewTransaction(row *db.TxHistoryRow, t time.Time) *Transaction { Successful: resultCode == xdr.TransactionResultCodeTxSuccess, ResultCode: int(resultCode), SourceAccountID: row.Envelope.Tx.SourceAccount.Address(), + meta: row.Meta.MustV1(), } if row.Envelope.Tx.Memo.Type != xdr.MemoTypeMemoNone {