diff --git a/cmd/api/handlers/account.go b/cmd/api/handlers/account.go index 272aaa1b7..bad03e976 100644 --- a/cmd/api/handlers/account.go +++ b/cmd/api/handlers/account.go @@ -3,6 +3,7 @@ package handlers import ( "net/http" + "github.com/baking-bad/bcdhub/internal/bcd" "github.com/baking-bad/bcdhub/internal/config" "github.com/gin-gonic/gin" ) @@ -39,20 +40,24 @@ func GetInfo() gin.HandlerFunc { if handleError(c, ctx.Storage, err, 0) { return } - block, err := ctx.Blocks.Last() - if handleError(c, ctx.Storage, err, 0) { - return - } - balance, err := ctx.Cache.TezosBalance(c, acc.Address, block.Level) - if handleError(c, ctx.Storage, err, 0) { - return + var balance int64 + if !(bcd.IsRollupAddressLazy(acc.Address) || bcd.IsSmartRollupAddressLazy(acc.Address)) { + block, err := ctx.Blocks.Last() + if handleError(c, ctx.Storage, err, 0) { + return + } + balance, err = ctx.Cache.TezosBalance(c, acc.Address, block.Level) + if handleError(c, ctx.Storage, err, 0) { + return + } } c.SecureJSON(http.StatusOK, AccountInfo{ - Address: acc.Address, - Alias: acc.Alias, - TxCount: stats.Count, - Balance: balance, - LastAction: stats.LastAction.UTC(), + Address: acc.Address, + Alias: acc.Alias, + TxCount: stats.Count, + Balance: balance, + LastAction: stats.LastAction.UTC(), + AccountType: acc.Type.String(), }) } diff --git a/cmd/api/handlers/helpers.go b/cmd/api/handlers/helpers.go index 800391569..ce8fa1414 100644 --- a/cmd/api/handlers/helpers.go +++ b/cmd/api/handlers/helpers.go @@ -39,3 +39,7 @@ func ContractsHelpers() gin.HandlerFunc { c.SecureJSON(http.StatusOK, response) } } + +func getStringPointer(s string) *string { + return &s +} diff --git a/cmd/api/handlers/operations.go b/cmd/api/handlers/operations.go index 681e2dbbc..11657d137 100644 --- a/cmd/api/handlers/operations.go +++ b/cmd/api/handlers/operations.go @@ -1,6 +1,7 @@ package handlers import ( + "encoding/hex" "net/http" "strings" @@ -305,11 +306,13 @@ func GetOperationDiff() gin.HandlerFunc { return } - bmd, err := ctx.BigMapDiffs.GetForOperation(operation.ID) - if handleError(c, ctx.Storage, err, 0) { - return + var bmd []bigmapdiff.BigMapDiff + if operation.BigMapDiffsCount > 0 { + bmd, err = ctx.BigMapDiffs.GetForOperation(operation.ID) + if handleError(c, ctx.Storage, err, 0) { + return + } } - if err := setStorageDiff(ctx, operation.DestinationID, operation.DeffatedStorage, &result, bmd, storageType); handleError(c, ctx.Storage, err, 0) { return } @@ -496,27 +499,55 @@ func prepareOperation(ctx *config.Context, operation operation.Operation, bmd [] } op.Protocol = proto.Hash - if operation.IsEvent() { - eventType, err := ast.NewTypedAstFromBytes(operation.EventType) + if err := formatErrors(operation.Errors, &op); err != nil { + return op, err + } + + switch operation.Kind { + case modelTypes.OperationKindEvent, modelTypes.OperationKindTransferTicket: + payloadType, err := ast.NewTypedAstFromBytes(operation.PayloadType) if err != nil { return op, err } - if err := eventType.SettleFromBytes(operation.EventPayload); err != nil { + if err := payloadType.SettleFromBytes(operation.Payload); err != nil { return op, err } - eventMiguel, err := eventType.ToMiguel() + payloadMiguel, err := payloadType.ToMiguel() if err != nil { return op, err } - op.Event = eventMiguel + op.Payload = payloadMiguel return op, err + case modelTypes.OperationKindSrExecuteOutboxMessage: + if len(operation.Payload) >= 32 { + commitment, err := encoding.EncodeBase58(operation.Payload[:32], []byte(encoding.PrefixSmartRollupCommitment)) + if err != nil { + return op, err + } + op.Payload = []*ast.MiguelNode{ + { + Prim: "pair", + Type: "namedtuple", + Children: []*ast.MiguelNode{ + { + Prim: "string", + Type: "string", + Name: getStringPointer("cemented_commitment"), + Value: commitment, + }, { + Prim: "bytes", + Type: "bytes", + Name: getStringPointer("output_proof"), + Value: hex.EncodeToString(operation.Payload[32:]), + }, + }, + }, + } + } + } if bcd.IsContract(op.Destination) { - if err := formatErrors(operation.Errors, &op); err != nil { - return op, err - } - if withStorageDiff { storageType, err := getStorageType(ctx.Contracts, op.Destination, proto.SymLink) if err != nil { @@ -544,6 +575,20 @@ func prepareOperation(ctx *config.Context, operation operation.Operation, bmd [] } } + if bcd.IsSmartRollupHash(op.Destination) && operation.IsTransaction() && operation.IsCall() && !tezerrors.HasParametersError(op.Errors) { + rollup, err := ctx.SmartRollups.Get(op.Destination) + if err != nil { + return op, err + } + tree, err := ast.NewTypedAstFromBytes(rollup.Type) + if err != nil { + return op, err + } + if err := setParameters(operation.Parameters, tree, &op); err != nil { + return op, err + } + } + return op, nil } @@ -554,7 +599,7 @@ func PrepareOperations(ctx *config.Context, ops []operation.Operation, withStora var diffs []bigmapdiff.BigMapDiff var err error - if withStorageDiff { + if withStorageDiff && ops[i].BigMapDiffsCount > 0 { diffs, err = ctx.BigMapDiffs.GetForOperation(ops[i].ID) if err != nil { return nil, err diff --git a/cmd/api/handlers/requests.go b/cmd/api/handlers/requests.go index 1c64df3b4..346a98f81 100644 --- a/cmd/api/handlers/requests.go +++ b/cmd/api/handlers/requests.go @@ -262,3 +262,13 @@ type getViewsArgs struct { type findContract struct { Tags string `form:"tags" binding:"omitempty"` } + +type smartRollupListRequest struct { + pageableRequest + + Sort string `form:"sort" binding:"omitempty,oneof=asc desc"` +} + +type getSmartRollupRequest struct { + Address string `uri:"address" binding:"required,smart_rollup"` +} diff --git a/cmd/api/handlers/responses.go b/cmd/api/handlers/responses.go index f1bb234c2..e06b89f94 100644 --- a/cmd/api/handlers/responses.go +++ b/cmd/api/handlers/responses.go @@ -1,6 +1,7 @@ package handlers import ( + "encoding/hex" stdJSON "encoding/json" "time" @@ -13,6 +14,7 @@ import ( "github.com/baking-bad/bcdhub/internal/models/contract" "github.com/baking-bad/bcdhub/internal/models/operation" "github.com/baking-bad/bcdhub/internal/models/protocol" + smartrollup "github.com/baking-bad/bcdhub/internal/models/smart_rollup" "github.com/baking-bad/bcdhub/internal/models/ticket" "github.com/baking-bad/bcdhub/internal/models/types" ) @@ -39,10 +41,12 @@ type Operation struct { ConsumedGas int64 `json:"consumed_gas,omitempty" extensions:"x-nullable" example:"100"` StorageSize int64 `json:"storage_size,omitempty" extensions:"x-nullable" example:"200"` PaidStorageSizeDiff int64 `json:"paid_storage_size_diff,omitempty" extensions:"x-nullable" example:"300"` + TicketUpdatesCount int `json:"ticket_updates_count"` + BigMapDiffsCount int `json:"big_map_diffs_count"` Errors []*tezerrors.Error `json:"errors,omitempty" extensions:"x-nullable"` Parameters interface{} `json:"parameters,omitempty" extensions:"x-nullable"` StorageDiff *ast.MiguelNode `json:"storage_diff,omitempty" extensions:"x-nullable"` - Event []*ast.MiguelNode `json:"event,omitempty" extensions:"x-nullable"` + Payload []*ast.MiguelNode `json:"payload,omitempty" extensions:"x-nullable"` RawMempool interface{} `json:"rawMempool,omitempty" extensions:"x-nullable"` Timestamp time.Time `json:"timestamp"` Protocol string `json:"protocol"` @@ -96,6 +100,8 @@ func (o *Operation) FromModel(operation operation.Operation) { o.StorageSize = operation.StorageSize o.PaidStorageSizeDiff = operation.PaidStorageSizeDiff o.AllocatedDestinationContract = operation.AllocatedDestinationContract + o.TicketUpdatesCount = operation.TicketUpdatesCount + o.BigMapDiffsCount = operation.BigMapDiffsCount } // ToModel - @@ -493,11 +499,12 @@ type Screenshot struct { // AccountInfo - type AccountInfo struct { - Address string `json:"address"` - Alias string `json:"alias,omitempty" extensions:"x-nullable"` - Balance int64 `json:"balance"` - TxCount int64 `json:"tx_count"` - LastAction time.Time `json:"last_action"` + Address string `json:"address"` + Alias string `json:"alias,omitempty" extensions:"x-nullable"` + Balance int64 `json:"balance"` + TxCount int64 `json:"tx_count"` + LastAction time.Time `json:"last_action"` + AccountType string `json:"account_type"` } // CountResponse - @@ -643,7 +650,7 @@ type Event struct { // NewEvent - func NewEvent(o operation.Operation) (*Event, error) { - if !o.IsEvent() { + if o.Kind != types.OperationKindEvent { return nil, nil } @@ -660,11 +667,11 @@ func NewEvent(o operation.Operation) (*Event, error) { Tag: o.Tag.String(), } - eventType, err := ast.NewTypedAstFromBytes(o.EventType) + eventType, err := ast.NewTypedAstFromBytes(o.PayloadType) if err != nil { return nil, err } - if err := eventType.SettleFromBytes(o.EventPayload); err != nil { + if err := eventType.SettleFromBytes(o.Payload); err != nil { return nil, err } eventMiguel, err := eventType.ToMiguel() @@ -699,3 +706,31 @@ func NewTicketUpdateFromModel(update ticket.TicketUpdate) TicketUpdate { Amount: update.Amount.String(), } } + +// SmartRollup - +type SmartRollup struct { + ID int64 `json:"id"` + Level int64 `json:"level"` + Timestamp time.Time `json:"timestamp"` + Size uint64 `json:"size"` + Address string `json:"address"` + GenesisCommitmentHash string `json:"genesis_commitment_hash"` + PvmKind string `json:"pvm_kind"` + Kernel string `json:"kernel"` + Type []ast.Typedef `json:"type"` +} + +// NewSmartRollup - +func NewSmartRollup(rollup smartrollup.SmartRollup) SmartRollup { + kernel := hex.EncodeToString(rollup.Kernel) + return SmartRollup{ + ID: rollup.ID, + Level: rollup.Level, + Timestamp: rollup.Timestamp, + Size: rollup.Size, + Address: rollup.Address.Address, + GenesisCommitmentHash: rollup.GenesisCommitmentHash, + PvmKind: rollup.PvmKind, + Kernel: kernel, + } +} diff --git a/cmd/api/handlers/smart_rollup.go b/cmd/api/handlers/smart_rollup.go new file mode 100644 index 000000000..7495ee29f --- /dev/null +++ b/cmd/api/handlers/smart_rollup.go @@ -0,0 +1,98 @@ +package handlers + +import ( + "net/http" + + "github.com/baking-bad/bcdhub/internal/bcd/ast" + "github.com/baking-bad/bcdhub/internal/config" + "github.com/gin-gonic/gin" +) + +// GetSmartRollup godoc +// @Summary Get smart rollup +// @Description Get smart rollup +// @Tags smart-rollups +// @ID get-smart-rollups +// @Param network path string true "network" +// @Param address path string true "expr address of smart rollup" minlength(36) maxlength(36) +// @Accept json +// @Produce json +// @Success 200 {object} SmartRollup +// @Failure 400 {object} Error +// @Failure 404 {object} Error +// @Failure 500 {object} Error +// @Router /v1/smart_rollups/{network}/{address} [get] +func GetSmartRollup() gin.HandlerFunc { + return func(c *gin.Context) { + ctx := c.MustGet("context").(*config.Context) + + var req getSmartRollupRequest + if err := c.BindUri(&req); handleError(c, ctx.Storage, err, http.StatusBadRequest) { + return + } + + rollup, err := ctx.SmartRollups.Get(req.Address) + if handleError(c, ctx.Storage, err, 0) { + return + } + + response := NewSmartRollup(rollup) + typ, err := ast.NewTypedAstFromBytes(rollup.Type) + if handleError(c, ctx.Storage, err, 0) { + return + } + docs, err := typ.Docs("") + if handleError(c, ctx.Storage, err, 0) { + return + } + response.Type = docs + c.SecureJSON(http.StatusOK, response) + } +} + +// ListSmartRollups godoc +// @Summary List smart rollups +// @Description List smart rollups +// @Tags smart-rollups +// @ID list-smart-rollups +// @Param network path string true "network" +// @Param size query integer false "Constants count" mininum(1) maximum(10) +// @Param offset query integer false "Offset" mininum(1) +// @Param sort query string false "Sort order" Enums(asc, desc) +// @Accept json +// @Produce json +// @Success 200 {array} SmartRollup +// @Failure 400 {object} Error +// @Failure 404 {object} Error +// @Failure 500 {object} Error +// @Router /v1/smart_rollups/{network} [get] +func ListSmartRollups() gin.HandlerFunc { + return func(c *gin.Context) { + ctx := c.MustGet("context").(*config.Context) + + var args smartRollupListRequest + if err := c.BindQuery(&args); handleError(c, ctx.Storage, err, http.StatusBadRequest) { + return + } + + rollups, err := ctx.SmartRollups.List(args.Size, args.Offset, args.Sort) + if handleError(c, ctx.Storage, err, 0) { + return + } + response := make([]SmartRollup, len(rollups)) + for i := range rollups { + response[i] = NewSmartRollup(rollups[i]) + + typ, err := ast.NewTypedAstFromBytes(rollups[i].Type) + if handleError(c, ctx.Storage, err, 0) { + return + } + docs, err := typ.Docs("") + if handleError(c, ctx.Storage, err, 0) { + return + } + response[i].Type = docs + } + c.SecureJSON(http.StatusOK, response) + } +} diff --git a/cmd/api/handlers/tickets.go b/cmd/api/handlers/tickets.go index d96a07f32..20fea5b50 100644 --- a/cmd/api/handlers/tickets.go +++ b/cmd/api/handlers/tickets.go @@ -6,6 +6,7 @@ import ( "github.com/baking-bad/bcdhub/internal/bcd/ast" "github.com/baking-bad/bcdhub/internal/bcd/encoding" "github.com/baking-bad/bcdhub/internal/config" + "github.com/baking-bad/bcdhub/internal/models/ticket" "github.com/gin-gonic/gin" ) @@ -43,43 +44,79 @@ func GetContractTicketUpdates() gin.HandlerFunc { if handleError(c, ctx.Storage, err, 0) { return } + response, err := prepareTicketUpdates(ctx, updates, nil) + if handleError(c, ctx.Storage, err, 0) { + return + } + c.SecureJSON(http.StatusOK, response) + } +} - response := make([]TicketUpdate, 0, len(updates)) - for i := range updates { - update := NewTicketUpdateFromModel(updates[i]) +// GetTicketUpdatesForOperation - +// @Router /v1/operation/{network}/{id}/ticket_updates [get] +func GetTicketUpdatesForOperation() gin.HandlerFunc { + return func(c *gin.Context) { + ctx := c.MustGet("context").(*config.Context) - content, err := ast.NewTypedAstFromBytes(updates[i].ContentType) - if handleError(c, ctx.Storage, err, 0) { - return - } - docs, err := content.Docs("") - if handleError(c, ctx.Storage, err, 0) { - return - } - update.ContentType = docs + var req getOperationByIDRequest + if err := c.BindUri(&req); handleError(c, ctx.Storage, err, http.StatusBadRequest) { + return + } + operation, err := ctx.Operations.GetByID(req.ID) + if handleError(c, ctx.Storage, err, 0) { + return + } + updates, err := ctx.TicketUpdates.ForOperation(req.ID) + if handleError(c, ctx.Storage, err, 0) { + return + } + response, err := prepareTicketUpdates(ctx, updates, operation.Hash) + if handleError(c, ctx.Storage, err, 0) { + return + } + c.SecureJSON(http.StatusOK, response) + } +} - if err := content.SettleFromBytes(updates[i].Content); handleError(c, ctx.Storage, err, 0) { - return - } - contentMiguel, err := content.ToMiguel() - if handleError(c, ctx.Storage, err, 0) { - return - } - if len(contentMiguel) > 0 { - update.Content = contentMiguel[0] - } +func prepareTicketUpdates(ctx *config.Context, updates []ticket.TicketUpdate, hash []byte) ([]TicketUpdate, error) { + response := make([]TicketUpdate, 0, len(updates)) + for i := range updates { + update := NewTicketUpdateFromModel(updates[i]) + + content, err := ast.NewTypedAstFromBytes(updates[i].ContentType) + if err != nil { + return nil, err + } + docs, err := content.Docs("") + if err != nil { + return nil, err + } + update.ContentType = docs + + if err := content.SettleFromBytes(updates[i].Content); err != nil { + return nil, err + } + contentMiguel, err := content.ToMiguel() + if err != nil { + return nil, err + } + if len(contentMiguel) > 0 { + update.Content = contentMiguel[0] + } + if len(hash) == 0 { operation, err := ctx.Operations.GetByID(updates[i].OperationID) - if handleError(c, ctx.Storage, err, 0) { - return + if err != nil { + return nil, err } - if len(operation.Hash) > 0 { - update.OperationHash = encoding.MustEncodeOperationHash(operation.Hash) - } - - response = append(response, update) + hash = operation.Hash + } + if len(hash) > 0 { + update.OperationHash = encoding.MustEncodeOperationHash(hash) } - c.SecureJSON(http.StatusOK, response) + response = append(response, update) } + + return response, nil } diff --git a/cmd/api/main.go b/cmd/api/main.go index f1277f924..402e70dc7 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -124,6 +124,7 @@ func (api *app) makeRouter() { { operation.GET("error_location", handlers.GetOperationErrorLocation()) operation.GET("diff", handlers.GetOperationDiff()) + operation.GET("ticket_updates", handlers.GetTicketUpdatesForOperation()) } stats := v1.Group("stats") @@ -213,6 +214,13 @@ func (api *app) makeRouter() { globalConstant.GET("contracts", handlers.GetGlobalConstantContracts()) } } + + smartRollups := v1.Group("smart_rollups/:network") + smartRollups.Use(handlers.NetworkMiddleware(api.Contexts)) + { + smartRollups.GET("", handlers.ListSmartRollups()) + smartRollups.GET(":address", handlers.GetSmartRollup()) + } } api.Router = r } diff --git a/cmd/api/validations/validations.go b/cmd/api/validations/validations.go index fe4259c5b..806098827 100644 --- a/cmd/api/validations/validations.go +++ b/cmd/api/validations/validations.go @@ -62,6 +62,10 @@ func Register(v *validator.Validate, cfg config.APIConfig) error { return err } + if err := v.RegisterValidation("smart_rollup", smartRollupValidator()); err != nil { + return err + } + return nil } @@ -71,6 +75,12 @@ func addressValidator() validator.Func { } } +func smartRollupValidator() validator.Func { + return func(fl validator.FieldLevel) bool { + return bcd.IsSmartRollupHash(fl.Field().String()) + } +} + func contractValidator() validator.Func { return func(fl validator.FieldLevel) bool { return bcd.IsContract(fl.Field().String()) diff --git a/cmd/indexer/indexer/indexer.go b/cmd/indexer/indexer/indexer.go index 2cf75a1ab..c2ca7d62a 100644 --- a/cmd/indexer/indexer/indexer.go +++ b/cmd/indexer/indexer/indexer.go @@ -81,6 +81,9 @@ func NewBlockchainIndexer(ctx context.Context, cfg config.Config, network string // Close - func (bi *BlockchainIndexer) Close() error { close(bi.refreshTimer) + if err := bi.receiver.Close(); err != nil { + return nil + } return bi.Context.Close() } @@ -242,9 +245,9 @@ func (bi *BlockchainIndexer) Index(ctx context.Context, head noderpc.Header) err } func (bi *BlockchainIndexer) handleBlock(ctx context.Context, block *Block) error { + start := time.Now() return bi.StorageDB.DB.RunInTransaction(ctx, func(tx *pg.Tx) error { - logger.Info().Str("network", bi.Network.String()).Int64("block", block.Header.Level).Msg("indexing") if block.Header.Protocol != bi.currentProtocol.Hash || (bi.Network == types.Mainnet && block.Header.Level == 1) { logger.Info().Str("network", bi.Network.String()).Msgf("New protocol detected: %s -> %s", bi.currentProtocol.Hash, block.Header.Protocol) @@ -270,6 +273,8 @@ func (bi *BlockchainIndexer) handleBlock(ctx context.Context, block *Block) erro if err := bi.createBlock(block.Header, tx); err != nil { return err } + + logger.Info().Str("network", bi.Network.String()).Int64("processing_time_ms", time.Since(start).Milliseconds()).Int64("block", block.Header.Level).Msg("indexed") return nil }, ) diff --git a/cmd/indexer/indexer/indices.go b/cmd/indexer/indexer/indices.go index 7db5a97e8..7e836f90a 100644 --- a/cmd/indexer/indexer/indices.go +++ b/cmd/indexer/indexer/indices.go @@ -11,6 +11,7 @@ import ( "github.com/baking-bad/bcdhub/internal/models/contract" "github.com/baking-bad/bcdhub/internal/models/migration" "github.com/baking-bad/bcdhub/internal/models/operation" + "github.com/baking-bad/bcdhub/internal/models/ticket" "github.com/go-pg/pg/v10" ) @@ -231,4 +232,17 @@ func (bi *BlockchainIndexer) createIndices() { `); err != nil { logger.Error().Err(err).Msg("can't create index") } + + // Ticket updates + if _, err := bi.Context.StorageDB.DB.Model(new(ticket.TicketUpdate)).Exec(` + CREATE INDEX CONCURRENTLY IF NOT EXISTS ticket_updates_operation_id_idx ON ?TableName (operation_id) + `); err != nil { + logger.Error().Err(err).Msg("can't create index") + } + + if _, err := bi.Context.StorageDB.DB.Model(new(ticket.TicketUpdate)).Exec(` + CREATE INDEX CONCURRENTLY IF NOT EXISTS ticket_updates_ticketer_id_idx ON ?TableName (ticketer_id) + `); err != nil { + logger.Error().Err(err).Msg("can't create index") + } } diff --git a/cmd/indexer/indexer/receiver.go b/cmd/indexer/indexer/receiver.go index f232e9b57..39d15db5b 100644 --- a/cmd/indexer/indexer/receiver.go +++ b/cmd/indexer/indexer/receiver.go @@ -2,10 +2,11 @@ package indexer import ( "context" - "sync" "github.com/baking-bad/bcdhub/internal/logger" "github.com/baking-bad/bcdhub/internal/noderpc" + "github.com/dipdup-io/workerpool" + "github.com/pkg/errors" ) // Block - @@ -17,15 +18,10 @@ type Block struct { // Receiver - type Receiver struct { - rpc noderpc.INode - queue chan int64 - failed chan int64 - blocks chan *Block - - threads chan struct{} - present map[int64]struct{} - mx sync.RWMutex - wg sync.WaitGroup + rpc noderpc.INode + blocks chan *Block + pool *workerpool.Pool[int64] + inProcess Map[int64, struct{}] } // NewReceiver - @@ -36,36 +32,37 @@ func NewReceiver(rpc noderpc.INode, queueSize, threadsCount int64) *Receiver { if threadsCount == 0 { threadsCount = 2 } - return &Receiver{ - rpc: rpc, - queue: make(chan int64, queueSize), - failed: make(chan int64, queueSize), - blocks: make(chan *Block, queueSize), - threads: make(chan struct{}, threadsCount), - present: make(map[int64]struct{}), + receiver := &Receiver{ + rpc: rpc, + blocks: make(chan *Block, queueSize), + inProcess: NewMap[int64, struct{}](), } + receiver.pool = workerpool.NewPool(receiver.job, int(threadsCount)) + return receiver } // AddTask - func (r *Receiver) AddTask(level int64) { - r.mx.RLock() - if _, ok := r.present[level]; ok { - r.mx.RUnlock() + if exists := r.inProcess.Exists(level); exists { return } - r.mx.RUnlock() - - r.mx.Lock() - { - r.present[level] = struct{}{} - } - r.mx.Unlock() - r.queue <- level + r.inProcess.Set(level, struct{}{}) + r.pool.AddTask(level) } // Start - func (r *Receiver) Start(ctx context.Context) { - go r.start(ctx) + r.pool.Start(ctx) +} + +// Close - +func (r *Receiver) Close() error { + if err := r.pool.Close(); err != nil { + return err + } + + close(r.blocks) + return nil } // Blocks - @@ -73,24 +70,6 @@ func (r *Receiver) Blocks() <-chan *Block { return r.blocks } -func (r *Receiver) start(ctx context.Context) { - for { - select { - case <-ctx.Done(): - r.wg.Wait() - close(r.threads) - close(r.blocks) - close(r.failed) - close(r.queue) - return - case level := <-r.failed: - r.job(ctx, level) - case level := <-r.queue: - r.job(ctx, level) - } - } -} - func (r *Receiver) get(ctx context.Context, level int64) (Block, error) { var block Block header, err := r.rpc.Block(ctx, level) @@ -114,28 +93,14 @@ func (r *Receiver) get(ctx context.Context, level int64) (Block, error) { } func (r *Receiver) job(ctx context.Context, level int64) { - r.threads <- struct{}{} - r.wg.Add(1) - go func() { - defer func() { - <-r.threads - r.wg.Done() - }() - - block, err := r.get(ctx, level) - if err != nil { - if ctx.Err() == nil { - logger.Error().Int64("block", level).Err(err).Msg("Receiver.get") - r.failed <- level - } - return - } - r.blocks <- &block - - r.mx.Lock() - { - delete(r.present, level) + block, err := r.get(ctx, level) + if err != nil { + if !errors.Is(err, context.Canceled) { + logger.Error().Int64("block", level).Err(err).Msg("Receiver.get") + r.pool.AddTask(level) } - r.mx.Unlock() - }() + return + } + r.blocks <- &block + r.inProcess.Delete(level) } diff --git a/cmd/indexer/indexer/sync_map.go b/cmd/indexer/indexer/sync_map.go new file mode 100644 index 000000000..5f178e527 --- /dev/null +++ b/cmd/indexer/indexer/sync_map.go @@ -0,0 +1,47 @@ +package indexer + +import "sync" + +// Map - +type Map[K comparable, V any] struct { + m map[K]V + mx *sync.RWMutex +} + +// NewMap - +func NewMap[K comparable, V any]() Map[K, V] { + return Map[K, V]{ + m: make(map[K]V), + mx: new(sync.RWMutex), + } +} + +// Set - +func (m Map[K, V]) Set(key K, value V) { + m.mx.Lock() + m.m[key] = value + m.mx.Unlock() +} + +// Get - +func (m Map[K, V]) Get(key K) (V, bool) { + m.mx.RLock() + value, ok := m.m[key] + m.mx.RUnlock() + return value, ok +} + +// Exists - +func (m Map[K, V]) Exists(key K) bool { + m.mx.RLock() + _, ok := m.m[key] + m.mx.RUnlock() + return ok +} + +// Delete - +func (m Map[K, V]) Delete(key K) { + m.mx.Lock() + delete(m.m, key) + m.mx.Unlock() +} diff --git a/configs/development.yml b/configs/development.yml index fc7d0c049..0bd948ddb 100644 --- a/configs/development.yml +++ b/configs/development.yml @@ -6,11 +6,11 @@ rpc: ghostnet: uri: https://rpc.tzkt.io/ghostnet timeout: 20 - requests_per_second: 10 + requests_per_second: 15 nairobinet: uri: https://rpc.tzkt.io/nairobinet timeout: 20 - requests_per_second: 20 + requests_per_second: 15 tzkt: mainnet: @@ -73,11 +73,11 @@ indexer: sentry_enabled: false networks: mainnet: - receiver_threads: 5 + receiver_threads: 15 ghostnet: - receiver_threads: 10 + receiver_threads: 15 nairobinet: - receiver_threads: 10 + receiver_threads: 15 connections: max: 5 idle: 5 diff --git a/configs/production.yml b/configs/production.yml index f92aa5f3d..6ff2ebe00 100644 --- a/configs/production.yml +++ b/configs/production.yml @@ -2,7 +2,7 @@ rpc: mainnet: uri: ${MAINNET_RPC_URI:-https://rpc.tzkt.io/mainnet} timeout: 20 - requests_per_second: 15 + requests_per_second: 10 ghostnet: uri: https://rpc.tzkt.io/ghostnet timeout: 20 @@ -10,7 +10,7 @@ rpc: nairobinet: uri: https://rpc.tzkt.io/nairobinet timeout: 20 - requests_per_second: 20 + requests_per_second: 10 tzkt: mainnet: @@ -74,11 +74,11 @@ indexer: sentry_enabled: true networks: mainnet: - receiver_threads: ${MAINNET_THREADS:-1} + receiver_threads: ${MAINNET_THREADS:-10} ghostnet: - receiver_threads: ${TESTNET_THREADS:-1} + receiver_threads: ${TESTNET_THREADS:-10} nairobinet: - receiver_threads: ${TESTNET_THREADS:-1} + receiver_threads: ${TESTNET_THREADS:-10} connections: max: 5 idle: 5 diff --git a/go.mod b/go.mod index f6fe4334e..51194bafa 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.44.92 github.com/btcsuite/btcutil v1.0.2 + github.com/dipdup-io/workerpool v0.0.3 github.com/ebellocchia/go-base58 v0.1.0 github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.13.0 diff --git a/go.sum b/go.sum index 3c0bdd2dc..55751557e 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dipdup-io/workerpool v0.0.3 h1:+cnO0/J0e4UiJ0EBEDpvuhriSDVHlsPminGRU2Il+ZI= +github.com/dipdup-io/workerpool v0.0.3/go.mod h1:m6YMqx7M+fORTyabHD/auKymBRpbDax0t1aPZ1i7GZA= github.com/ebellocchia/go-base58 v0.1.0 h1:0w/ODEfZnOPW5KW0QY/Xpb1fxba/BxQJMUa5iYzpljk= github.com/ebellocchia/go-base58 v0.1.0/go.mod h1:RHE/6C6Ru6YAH9Tc+A9eHQ6ZKEooLC0jw+YLnpt3CAU= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/internal/bcd/ast/jsonschema.go b/internal/bcd/ast/jsonschema.go index 95cc3ab36..ba731e5d0 100644 --- a/internal/bcd/ast/jsonschema.go +++ b/internal/bcd/ast/jsonschema.go @@ -3,6 +3,7 @@ package ast import ( "encoding/hex" "math/big" + "strings" "github.com/baking-bad/bcdhub/internal/bcd/types" "github.com/pkg/errors" @@ -94,7 +95,7 @@ func setBytesJSONSchema(d *Default, data map[string]interface{}) error { if err := BytesValidator(str); err != nil { return err } - if _, err := hex.DecodeString(str); err != nil { + if _, err := hex.DecodeString(strings.TrimPrefix(str, "0x")); err != nil { return errors.Errorf("bytes decoding error: %s=%v", key, value) } diff --git a/internal/bcd/ast/validators.go b/internal/bcd/ast/validators.go index 27d73111f..47b2f96d2 100644 --- a/internal/bcd/ast/validators.go +++ b/internal/bcd/ast/validators.go @@ -23,7 +23,8 @@ type ValidatorConstraint interface { type Validator[T ValidatorConstraint] func(T) error var ( - hexRegex = regexp.MustCompile("^[0-9a-fA-F]+$") + hexRegex = regexp.MustCompile("^[0-9a-fA-F]+$") + hexWithPrefixRegex = regexp.MustCompile("^(0x)?[0-9a-fA-F]+$") ) // AddressValidator - @@ -124,7 +125,7 @@ func BytesValidator(value string) error { if len(value)%2 > 0 { return errors.Wrapf(ErrValidation, "invalid bytes in hex length '%s'", value) } - if value != "" && !hexRegex.MatchString(value) { + if value != "" && !hexWithPrefixRegex.MatchString(value) { return errors.Wrapf(ErrValidation, "bytes '%s' should be hexademical without prefixes", value) } return nil diff --git a/internal/bcd/ast/validators_test.go b/internal/bcd/ast/validators_test.go index 51fbfe9fb..4d61fbd31 100644 --- a/internal/bcd/ast/validators_test.go +++ b/internal/bcd/ast/validators_test.go @@ -124,7 +124,7 @@ func TestBytesValidator(t *testing.T) { }, { name: "test 3", value: "0x030ed412d33412ab4b71df0aaba07df7ddd2a44eb55c87bf81868ba09a358bc0e0", - wantErr: true, + wantErr: false, }, { name: "test 4", value: "", diff --git a/internal/bcd/consts/const.go b/internal/bcd/consts/const.go index 4277a15cb..96d2df851 100644 --- a/internal/bcd/consts/const.go +++ b/internal/bcd/consts/const.go @@ -10,6 +10,10 @@ const ( RegisterGlobalConstant = "register_global_constant" TxRollupOrigination = "tx_rollup_origination" Event = "event" + TransferTicket = "transfer_ticket" + SrOriginate = "smart_rollup_originate" + SrExecuteOutboxMessage = "smart_rollup_execute_outbox_message" + Delegation = "delegation" ) // Error IDs diff --git a/internal/bcd/encoding/base58.go b/internal/bcd/encoding/base58.go index d6d1a937e..9e5cadc52 100644 --- a/internal/bcd/encoding/base58.go +++ b/internal/bcd/encoding/base58.go @@ -58,6 +58,10 @@ const ( PrefixSecp256k1EncryptedSecretKey = "spesk" PrefixP256EncryptedSecretKey = "p2esk" PrefixBakerHash = "SG1" + PrefixSmartRollupCommitment = "src1" + PrefixSmartRollupState = "srs1" + PrefixSmartRollupInbox = "srib1" + PrefixSmartRollupMerkelizedTree = "srib2" ) var base58Encodings = []base58Encoding{ @@ -109,6 +113,10 @@ var base58Encodings = []base58Encoding{ {[]byte(PrefixP256EncryptedSecretKey), 88, []byte{9, 48, 57, 115, 171}, 56, "p256_encrypted_secret_key"}, {[]byte(PrefixBakerHash), 36, []byte{3, 56, 226}, 20, "baker hash"}, + {[]byte(PrefixSmartRollupCommitment), 54, []byte{17, 165, 134, 138}, 32, "smart rollup commitment hash"}, + {[]byte(PrefixSmartRollupState), 54, []byte{17, 165, 235, 240}, 32, "smart rollup state hash"}, + {[]byte(PrefixSmartRollupInbox), 55, []byte{3, 255, 138, 145, 110}, 32, "smart rollup inbox hash"}, + {[]byte(PrefixSmartRollupMerkelizedTree), 55, []byte{3, 255, 138, 145, 140}, 32, "smart rollup merkelized tree hash"}, } func getBase58EncodingForDecode(data []byte) (base58Encoding, error) { diff --git a/internal/bcd/encoding/base58_test.go b/internal/bcd/encoding/base58_test.go index 5e014346b..9a4d177ed 100644 --- a/internal/bcd/encoding/base58_test.go +++ b/internal/bcd/encoding/base58_test.go @@ -46,6 +46,11 @@ func TestDecodeBase58String(t *testing.T) { data: "txr1YNMEtkj5Vkqsbdmt7xaxBTMRZjzS96UAi", want: "76a57f87ee7624b92ab1453c75ba5d29ed8fe0bf", }, + { + name: "smart rollup commitment hash", + data: "src13MtM1eBzxCH1FBhLAkAiWGW6JbjvycLeH6vuz5k9GSiTYTCTja", + want: "751b92ce705ebc551917bb488310498e969d7a1261fda86b509e7da2c780ec8d", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -171,6 +176,12 @@ func TestEncodeBase58String(t *testing.T) { prefix: "txr1", want: "txr1YNMEtkj5Vkqsbdmt7xaxBTMRZjzS96UAi", }, + { + name: "smart rollup commitment hash", + prefix: "src1", + data: "751b92ce705ebc551917bb488310498e969d7a1261fda86b509e7da2c780ec8d", + want: "src13MtM1eBzxCH1FBhLAkAiWGW6JbjvycLeH6vuz5k9GSiTYTCTja", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/bcd/literal.go b/internal/bcd/literal.go index bc48a92ca..02dfd336d 100644 --- a/internal/bcd/literal.go +++ b/internal/bcd/literal.go @@ -37,11 +37,17 @@ func IsRollupAddressLazy(address string) bool { return len(address) == 37 && strings.HasPrefix(address, "txr") } +// IsRollupAddressLazy - +func IsSmartRollupAddressLazy(address string) bool { + return len(address) == 36 && strings.HasPrefix(address, "sr1") +} + var ( - addressRegex = regexp.MustCompile("(tz|KT|txr)[0-9A-Za-z]{34}") - contractRegex = regexp.MustCompile("(KT1)[0-9A-Za-z]{33}") - bakerHashRegex = regexp.MustCompile("(SG1)[0-9A-Za-z]{33}") - operationRegex = regexp.MustCompile("^o[1-9A-HJ-NP-Za-km-z]{50}$") + addressRegex = regexp.MustCompile("(tz|KT|txr|sr)[0-9A-Za-z]{34}") + contractRegex = regexp.MustCompile("(KT1)[0-9A-Za-z]{33}") + bakerHashRegex = regexp.MustCompile("(SG1)[0-9A-Za-z]{33}") + operationRegex = regexp.MustCompile("^o[1-9A-HJ-NP-Za-km-z]{50}$") + smartRollupRegex = regexp.MustCompile("(sr)[0-9A-Za-z]{34}") ) // IsAddress - @@ -63,3 +69,8 @@ func IsBakerHash(str string) bool { func IsOperationHash(str string) bool { return operationRegex.MatchString(str) } + +// IsSmartRollupHash - +func IsSmartRollupHash(str string) bool { + return smartRollupRegex.MatchString(str) +} diff --git a/internal/bcd/literal_test.go b/internal/bcd/literal_test.go index c9056f70e..05d3d9457 100644 --- a/internal/bcd/literal_test.go +++ b/internal/bcd/literal_test.go @@ -59,6 +59,10 @@ func TestIsAddress(t *testing.T) { name: "txr1YNMEtkj5Vkqsbdmt7xaxBTMRZjzS96UA", address: "txr1YNMEtkj5Vkqsbdmt7xaxBTMRZjzS96UA", want: false, + }, { + name: "sr1J1ECygUgzE7urU3Ayr5HZaty83hpjbs28", + address: "sr1J1ECygUgzE7urU3Ayr5HZaty83hpjbs28", + want: true, }, } for _, tt := range tests { diff --git a/internal/config/config.go b/internal/config/config.go index 82a9c5d32..2426d6434 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -56,6 +56,7 @@ type RPCConfig struct { Timeout int `yaml:"timeout"` Cache string `yaml:"cache"` RequestsPerSecond int `yaml:"requests_per_second"` + Log bool `yaml:"log"` } // TzKTConfig - diff --git a/internal/config/context.go b/internal/config/context.go index 18ac661c4..b41e2ad39 100644 --- a/internal/config/context.go +++ b/internal/config/context.go @@ -13,6 +13,7 @@ import ( "github.com/baking-bad/bcdhub/internal/models/migration" "github.com/baking-bad/bcdhub/internal/models/operation" "github.com/baking-bad/bcdhub/internal/models/protocol" + smartrollup "github.com/baking-bad/bcdhub/internal/models/smart_rollup" "github.com/baking-bad/bcdhub/internal/models/ticket" "github.com/baking-bad/bcdhub/internal/models/types" "github.com/baking-bad/bcdhub/internal/noderpc" @@ -47,6 +48,7 @@ type Context struct { TicketUpdates ticket.Repository Domains domains.Repository Scripts contract.ScriptRepository + SmartRollups smartrollup.Repository Partitions *postgres.PartitionManager Cache *cache.Cache diff --git a/internal/config/options.go b/internal/config/options.go index 0668b7840..d0f1433b2 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -13,6 +13,7 @@ import ( "github.com/baking-bad/bcdhub/internal/postgres/migration" "github.com/baking-bad/bcdhub/internal/postgres/operation" "github.com/baking-bad/bcdhub/internal/postgres/protocol" + smartrollup "github.com/baking-bad/bcdhub/internal/postgres/smart_rollup" "github.com/baking-bad/bcdhub/internal/postgres/ticket" "github.com/baking-bad/bcdhub/internal/services/mempool" @@ -37,6 +38,9 @@ func WithRPC(rpcConfig map[string]RPCConfig) ContextOption { noderpc.WithTimeout(time.Second * time.Duration(rpcProvider.Timeout)), noderpc.WithRateLimit(rpcProvider.RequestsPerSecond), } + if rpcProvider.Log { + opts = append(opts, noderpc.WithLog()) + } ctx.RPC = noderpc.NewNodeRPC(rpcProvider.URI, opts...) } @@ -80,6 +84,7 @@ func WithStorage(cfg StorageConfig, appName string, maxPageSize int64, maxConnCo ctx.Domains = domains.NewStorage(conn) ctx.TicketUpdates = ticket.NewStorage(conn) ctx.Scripts = contractStorage + ctx.SmartRollups = smartrollup.NewStorage(conn) ctx.Partitions = postgres.NewPartitionManager(conn) } } diff --git a/internal/models/account/repository.go b/internal/models/account/repository.go index d7727b6f3..4328a5d3e 100644 --- a/internal/models/account/repository.go +++ b/internal/models/account/repository.go @@ -3,6 +3,4 @@ package account // Repository - type Repository interface { Get(address string) (Account, error) - Alias(address string) (string, error) - UpdateAlias(account Account) error } diff --git a/internal/models/consts.go b/internal/models/consts.go index d27d1e8d4..508033fc1 100644 --- a/internal/models/consts.go +++ b/internal/models/consts.go @@ -9,6 +9,7 @@ import ( "github.com/baking-bad/bcdhub/internal/models/migration" "github.com/baking-bad/bcdhub/internal/models/operation" "github.com/baking-bad/bcdhub/internal/models/protocol" + smartrollup "github.com/baking-bad/bcdhub/internal/models/smart_rollup" "github.com/baking-bad/bcdhub/internal/models/ticket" ) @@ -26,6 +27,7 @@ const ( DocProtocol = "protocols" DocScripts = "scripts" DocTicketUpdates = "ticket_updates" + DocSmartRollups = "smart_rollups" ) // AllDocuments - returns all document names @@ -43,6 +45,7 @@ func AllDocuments() []string { DocProtocol, DocScripts, DocTicketUpdates, + DocSmartRollups, } } @@ -62,6 +65,7 @@ func AllModels() []Model { &contract.ScriptConstants{}, &contract.Contract{}, &migration.Migration{}, + &smartrollup.SmartRollup{}, } } diff --git a/internal/models/mock/account/mock.go b/internal/models/mock/account/mock.go index 466f57b0d..fd977409f 100644 --- a/internal/models/mock/account/mock.go +++ b/internal/models/mock/account/mock.go @@ -5,35 +5,36 @@ package account import ( + reflect "reflect" + model "github.com/baking-bad/bcdhub/internal/models/account" gomock "github.com/golang/mock/gomock" - reflect "reflect" ) -// MockRepository is a mock of Repository interface +// MockRepository is a mock of Repository interface. type MockRepository struct { ctrl *gomock.Controller recorder *MockRepositoryMockRecorder } -// MockRepositoryMockRecorder is the mock recorder for MockRepository +// MockRepositoryMockRecorder is the mock recorder for MockRepository. type MockRepositoryMockRecorder struct { mock *MockRepository } -// NewMockRepository creates a new mock instance +// NewMockRepository creates a new mock instance. func NewMockRepository(ctrl *gomock.Controller) *MockRepository { mock := &MockRepository{ctrl: ctrl} mock.recorder = &MockRepositoryMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { return m.recorder } -// Get mocks base method +// Get mocks base method. func (m *MockRepository) Get(address string) (model.Account, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", address) @@ -42,37 +43,8 @@ func (m *MockRepository) Get(address string) (model.Account, error) { return ret0, ret1 } -// Get indicates an expected call of Get +// Get indicates an expected call of Get. func (mr *MockRepositoryMockRecorder) Get(address interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), address) } - -// Alias mocks base method -func (m *MockRepository) Alias(address string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Alias", address) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Alias indicates an expected call of Alias -func (mr *MockRepositoryMockRecorder) Alias(address interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Alias", reflect.TypeOf((*MockRepository)(nil).Alias), address) -} - -// UpdateAlias mocks base method -func (m *MockRepository) UpdateAlias(account model.Account) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateAlias", account) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateAlias indicates an expected call of UpdateAlias -func (mr *MockRepositoryMockRecorder) UpdateAlias(account interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAlias", reflect.TypeOf((*MockRepository)(nil).UpdateAlias), account) -} diff --git a/internal/models/mock/smart_rollup/mock.go b/internal/models/mock/smart_rollup/mock.go new file mode 100644 index 000000000..0d8dd2b72 --- /dev/null +++ b/internal/models/mock/smart_rollup/mock.go @@ -0,0 +1,65 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: internal/models/smart_rollup/repository.go + +// Package model is a generated GoMock package. +package smartrollup + +import ( + reflect "reflect" + + model "github.com/baking-bad/bcdhub/internal/models/smart_rollup" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockRepository) Get(address string) (model.SmartRollup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", address) + ret0, _ := ret[0].(model.SmartRollup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockRepositoryMockRecorder) Get(address interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), address) +} + +// List mocks base method. +func (m *MockRepository) List(limit, offset int64, sort string) ([]model.SmartRollup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", limit, offset, sort) + ret0, _ := ret[0].([]model.SmartRollup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockRepositoryMockRecorder) List(limit, offset, sort interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRepository)(nil).List), limit, offset, sort) +} diff --git a/internal/models/mock/ticket/mock.go b/internal/models/mock/ticket/mock.go index 548be5093..57047f75a 100644 --- a/internal/models/mock/ticket/mock.go +++ b/internal/models/mock/ticket/mock.go @@ -1,49 +1,80 @@ // Code generated by MockGen. DO NOT EDIT. // Source: internal/models/ticket/repository.go -// Package ticket is a generated GoMock package. +// Package mock_ticket is a generated GoMock package. package ticket import ( - models "github.com/baking-bad/bcdhub/internal/models/ticket" - gomock "github.com/golang/mock/gomock" reflect "reflect" + + model "github.com/baking-bad/bcdhub/internal/models/ticket" + gomock "github.com/golang/mock/gomock" ) -// MockRepository is a mock of Repository interface +// MockRepository is a mock of Repository interface. type MockRepository struct { ctrl *gomock.Controller recorder *MockRepositoryMockRecorder } -// MockRepositoryMockRecorder is the mock recorder for MockRepository +// MockRepositoryMockRecorder is the mock recorder for MockRepository. type MockRepositoryMockRecorder struct { mock *MockRepository } -// NewMockRepository creates a new mock instance +// NewMockRepository creates a new mock instance. func NewMockRepository(ctrl *gomock.Controller) *MockRepository { mock := &MockRepository{ctrl: ctrl} mock.recorder = &MockRepositoryMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { return m.recorder } -// Get mocks base method -func (m *MockRepository) Get(contract string, limit, offset int) ([]models.TicketUpdate, error) { +// ForOperation mocks base method. +func (m *MockRepository) ForOperation(operationId int64) ([]model.TicketUpdate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ForOperation", operationId) + ret0, _ := ret[0].([]model.TicketUpdate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ForOperation indicates an expected call of ForOperation. +func (mr *MockRepositoryMockRecorder) ForOperation(operationId interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForOperation", reflect.TypeOf((*MockRepository)(nil).ForOperation), operationId) +} + +// Get mocks base method. +func (m *MockRepository) Get(ticketer string, limit, offset int64) ([]model.TicketUpdate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ticketer, limit, offset) + ret0, _ := ret[0].([]model.TicketUpdate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockRepositoryMockRecorder) Get(ticketer, limit, offset interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), ticketer, limit, offset) +} + +// Has mocks base method. +func (m *MockRepository) Has(contractID int64) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", contract, limit, offset) - ret0, _ := ret[0].([]models.TicketUpdate) + ret := m.ctrl.Call(m, "Has", contractID) + ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } -// Get indicates an expected call of Get -func (mr *MockRepositoryMockRecorder) Get(contract, limit, offset interface{}) *gomock.Call { +// Has indicates an expected call of Has. +func (mr *MockRepositoryMockRecorder) Has(contractID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), contract, limit, offset) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockRepository)(nil).Has), contractID) } diff --git a/internal/models/model.go b/internal/models/model.go index d9fcfb924..f767855c6 100644 --- a/internal/models/model.go +++ b/internal/models/model.go @@ -10,6 +10,7 @@ import ( "github.com/baking-bad/bcdhub/internal/models/migration" "github.com/baking-bad/bcdhub/internal/models/operation" "github.com/baking-bad/bcdhub/internal/models/protocol" + smartrollup "github.com/baking-bad/bcdhub/internal/models/smart_rollup" "github.com/baking-bad/bcdhub/internal/models/ticket" "github.com/go-pg/pg/v10" ) @@ -26,7 +27,7 @@ type Constraint interface { *account.Account | *bigmapaction.BigMapAction | *bigmapdiff.BigMapDiff | *bigmapdiff.BigMapState | *block.Block | *contract.Contract | *contract.Script | *contract.GlobalConstant | *contract.ScriptConstants | *migration.Migration | *operation.Operation | *protocol.Protocol | domains.BigMapDiff | - *ticket.TicketUpdate + *ticket.TicketUpdate | *smartrollup.SmartRollup Model } diff --git a/internal/models/operation/model.go b/internal/models/operation/model.go index 7e36fbf4d..7b53acc15 100644 --- a/internal/models/operation/model.go +++ b/internal/models/operation/model.go @@ -35,6 +35,8 @@ type Operation struct { Burned int64 `pg:",use_zero"` AllocatedDestinationContractBurned int64 `pg:",use_zero"` ProtocolID int64 `pg:",type:SMALLINT"` + TicketUpdatesCount int `pg:",use_zero"` + BigMapDiffsCount int `pg:",use_zero"` Tags types.Tags `pg:",use_zero"` Nonce *int64 @@ -56,8 +58,8 @@ type Operation struct { Hash []byte Parameters []byte DeffatedStorage []byte - EventPayload []byte - EventType []byte + Payload []byte + PayloadType []byte Script []byte `pg:"-"` Errors tezerrors.Errors `pg:",type:bytea"` @@ -147,12 +149,7 @@ func (o *Operation) IsApplied() bool { // IsCall - func (o *Operation) IsCall() bool { - return bcd.IsContract(o.Destination.Address) && len(o.Parameters) > 0 -} - -// IsEvent - -func (o *Operation) IsEvent() bool { - return o.Kind == types.OperationKindEvent + return (bcd.IsContract(o.Destination.Address) || bcd.IsSmartRollupHash(o.Destination.Address)) && len(o.Parameters) > 0 } // Result - diff --git a/internal/models/smart_rollup/model.go b/internal/models/smart_rollup/model.go new file mode 100644 index 000000000..f49c5be9f --- /dev/null +++ b/internal/models/smart_rollup/model.go @@ -0,0 +1,51 @@ +package smartrollup + +import ( + "time" + + "github.com/baking-bad/bcdhub/internal/models/account" + "github.com/go-pg/pg/v10" +) + +// SmartRollup - entity for smart rollup +type SmartRollup struct { + // nolint + tableName struct{} `pg:"smart_rollup"` + + ID int64 + Level int64 + Timestamp time.Time + + Size uint64 `pg:",use_zero"` + AddressId int64 + Address account.Account `pg:",rel:has-one"` + + GenesisCommitmentHash string + PvmKind string + Kernel []byte `pg:",type:bytea"` + Type []byte `pg:",type:bytea"` +} + +// GetID - +func (sr *SmartRollup) GetID() int64 { + return sr.ID +} + +// GetIndex - +func (SmartRollup) GetIndex() string { + return "contracts" +} + +// Save - +func (sr *SmartRollup) Save(tx pg.DBI) error { + _, err := tx.Model(sr).OnConflict("DO NOTHING").Returning("id").Insert() + return err +} + +// LogFields - +func (sr *SmartRollup) LogFields() map[string]interface{} { + return map[string]interface{}{ + "address": sr.Address.Address, + "block": sr.Level, + } +} diff --git a/internal/models/smart_rollup/repository.go b/internal/models/smart_rollup/repository.go new file mode 100644 index 000000000..9d3dd3c3d --- /dev/null +++ b/internal/models/smart_rollup/repository.go @@ -0,0 +1,7 @@ +package smartrollup + +// Repository - +type Repository interface { + Get(address string) (SmartRollup, error) + List(limit, offset int64, sort string) ([]SmartRollup, error) +} diff --git a/internal/models/ticket/repository.go b/internal/models/ticket/repository.go index d8e99c62f..71506dead 100644 --- a/internal/models/ticket/repository.go +++ b/internal/models/ticket/repository.go @@ -4,4 +4,5 @@ package ticket type Repository interface { Get(ticketer string, limit, offset int64) ([]TicketUpdate, error) Has(contractID int64) (bool, error) + ForOperation(operationId int64) ([]TicketUpdate, error) } diff --git a/internal/models/types/account_type.go b/internal/models/types/account_type.go index 7f31d3847..8370e8918 100644 --- a/internal/models/types/account_type.go +++ b/internal/models/types/account_type.go @@ -11,6 +11,7 @@ const ( AccountTypeContract AccountTypeTz AccountTypeRollup + AccountTypeSmartRollup ) // NewAccountType - @@ -22,7 +23,25 @@ func NewAccountType(address string) AccountType { return AccountTypeTz case bcd.IsRollupAddressLazy(address): return AccountTypeRollup + case bcd.IsSmartRollupAddressLazy(address): + return AccountTypeSmartRollup default: return AccountTypeUnknown } } + +// String - +func (typ AccountType) String() string { + switch typ { + case AccountTypeContract: + return "contract" + case AccountTypeRollup: + return "rollup" + case AccountTypeSmartRollup: + return "smart_rollup" + case AccountTypeTz: + return "account" + default: + return "unknown" + } +} diff --git a/internal/models/types/operation_kind.go b/internal/models/types/operation_kind.go index d2c44a193..91e55430a 100644 --- a/internal/models/types/operation_kind.go +++ b/internal/models/types/operation_kind.go @@ -1,25 +1,33 @@ package types +import "github.com/baking-bad/bcdhub/internal/bcd/consts" + // OperationKind - type OperationKind int // NewOperationKind - func NewOperationKind(value string) OperationKind { switch value { - case "transaction": + case consts.Transaction: return OperationKindTransaction - case "origination": + case consts.Origination: return OperationKindOrigination - case "origination_new": + case consts.OriginationNew: return OperationKindOriginationNew - case "delegation": + case consts.Delegation: return OperationKindDelegation - case "register_global_constant": + case consts.RegisterGlobalConstant: return OperationKindRegisterGlobalConstant - case "tx_rollup_origination": + case consts.TxRollupOrigination: return OperationKindTxRollupOrigination - case "event": + case consts.Event: return OperationKindEvent + case consts.TransferTicket: + return OperationKindTransferTicket + case consts.SrOriginate: + return OperationKindSrOrigination + case consts.SrExecuteOutboxMessage: + return OperationKindSrExecuteOutboxMessage default: return 0 } @@ -29,19 +37,25 @@ func NewOperationKind(value string) OperationKind { func (kind OperationKind) String() string { switch kind { case OperationKindTransaction: - return "transaction" + return consts.Transaction case OperationKindOrigination: - return "origination" + return consts.Origination case OperationKindOriginationNew: - return "origination_new" + return consts.OriginationNew case OperationKindDelegation: - return "delegation" + return consts.Delegation case OperationKindRegisterGlobalConstant: - return "register_global_constant" + return consts.RegisterGlobalConstant case OperationKindTxRollupOrigination: - return "tx_rollup_origination" + return consts.TxRollupOrigination case OperationKindEvent: - return "event" + return consts.Event + case OperationKindTransferTicket: + return consts.TransferTicket + case OperationKindSrOrigination: + return consts.SrOriginate + case OperationKindSrExecuteOutboxMessage: + return consts.SrExecuteOutboxMessage default: return "" } @@ -55,4 +69,7 @@ const ( OperationKindRegisterGlobalConstant OperationKindTxRollupOrigination OperationKindEvent + OperationKindTransferTicket + OperationKindSrOrigination + OperationKindSrExecuteOutboxMessage ) diff --git a/internal/noderpc/errors.go b/internal/noderpc/errors.go index 65254a5b6..894911ff8 100644 --- a/internal/noderpc/errors.go +++ b/internal/noderpc/errors.go @@ -25,28 +25,8 @@ func (e NodeUnavailiableError) Error() string { return fmt.Sprintf("%s is unavailiable: %d", e.Node, e.Code) } -// MaxRetryExceededError - -type MaxRetryExceededError struct { - Node string -} - -// NewMaxRetryExceededError - -func NewMaxRetryExceededError(node string) MaxRetryExceededError { - return MaxRetryExceededError{ - Node: node, - } -} - -// Error - -func (e MaxRetryExceededError) Error() string { - return fmt.Sprintf("%s: max HTTP request retry exceeded", e.Node) -} - // IsNodeUnavailiableError - func IsNodeUnavailiableError(err error) bool { - if _, ok := err.(MaxRetryExceededError); ok { - return true - } if _, ok := err.(NodeUnavailiableError); ok { return true } diff --git a/internal/noderpc/node_options.go b/internal/noderpc/node_options.go index 82bff6783..e330674bd 100644 --- a/internal/noderpc/node_options.go +++ b/internal/noderpc/node_options.go @@ -16,13 +16,6 @@ func WithTimeout(timeout time.Duration) NodeOption { } } -// WithRetryCount - -func WithRetryCount(retryCount int) NodeOption { - return func(node *NodeRPC) { - node.retryCount = retryCount - } -} - // WithRateLimit - func WithRateLimit(requestPerSecond int) NodeOption { return func(node *NodeRPC) { @@ -31,3 +24,10 @@ func WithRateLimit(requestPerSecond int) NodeOption { } } } + +// WithLog - +func WithLog() NodeOption { + return func(node *NodeRPC) { + node.needLog = true + } +} diff --git a/internal/noderpc/responses.go b/internal/noderpc/responses.go index 535d11727..9a0a8ca29 100644 --- a/internal/noderpc/responses.go +++ b/internal/noderpc/responses.go @@ -82,25 +82,36 @@ type OperationGroup struct { // Operation - type Operation struct { - Kind string `json:"kind"` - Source string `json:"source"` - Destination *string `json:"destination,omitempty"` - Delegate string `json:"delegate,omitempty"` - Fee int64 `json:"fee,string"` - Counter int64 `json:"counter,string"` - Balance *int64 `json:"balance,omitempty,string"` - GasLimit int64 `json:"gas_limit,string"` - StorageLimit int64 `json:"storage_limit,string"` - Amount *int64 `json:"amount,omitempty,string"` - Nonce *int64 `json:"nonce,omitempty"` - Tag *string `json:"tag,omitempty"` - Parameters stdJSON.RawMessage `json:"parameters,omitempty"` - Metadata *OperationMetadata `json:"metadata,omitempty"` - Result *OperationResult `json:"result,omitempty"` - Script stdJSON.RawMessage `json:"script,omitempty"` - Value stdJSON.RawMessage `json:"value,omitempty"` - Payload stdJSON.RawMessage `json:"payload,omitempty"` - Type stdJSON.RawMessage `json:"type,omitempty"` + Kind string `json:"kind"` + Source string `json:"source"` + Destination *string `json:"destination,omitempty"` + Rollup *string `json:"rollup,omitempty"` + Delegate string `json:"delegate,omitempty"` + Fee int64 `json:"fee,string"` + Counter int64 `json:"counter,string"` + Balance *int64 `json:"balance,omitempty,string"` + GasLimit int64 `json:"gas_limit,string"` + StorageLimit int64 `json:"storage_limit,string"` + Amount *int64 `json:"amount,omitempty,string"` + Nonce *int64 `json:"nonce,omitempty"` + Tag *string `json:"tag,omitempty"` + Entrypoint *string `json:"entrypoint,omitempty"` + TicketTicketer string `json:"ticket_ticketer,omitempty"` + TicketAmount string `json:"ticket_amount,omitempty"` + PvmKind string `json:"pvm_kind,omitempty"` + Kernel string `json:"kernel,omitempty"` + CementedCommitment string `json:"cemented_commitment,omitempty"` + OutputProof string `json:"output_proof,omitempty"` + Parameters stdJSON.RawMessage `json:"parameters,omitempty"` + Metadata *OperationMetadata `json:"metadata,omitempty"` + Result *OperationResult `json:"result,omitempty"` + Script stdJSON.RawMessage `json:"script,omitempty"` + Value stdJSON.RawMessage `json:"value,omitempty"` + Payload stdJSON.RawMessage `json:"payload,omitempty"` + Type stdJSON.RawMessage `json:"type,omitempty"` + TicketContent stdJSON.RawMessage `json:"ticket_contents,omitempty"` + TicketType stdJSON.RawMessage `json:"ticket_ty,omitempty"` + ParameterType stdJSON.RawMessage `json:"parameters_ty,omitempty"` } // GetResult - @@ -184,6 +195,9 @@ type OperationResult struct { OriginatedRollup string `json:"originated_rollup,omitempty"` TicketUpdates []TicketUpdate `json:"ticket_updates,omitempty"` TicketReceipt []TicketUpdate `json:"ticket_receipt,omitempty"` + Address string `json:"address,omitempty"` + GenesisCommitmentHash string `json:"genesis_commitment_hash,omitempty"` + Size string `json:"size,omitempty"` } // LazyStorageDiff - diff --git a/internal/noderpc/rpc.go b/internal/noderpc/rpc.go index 0f8240b47..1ec806940 100644 --- a/internal/noderpc/rpc.go +++ b/internal/noderpc/rpc.go @@ -37,19 +37,18 @@ type NodeRPC struct { baseURL string client *http.Client - timeout time.Duration - userAgent string - retryCount int - rateLimit *rate.Limiter + timeout time.Duration + userAgent string + rateLimit *rate.Limiter + needLog bool } // NewNodeRPC - func NewNodeRPC(baseURL string, opts ...NodeOption) *NodeRPC { node := &NodeRPC{ - baseURL: baseURL, - timeout: time.Second * 10, - retryCount: 3, - userAgent: userAgent, + baseURL: baseURL, + timeout: time.Second * 10, + userAgent: userAgent, } if bcdUserAgent := os.Getenv("BCD_USER_AGENT"); bcdUserAgent != "" { @@ -61,10 +60,9 @@ func NewNodeRPC(baseURL string, opts ...NodeOption) *NodeRPC { } t := http.DefaultTransport.(*http.Transport).Clone() - t.MaxIdleConns = 100 - t.MaxConnsPerHost = 100 - t.MaxIdleConnsPerHost = 100 - + t.MaxIdleConns = 20 + t.MaxConnsPerHost = 20 + t.MaxIdleConnsPerHost = 20 node.client = &http.Client{ Timeout: node.timeout, Transport: t, @@ -88,15 +86,15 @@ func NewWaitNodeRPC(baseURL string, opts ...NodeOption) *NodeRPC { return node } -func (rpc *NodeRPC) checkStatusCode(resp *http.Response, checkStatusCode bool) error { +func (rpc *NodeRPC) checkStatusCode(r io.Reader, statusCode int, checkStatusCode bool) error { switch { - case resp.StatusCode == http.StatusOK: + case statusCode == http.StatusOK: return nil - case resp.StatusCode > http.StatusInternalServerError: - return NewNodeUnavailiableError(rpc.baseURL, resp.StatusCode) + case statusCode > http.StatusInternalServerError: + return NewNodeUnavailiableError(rpc.baseURL, statusCode) case checkStatusCode: invalidResponseErr := newInvalidNodeResponse() - data, err := io.ReadAll(resp.Body) + data, err := io.ReadAll(r) if err != nil { return err } @@ -110,31 +108,17 @@ func (rpc *NodeRPC) checkStatusCode(resp *http.Response, checkStatusCode bool) e } } -func (rpc *NodeRPC) parseResponse(resp *http.Response, checkStatusCode bool, uri string, response interface{}) error { - if err := rpc.checkStatusCode(resp, checkStatusCode); err != nil { +func (rpc *NodeRPC) parseResponse(r io.Reader, statusCode int, checkStatusCode bool, uri string, response interface{}) error { + if err := rpc.checkStatusCode(r, statusCode, checkStatusCode); err != nil { return fmt.Errorf("%w (%s): %w", ErrNodeRPCError, uri, err) } - return json.NewDecoder(resp.Body).Decode(response) + return json.NewDecoder(r).Decode(response) } func (rpc *NodeRPC) makeRequest(ctx context.Context, req *http.Request) (*http.Response, error) { req.Header.Set("User-Agent", rpc.userAgent) - - for count := 0; count < rpc.retryCount; count++ { - if ctx.Err() != nil { - return nil, ctx.Err() - } - - resp, err := rpc.client.Do(req) - if err != nil { - logger.Warning().Msgf("Attempt #%d: %s", count+1, err.Error()) - continue - } - return resp, err - } - - return nil, NewMaxRetryExceededError(rpc.baseURL) + return rpc.client.Do(req) } func (rpc *NodeRPC) makeGetRequest(ctx context.Context, uri string) (*http.Response, error) { @@ -168,37 +152,79 @@ func (rpc *NodeRPC) get(ctx context.Context, uri string, response interface{}) e } } + start := time.Now() + defer func() { + if rpc.needLog { + logger.Info().Str("method", "get").Int64("ms", time.Since(start).Milliseconds()).Msg(uri) + } + }() + resp, err := rpc.makeGetRequest(ctx, uri) if err != nil { return err } defer resp.Body.Close() - return rpc.parseResponse(resp, true, uri, response) + buffer := new(bytes.Buffer) + if _, err = io.Copy(buffer, resp.Body); err != nil { + return err + } + + return rpc.parseResponse(buffer, resp.StatusCode, true, uri, response) } func (rpc *NodeRPC) getRaw(ctx context.Context, uri string) ([]byte, error) { + if rpc.rateLimit != nil { + if err := rpc.rateLimit.Wait(ctx); err != nil { + return nil, err + } + } + + start := time.Now() + defer func() { + if rpc.needLog { + logger.Info().Str("method", "get").Int64("ms", time.Since(start).Milliseconds()).Msg(uri) + } + }() + resp, err := rpc.makeGetRequest(ctx, uri) if err != nil { return nil, err } defer resp.Body.Close() - if err := rpc.checkStatusCode(resp, true); err != nil { + if err := rpc.checkStatusCode(resp.Body, resp.StatusCode, true); err != nil { return nil, fmt.Errorf("%w (%s): %w", ErrNodeRPCError, uri, err) } return io.ReadAll(resp.Body) } -// nolint func (rpc *NodeRPC) post(ctx context.Context, uri string, data interface{}, checkStatusCode bool, response interface{}) error { + if rpc.rateLimit != nil { + if err := rpc.rateLimit.Wait(ctx); err != nil { + return err + } + } + + start := time.Now() + defer func() { + if rpc.needLog { + logger.Info().Str("method", "post").Int64("ms", time.Since(start).Milliseconds()).Msg(uri) + } + }() + resp, err := rpc.makePostRequest(ctx, uri, data) if err != nil { - return NewMaxRetryExceededError(rpc.baseURL) + return err } defer resp.Body.Close() - return rpc.parseResponse(resp, checkStatusCode, uri, response) + buffer := new(bytes.Buffer) + if _, err = io.Copy(buffer, resp.Body); err != nil { + return err + } + + return rpc.parseResponse(buffer, resp.StatusCode, checkStatusCode, uri, response) } // Block - returns block diff --git a/internal/parsers/operations/event.go b/internal/parsers/operations/event.go index 5a72f6df6..7070fea6a 100644 --- a/internal/parsers/operations/event.go +++ b/internal/parsers/operations/event.go @@ -40,8 +40,8 @@ func (p Event) Parse(data noderpc.Operation, store parsers.Store) error { Nonce: data.Nonce, ContentIndex: p.contentIdx, Tag: types.NewNullString(data.Tag), - EventPayload: data.Payload, - EventType: data.Type, + Payload: data.Payload, + PayloadType: data.Type, Internal: true, } diff --git a/internal/parsers/operations/operation_group.go b/internal/parsers/operations/operation_group.go index 952b5d9e2..4dc17682f 100644 --- a/internal/parsers/operations/operation_group.go +++ b/internal/parsers/operations/operation_group.go @@ -9,6 +9,11 @@ import ( "github.com/baking-bad/bcdhub/internal/parsers" ) +// OperationParser - +type OperationParser interface { + Parse(data noderpc.Operation, store parsers.Store) error +} + // Group - type Group struct { *ParseParams @@ -62,7 +67,10 @@ func (Group) needParse(item noderpc.LightOperation) bool { originationCondition := (item.Kind == consts.Origination || item.Kind == consts.OriginationNew || item.Kind == consts.TxRollupOrigination) registerGlobalConstantCondition := item.Kind == consts.RegisterGlobalConstant eventCondition := item.Kind == consts.Event - return originationCondition || transactionCondition || registerGlobalConstantCondition || eventCondition + transferTicketCondition := item.Kind == consts.TransferTicket + srCondition := item.Kind == consts.SrOriginate || item.Kind == consts.SrExecuteOutboxMessage + return originationCondition || transactionCondition || srCondition || + registerGlobalConstantCondition || eventCondition || transferTicketCondition } // Content - @@ -77,30 +85,30 @@ func NewContent(params *ParseParams) Content { // Parse - func (content Content) Parse(data noderpc.Operation, store parsers.Store) error { + var operationParser OperationParser switch data.Kind { case consts.Origination, consts.OriginationNew: - if err := NewOrigination(content.ParseParams).Parse(data, store); err != nil { - return err - } + operationParser = NewOrigination(content.ParseParams) case consts.Transaction: - if err := NewTransaction(content.ParseParams).Parse(data, store); err != nil { - return err - } + operationParser = NewTransaction(content.ParseParams) case consts.RegisterGlobalConstant: - if err := NewRegisterGlobalConstant(content.ParseParams).Parse(data, store); err != nil { - return err - } + operationParser = NewRegisterGlobalConstant(content.ParseParams) case consts.TxRollupOrigination: - if err := NewTxRollupOrigination(content.ParseParams).Parse(data, store); err != nil { - return err - } + operationParser = NewTxRollupOrigination(content.ParseParams) case consts.Event: - if err := NewEvent(content.ParseParams).Parse(data, store); err != nil { - return err - } + operationParser = NewEvent(content.ParseParams) + case consts.TransferTicket: + operationParser = NewTransferTicket(content.ParseParams) + case consts.SrOriginate: + operationParser = NewSrOriginate(content.ParseParams) + case consts.SrExecuteOutboxMessage: + operationParser = NewSrExecuteOutboxMessage(content.ParseParams) default: return nil } + if err := operationParser.Parse(data, store); err != nil { + return err + } if err := content.parseInternal(data, store); err != nil { return err diff --git a/internal/parsers/operations/origination.go b/internal/parsers/operations/origination.go index a47ead587..a2af3f2cf 100644 --- a/internal/parsers/operations/origination.go +++ b/internal/parsers/operations/origination.go @@ -101,8 +101,6 @@ func (p Origination) appliedHandler(ctx context.Context, item noderpc.Operation, return err } - new(TicketUpdateParser).Parse(item.Result, origination) - return nil } diff --git a/internal/parsers/operations/result.go b/internal/parsers/operations/result.go index f12f50a89..679d749ab 100644 --- a/internal/parsers/operations/result.go +++ b/internal/parsers/operations/result.go @@ -51,4 +51,8 @@ func parseOperationResult(data noderpc.Operation, tx *operation.Operation) { if errs, err := tezerrors.ParseArray(result.Errors); err == nil { tx.Errors = errs } + + if tx.IsApplied() { + new(TicketUpdateParser).Parse(result, tx) + } } diff --git a/internal/parsers/operations/result_test.go b/internal/parsers/operations/result_test.go index eb8d0c676..c1a88de12 100644 --- a/internal/parsers/operations/result_test.go +++ b/internal/parsers/operations/result_test.go @@ -1,13 +1,14 @@ package operations import ( - "reflect" "testing" "github.com/baking-bad/bcdhub/internal/models/account" "github.com/baking-bad/bcdhub/internal/models/operation" + "github.com/baking-bad/bcdhub/internal/models/ticket" "github.com/baking-bad/bcdhub/internal/models/types" "github.com/baking-bad/bcdhub/internal/noderpc" + "github.com/stretchr/testify/require" ) func Test_parseOperationResult(t *testing.T) { @@ -20,15 +21,17 @@ func Test_parseOperationResult(t *testing.T) { name: "test 1", fileName: "./data/result/test1.json", want: operation.Operation{ - Status: types.OperationStatusApplied, - ConsumedGas: 1020700, + Status: types.OperationStatusApplied, + ConsumedGas: 1020700, + TickerUpdates: make([]*ticket.TicketUpdate, 0), }, }, { name: "test 2", fileName: "./data/result/test2.json", want: operation.Operation{ - Status: types.OperationStatusApplied, - ConsumedGas: 1020700, + Status: types.OperationStatusApplied, + ConsumedGas: 1020700, + TickerUpdates: make([]*ticket.TicketUpdate, 0), }, }, { name: "test 3", @@ -42,6 +45,7 @@ func Test_parseOperationResult(t *testing.T) { Address: "KT1FVhijNC7ZBL5EjcetiKddDQ2n98t8w4jo", Type: types.AccountTypeContract, }, + TickerUpdates: make([]*ticket.TicketUpdate, 0), }, }, } @@ -55,9 +59,7 @@ func Test_parseOperationResult(t *testing.T) { var res operation.Operation parseOperationResult(op, &res) - if !reflect.DeepEqual(res, tt.want) { - t.Errorf("Result.Parse() = %v, want %v", res, tt.want) - } + require.Equal(t, tt.want, res) }) } } diff --git a/internal/parsers/operations/smart_rollup.go b/internal/parsers/operations/smart_rollup.go new file mode 100644 index 000000000..0bbf3386c --- /dev/null +++ b/internal/parsers/operations/smart_rollup.go @@ -0,0 +1,51 @@ +package operations + +import ( + "encoding/hex" + "strconv" + + "github.com/baking-bad/bcdhub/internal/models/account" + "github.com/baking-bad/bcdhub/internal/models/operation" + smartrollup "github.com/baking-bad/bcdhub/internal/models/smart_rollup" + "github.com/baking-bad/bcdhub/internal/models/types" + "github.com/baking-bad/bcdhub/internal/noderpc" +) + +// SmartRolupParser - +type SmartRolupParser struct{} + +// NewSmartRolupParser - +func NewSmartRolupParser() SmartRolupParser { + return SmartRolupParser{} +} + +// Parse - +func (sr SmartRolupParser) Parse(data noderpc.Operation, operation operation.Operation) (smartrollup.SmartRollup, error) { + result := data.GetResult() + rollup := smartrollup.SmartRollup{ + Address: account.Account{ + Address: result.Address, + Type: types.NewAccountType(result.Address), + }, + GenesisCommitmentHash: result.GenesisCommitmentHash, + PvmKind: data.PvmKind, + Type: data.ParameterType, + Level: operation.Level, + Timestamp: operation.Timestamp, + } + if data.Kernel != "" { + kernel, err := hex.DecodeString(data.Kernel) + if err != nil { + return rollup, err + } + rollup.Kernel = kernel + } + if result.Size != "" { + size, err := strconv.ParseUint(result.Size, 10, 64) + if err != nil { + return rollup, err + } + rollup.Size = size + } + return rollup, nil +} diff --git a/internal/parsers/operations/sr_execute_outbox_message.go b/internal/parsers/operations/sr_execute_outbox_message.go new file mode 100644 index 000000000..21583014c --- /dev/null +++ b/internal/parsers/operations/sr_execute_outbox_message.go @@ -0,0 +1,89 @@ +package operations + +import ( + "encoding/hex" + + "github.com/baking-bad/bcdhub/internal/bcd/encoding" + "github.com/baking-bad/bcdhub/internal/models/account" + "github.com/baking-bad/bcdhub/internal/models/operation" + "github.com/baking-bad/bcdhub/internal/models/types" + "github.com/baking-bad/bcdhub/internal/noderpc" + "github.com/baking-bad/bcdhub/internal/parsers" + "github.com/pkg/errors" +) + +// SrExecuteOutboxMessage - +type SrExecuteOutboxMessage struct { + *ParseParams +} + +// NewSrExecuteOutboxMessage - +func NewSrExecuteOutboxMessage(params *ParseParams) SrExecuteOutboxMessage { + return SrExecuteOutboxMessage{params} +} + +// Parse - +func (p SrExecuteOutboxMessage) Parse(data noderpc.Operation, store parsers.Store) error { + source := account.Account{ + Address: data.Source, + Type: types.NewAccountType(data.Source), + } + + operation := operation.Operation{ + Source: source, + Initiator: source, + Fee: data.Fee, + Counter: data.Counter, + StorageLimit: data.StorageLimit, + GasLimit: data.GasLimit, + Hash: p.hash, + ProtocolID: p.protocol.ID, + Level: p.head.Level, + Timestamp: p.head.Timestamp, + Kind: types.NewOperationKind(data.Kind), + ContentIndex: p.contentIdx, + Payload: make([]byte, 0), + } + if data.Rollup != nil { + operation.Destination = account.Account{ + Address: *data.Rollup, + Type: types.NewAccountType(*data.Rollup), + } + } + p.fillInternal(&operation) + operation.SetBurned(*p.protocol.Constants) + parseOperationResult(data, &operation) + p.stackTrace.Add(operation) + + if operation.IsApplied() { + commitment, err := encoding.DecodeBase58(data.CementedCommitment) + if err != nil { + return errors.Wrap(err, "cemented commitment decoding") + } + operation.Payload = append(operation.Payload, commitment...) + + proof, err := hex.DecodeString(data.OutputProof) + if err != nil { + return errors.Wrap(err, "outbox proof decoding") + } + operation.Payload = append(operation.Payload, proof...) + } + + store.AddOperations(&operation) + + return nil +} + +func (p SrExecuteOutboxMessage) fillInternal(tx *operation.Operation) { + if p.main == nil { + p.main = tx + return + } + + tx.Counter = p.main.Counter + tx.Hash = p.main.Hash + tx.Level = p.main.Level + tx.Timestamp = p.main.Timestamp + tx.Internal = true + tx.Initiator = p.main.Source +} diff --git a/internal/parsers/operations/sr_originate.go b/internal/parsers/operations/sr_originate.go new file mode 100644 index 000000000..e9216476f --- /dev/null +++ b/internal/parsers/operations/sr_originate.go @@ -0,0 +1,74 @@ +package operations + +import ( + "github.com/baking-bad/bcdhub/internal/models/account" + "github.com/baking-bad/bcdhub/internal/models/operation" + "github.com/baking-bad/bcdhub/internal/models/types" + "github.com/baking-bad/bcdhub/internal/noderpc" + "github.com/baking-bad/bcdhub/internal/parsers" +) + +// SrOriginate - +type SrOriginate struct { + *ParseParams +} + +// NewSrOriginate - +func NewSrOriginate(params *ParseParams) SrOriginate { + return SrOriginate{params} +} + +// Parse - +func (p SrOriginate) Parse(data noderpc.Operation, store parsers.Store) error { + source := account.Account{ + Address: data.Source, + Type: types.NewAccountType(data.Source), + } + + operation := operation.Operation{ + Source: source, + Initiator: source, + Fee: data.Fee, + Counter: data.Counter, + StorageLimit: data.StorageLimit, + GasLimit: data.GasLimit, + Hash: p.hash, + ProtocolID: p.protocol.ID, + Level: p.head.Level, + Timestamp: p.head.Timestamp, + Kind: types.NewOperationKind(data.Kind), + ContentIndex: p.contentIdx, + } + + p.fillInternal(&operation) + operation.SetBurned(*p.protocol.Constants) + parseOperationResult(data, &operation) + p.stackTrace.Add(operation) + + store.AddOperations(&operation) + + if operation.IsApplied() { + smartRollup, err := NewSmartRolupParser().Parse(data, operation) + if err != nil { + return err + } + store.AddSmartRollups(&smartRollup) + operation.Destination = smartRollup.Address + } + + return nil +} + +func (p SrOriginate) fillInternal(tx *operation.Operation) { + if p.main == nil { + p.main = tx + return + } + + tx.Counter = p.main.Counter + tx.Hash = p.main.Hash + tx.Level = p.main.Level + tx.Timestamp = p.main.Timestamp + tx.Internal = true + tx.Initiator = p.main.Source +} diff --git a/internal/parsers/operations/ticket_updates.go b/internal/parsers/operations/ticket_updates.go index 3025b912e..a3998336b 100644 --- a/internal/parsers/operations/ticket_updates.go +++ b/internal/parsers/operations/ticket_updates.go @@ -26,6 +26,8 @@ func (p *TicketUpdateParser) Parse(data *noderpc.OperationResult, operation *ope tckt := p.toModel(data.TicketReceipt[i], operation) operation.TickerUpdates = append(operation.TickerUpdates, tckt...) } + + operation.TicketUpdatesCount = len(operation.TickerUpdates) } func (p *TicketUpdateParser) toModel(data noderpc.TicketUpdate, operation *operation.Operation) []*ticket.TicketUpdate { diff --git a/internal/parsers/operations/transaction.go b/internal/parsers/operations/transaction.go index b749f9f65..d255660b0 100644 --- a/internal/parsers/operations/transaction.go +++ b/internal/parsers/operations/transaction.go @@ -68,10 +68,29 @@ func (p Transaction) Parse(data noderpc.Operation, store parsers.Store) error { store.AddOperations(&tx) - if tx.Destination.Type != modelsTypes.AccountTypeContract { + switch tx.Destination.Type { + case modelsTypes.AccountTypeContract: + return p.parseContractParams(data, store, &tx) + case modelsTypes.AccountTypeSmartRollup: + return p.parseSmartRollupParams(data, &tx) + default: return nil } +} + +func (p Transaction) parseSmartRollupParams(data noderpc.Operation, tx *operation.Operation) error { + if len(tx.Parameters) == 0 { + return tx.Entrypoint.Set(consts.DefaultEntrypoint) + } + + params := types.NewParameters(tx.Parameters) + if err := tx.Entrypoint.Set(params.Entrypoint); err != nil { + return err + } + return nil +} +func (p Transaction) parseContractParams(data noderpc.Operation, store parsers.Store, tx *operation.Operation) error { for i := range tx.Errors { if tx.Errors[i].Is("contract.non_existing_contract") { return nil @@ -123,21 +142,20 @@ func (p Transaction) Parse(data noderpc.Operation, store parsers.Store) error { return err } - if err := setTags(p.ctx, nil, &tx); err != nil { + if err := setTags(p.ctx, nil, tx); err != nil { return err } - if err := p.getEntrypoint(&tx); err != nil { + if err := p.getEntrypoint(tx); err != nil { return err } - p.stackTrace.Add(tx) + p.stackTrace.Add(*tx) if tx.IsApplied() { - if err := p.appliedHandler(data, &tx, store); err != nil { + if err := p.appliedHandler(data, tx, store); err != nil { return err } } - return nil } @@ -160,8 +178,6 @@ func (p Transaction) appliedHandler(item noderpc.Operation, tx *operation.Operat return err } - new(TicketUpdateParser).Parse(item.Result, tx) - return NewMigration(p.ctx.Contracts).Parse(item, tx, p.protocol.Hash, store) } diff --git a/internal/parsers/operations/transfer_ticket.go b/internal/parsers/operations/transfer_ticket.go new file mode 100644 index 000000000..80385e447 --- /dev/null +++ b/internal/parsers/operations/transfer_ticket.go @@ -0,0 +1,75 @@ +package operations + +import ( + "github.com/baking-bad/bcdhub/internal/models/account" + "github.com/baking-bad/bcdhub/internal/models/operation" + "github.com/baking-bad/bcdhub/internal/models/types" + "github.com/baking-bad/bcdhub/internal/noderpc" + "github.com/baking-bad/bcdhub/internal/parsers" +) + +// TransferTicket - +type TransferTicket struct { + *ParseParams +} + +// NewTransferTicket - +func NewTransferTicket(params *ParseParams) TransferTicket { + return TransferTicket{params} +} + +// Parse - +func (p TransferTicket) Parse(data noderpc.Operation, store parsers.Store) error { + source := account.Account{ + Address: data.Source, + Type: types.NewAccountType(data.Source), + } + + transferTicket := operation.Operation{ + Source: source, + Initiator: source, + StorageLimit: data.StorageLimit, + Fee: data.Fee, + Counter: data.Counter, + GasLimit: data.GasLimit, + Hash: p.hash, + ProtocolID: p.protocol.ID, + Level: p.head.Level, + Timestamp: p.head.Timestamp, + Kind: types.NewOperationKind(data.Kind), + Entrypoint: types.NewNullString(data.Entrypoint), + ContentIndex: p.contentIdx, + Payload: data.TicketContent, + PayloadType: data.TicketType, + } + + if data.Destination != nil { + transferTicket.Destination = account.Account{ + Address: *data.Destination, + Type: types.NewAccountType(*data.Destination), + } + } + + p.fillInternal(&transferTicket) + transferTicket.SetBurned(*p.protocol.Constants) + parseOperationResult(data, &transferTicket) + p.stackTrace.Add(transferTicket) + + store.AddOperations(&transferTicket) + + return nil +} + +func (p TransferTicket) fillInternal(tx *operation.Operation) { + if p.main == nil { + p.main = tx + return + } + + tx.Counter = p.main.Counter + tx.Hash = p.main.Hash + tx.Level = p.main.Level + tx.Timestamp = p.main.Timestamp + tx.Internal = true + tx.Initiator = p.main.Source +} diff --git a/internal/parsers/storage/alpha.go b/internal/parsers/storage/alpha.go index 9b1ecb6c3..5f4af28c0 100644 --- a/internal/parsers/storage/alpha.go +++ b/internal/parsers/storage/alpha.go @@ -112,6 +112,7 @@ func (a *Alpha) ParseOrigination(content noderpc.Operation, operation *operation return err } operation.DeffatedStorage = b + operation.BigMapDiffsCount = len(operation.BigMapDiffs) return nil } @@ -130,6 +131,7 @@ func (a *Alpha) getBigMapDiff(diffs []noderpc.BigMapDiff, address string, operat } operation.BigMapDiffs = append(operation.BigMapDiffs, b) + operation.BigMapDiffsCount = len(operation.BigMapDiffs) state := b.ToState() state.Ptr = -1 store.AddBigMapStates(state) diff --git a/internal/parsers/storage/babylon.go b/internal/parsers/storage/babylon.go index fa4111414..bb721104c 100644 --- a/internal/parsers/storage/babylon.go +++ b/internal/parsers/storage/babylon.go @@ -165,6 +165,7 @@ func (b *Babylon) handleBigMapDiff(result *noderpc.OperationResult, address stri } } + op.BigMapDiffsCount = len(op.BigMapDiffs) return nil } diff --git a/internal/parsers/storage/lazy_babylon.go b/internal/parsers/storage/lazy_babylon.go index feaf0ad0f..ad1d93313 100644 --- a/internal/parsers/storage/lazy_babylon.go +++ b/internal/parsers/storage/lazy_babylon.go @@ -172,6 +172,8 @@ func (b *LazyBabylon) handleBigMapDiff(result *noderpc.OperationResult, address } } + op.BigMapDiffsCount = len(op.BigMapDiffs) + return nil } diff --git a/internal/parsers/store.go b/internal/parsers/store.go index 9b939119a..eb3ce2820 100644 --- a/internal/parsers/store.go +++ b/internal/parsers/store.go @@ -7,6 +7,7 @@ import ( "github.com/baking-bad/bcdhub/internal/models/contract" "github.com/baking-bad/bcdhub/internal/models/migration" "github.com/baking-bad/bcdhub/internal/models/operation" + smartrollup "github.com/baking-bad/bcdhub/internal/models/smart_rollup" ) // Store - @@ -16,6 +17,7 @@ type Store interface { AddMigrations(migrations ...*migration.Migration) AddOperations(operations ...*operation.Operation) AddGlobalConstants(constants ...*contract.GlobalConstant) + AddSmartRollups(rollups ...*smartrollup.SmartRollup) ListContracts() []*contract.Contract ListOperations() []*operation.Operation Save(ctx context.Context) error @@ -28,6 +30,7 @@ type TestStore struct { Migrations []*migration.Migration Operations []*operation.Operation GlobalConstants []*contract.GlobalConstant + SmartRollups []*smartrollup.SmartRollup } // NewTestStore - @@ -38,6 +41,7 @@ func NewTestStore() *TestStore { Migrations: make([]*migration.Migration, 0), Operations: make([]*operation.Operation, 0), GlobalConstants: make([]*contract.GlobalConstant, 0), + SmartRollups: make([]*smartrollup.SmartRollup, 0), } } @@ -66,6 +70,11 @@ func (store *TestStore) AddGlobalConstants(constants ...*contract.GlobalConstant store.GlobalConstants = append(store.GlobalConstants, constants...) } +// AddSmartRollups - +func (store *TestStore) AddSmartRollups(rollups ...*smartrollup.SmartRollup) { + store.SmartRollups = append(store.SmartRollups, rollups...) +} + // ListContracts - func (store *TestStore) ListContracts() []*contract.Contract { return store.Contracts diff --git a/internal/postgres/account/storage.go b/internal/postgres/account/storage.go index c1c19bdc3..318e060ce 100644 --- a/internal/postgres/account/storage.go +++ b/internal/postgres/account/storage.go @@ -23,19 +23,3 @@ func (storage *Storage) Get(address string) (account account.Account, err error) Select(&account) return } - -// Alias - -func (storage *Storage) Alias(address string) (alias string, err error) { - err = storage.DB.Model((*account.Account)(nil)). - Column("alias"). - Where("address = ?", address). - Limit(1). - Select(&alias) - return -} - -// UpdateAlias - -func (storage *Storage) UpdateAlias(account account.Account) error { - _, err := storage.DB.Model(&account).Set("alias = _data.alias").WherePK().Update() - return err -} diff --git a/internal/postgres/smart_rollup/storage.go b/internal/postgres/smart_rollup/storage.go new file mode 100644 index 000000000..9b9292ac9 --- /dev/null +++ b/internal/postgres/smart_rollup/storage.go @@ -0,0 +1,49 @@ +package smartrollup + +import ( + "strings" + + "github.com/baking-bad/bcdhub/internal/models/account" + smartrollup "github.com/baking-bad/bcdhub/internal/models/smart_rollup" + "github.com/baking-bad/bcdhub/internal/postgres/core" + "github.com/go-pg/pg/v10" +) + +// Storage - +type Storage struct { + *core.Postgres +} + +// NewStorage - +func NewStorage(pg *core.Postgres) *Storage { + return &Storage{pg} +} + +// Get - +func (storage *Storage) Get(address string) (response smartrollup.SmartRollup, err error) { + var accountID int64 + if err = storage.DB.Model((*account.Account)(nil)).Column("id").Where("address = ?", address).Select(&accountID); err != nil { + return + } + + err = storage.DB.Model(&response).Where("address_id = ?", accountID).Relation("Address").Select() + return +} + +// List - +func (storage *Storage) List(limit, offset int64, sort string) (response []smartrollup.SmartRollup, err error) { + query := storage.DB.Model((*smartrollup.SmartRollup)(nil)). + Limit(storage.GetPageSize(limit)) + + if offset > 0 { + query.Offset(int(offset)) + } + lowerSort := strings.ToLower(sort) + if lowerSort != "asc" && lowerSort != "desc" { + lowerSort = "desc" + } + query.OrderExpr("id ?", pg.Safe(lowerSort)) + + err = query.Relation("Address").Select(&response) + return +} diff --git a/internal/postgres/store.go b/internal/postgres/store.go index 48078642c..970fe9ad1 100644 --- a/internal/postgres/store.go +++ b/internal/postgres/store.go @@ -8,6 +8,7 @@ import ( "github.com/baking-bad/bcdhub/internal/models/contract" "github.com/baking-bad/bcdhub/internal/models/migration" "github.com/baking-bad/bcdhub/internal/models/operation" + smartrollup "github.com/baking-bad/bcdhub/internal/models/smart_rollup" "github.com/baking-bad/bcdhub/internal/models/types" "github.com/go-pg/pg/v10" @@ -20,6 +21,7 @@ type Store struct { Migrations []*migration.Migration Operations []*operation.Operation GlobalConstants []*contract.GlobalConstant + SmartRollups []*smartrollup.SmartRollup partitions *PartitionManager tx pg.DBI @@ -33,6 +35,7 @@ func NewStore(tx pg.DBI, pm *PartitionManager) *Store { Migrations: make([]*migration.Migration, 0), Operations: make([]*operation.Operation, 0), GlobalConstants: make([]*contract.GlobalConstant, 0), + SmartRollups: make([]*smartrollup.SmartRollup, 0), partitions: pm, tx: tx, @@ -64,6 +67,11 @@ func (store *Store) AddGlobalConstants(constants ...*contract.GlobalConstant) { store.GlobalConstants = append(store.GlobalConstants, constants...) } +// AddSmartRollups - +func (store *Store) AddSmartRollups(rollups ...*smartrollup.SmartRollup) { + store.SmartRollups = append(store.SmartRollups, rollups...) +} + // ListContracts - func (store *Store) ListContracts() []*contract.Contract { return store.Contracts @@ -100,6 +108,10 @@ func (store *Store) Save(ctx context.Context) error { } } + if err := store.saveSmartRollups(store.tx); err != nil { + return err + } + return nil } @@ -118,6 +130,24 @@ func (store *Store) saveMigrations(tx pg.DBI) error { return err } +func (store *Store) saveSmartRollups(tx pg.DBI) error { + if len(store.SmartRollups) == 0 { + return nil + } + + for i := range store.SmartRollups { + if !store.SmartRollups[i].Address.IsEmpty() { + if err := store.SmartRollups[i].Address.Save(tx); err != nil { + return err + } + store.SmartRollups[i].AddressId = store.SmartRollups[i].Address.ID + } + } + + _, err := tx.Model(&store.SmartRollups).Returning("id").Insert() + return err +} + func (store *Store) saveOperations(ctx context.Context, tx pg.DBI) error { if len(store.Operations) == 0 { return nil diff --git a/internal/postgres/ticket/storage.go b/internal/postgres/ticket/storage.go index 1f28e4c57..3fd970030 100644 --- a/internal/postgres/ticket/storage.go +++ b/internal/postgres/ticket/storage.go @@ -54,3 +54,14 @@ func (storage *Storage) Has(contractID int64) (bool, error) { } return true, nil } + +// ForOperation - +func (storage *Storage) ForOperation(operationId int64) (response []ticket.TicketUpdate, err error) { + err = storage.DB. + Model(&response). + Relation("Ticketer"). + Relation("Account"). + Where("operation_id = ?", operationId). + Select() + return +}