From bbfa65d760e6aa6522f47df877fe9bd323127974 Mon Sep 17 00:00:00 2001 From: Andrii Rozinko Date: Wed, 19 Aug 2020 15:30:20 +0300 Subject: [PATCH] Initial commit --- Dockerfile | 41 ++++++++++ cmd/collector/main.go | 82 ++++++++++++++++++++ explorer-backend.go | 2 + go.mod | 4 + model/account.go | 26 +++++++ model/app.go | 15 ++++ model/atx.go | 34 +++++++++ model/block.go | 16 ++++ model/epoch.go | 32 ++++++++ model/layer.go | 56 ++++++++++++++ model/reward.go | 36 +++++++++ model/smesher.go | 24 ++++++ model/tx.go | 90 ++++++++++++++++++++++ storage/account.go | 68 +++++++++++++++++ storage/app.go | 65 ++++++++++++++++ storage/atx.go | 99 ++++++++++++++++++++++++ storage/block.go | 88 +++++++++++++++++++++ storage/epoch.go | 125 ++++++++++++++++++++++++++++++ storage/layer.go | 73 ++++++++++++++++++ storage/reward.go | 80 ++++++++++++++++++++ storage/smesher.go | 90 ++++++++++++++++++++++ storage/storage.go | 84 ++++++++++++++++++++ storage/tx.go | 160 +++++++++++++++++++++++++++++++++++++++ utils/bytes-to-string.go | 31 ++++++++ 24 files changed, 1421 insertions(+) create mode 100644 Dockerfile create mode 100644 cmd/collector/main.go create mode 100644 explorer-backend.go create mode 100644 go.mod create mode 100644 model/account.go create mode 100644 model/app.go create mode 100644 model/atx.go create mode 100644 model/block.go create mode 100644 model/epoch.go create mode 100644 model/layer.go create mode 100644 model/reward.go create mode 100644 model/smesher.go create mode 100644 model/tx.go create mode 100644 storage/account.go create mode 100644 storage/app.go create mode 100644 storage/atx.go create mode 100644 storage/block.go create mode 100644 storage/epoch.go create mode 100644 storage/layer.go create mode 100644 storage/reward.go create mode 100644 storage/smesher.go create mode 100644 storage/storage.go create mode 100644 storage/tx.go create mode 100644 utils/bytes-to-string.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d9bfa9f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# Inspired by https://container-solutions.com/faster-builds-in-docker-with-go-1-11/ +# Base build image +FROM golang:1.14.6-alpine3.12 AS build_base +RUN apk add bash make git curl unzip rsync libc6-compat gcc musl-dev +WORKDIR /go/src/github.com/spacemeshos/explorer-backend + +# Force the go compiler to use modules +ENV GO111MODULE=on +ENV GOPROXY=https://proxy.golang.org + +# We want to populate the module cache based on the go.{mod,sum} files. +COPY go.mod . +COPY go.sum . + +# Download dependencies +RUN go mod download + +RUN go get github.com/golang/snappy@v0.0.1 + +# This image builds the explorer-backend +FROM build_base AS server_builder +# Here we copy the rest of the source code +COPY . . + +# And compile the project +RUN make build + +#In this last stage, we start from a fresh Alpine image, to reduce the image size and not ship the Go compiler in our production artifacts. +FROM alpine AS dash-backend + +# Finally we copy the statically compiled Go binary. +COPY --from=server_builder /go/src/github.com/spacemeshos/explorer-backend/build/explorer-backend /bin/explorer-backend + +ENTRYPOINT ["/bin/explorer-backend"] +EXPOSE 8080 + +# profiling port +EXPOSE 6060 + +# gRPC port +EXPOSE 9990 diff --git a/cmd/collector/main.go b/cmd/collector/main.go new file mode 100644 index 0000000..614b51e --- /dev/null +++ b/cmd/collector/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" + "github.com/spacemeshos/go-spacemesh/log" + "github.com/spacemeshos/dash-backend/collector" + "github.com/spacemeshos/explorer-backend/storage" +) + +var ( + version string + commit string + branch string +) + +var ( + nodeAddressStringFlag string + mongoDbUrlStringFlag string + mongoDbNameStringFlag string +) + +var flags = []cli.Flag{ + cli.StringFlag{ + Name: "node", + Usage: "Spacemesh node API address string in format :", + Required: false, + Destination: &nodeAddressStringFlag, + Value: "localhost:9092", + }, + cli.StringFlag{ + Name: "mongodb", + Usage: "Explorer MongoDB Uri string in format mongodb://:", + Required: false, + Destination: &mongoDbUrlStringFlag, + Value: "mongodb://localhost:27017", + }, + cli.StringFlag{ + Name: "db", + Usage: "MongoDB Explorer database name string", + Required: false, + Destination: &mongoDbNameStringFlag, + Value: "explorer", + }, +} + +func main() { + app := cli.NewApp() + app.Name = "Spacemesh Explorer Collector" + app.Version = fmt.Sprintf("%s, commit '%s', branch '%s'", version, commit, branch) + app.Flags = flags + app.Writer = os.Stderr + + app.Action = func(ctx *cli.Context) (error) { + + log.InitSpacemeshLoggingSystem("", "spacemesh-explorer-collector.log") + + mongoStorage := storage.New() + + err := mongoStorage.Open(mongoDbUrlStringFlag, mongoDbNameStringFlag) + if err != nil { + log.Info("MongoDB storage open error %v", err) + return err + } + + collector := collector.NewCollector(nodeAddressStringFlag, storage) + + collector.Run() + + log.Info("Collector is shutdown") + return nil + } + + if err := app.Run(os.Args); err != nil { + log.Info("%+v", err) + os.Exit(1) + } + + os.Exit(0) +} diff --git a/explorer-backend.go b/explorer-backend.go new file mode 100644 index 0000000..bd66189 --- /dev/null +++ b/explorer-backend.go @@ -0,0 +1,2 @@ +package explorer_backend + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fab5f84 --- /dev/null +++ b/go.mod @@ -0,0 +1,4 @@ +module explorer-backend + +go 1.14 + diff --git a/model/account.go b/model/account.go new file mode 100644 index 0000000..73b2f93 --- /dev/null +++ b/model/account.go @@ -0,0 +1,26 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" + pb "github.com/spacemeshos/api/release/go/spacemesh/v1" + "github.com/spacemeshos/explorer-backend/utils" +) + +type Account struct { + Address string // account public address + Balance uint64 // known account balance +} + +type AccountService interface { + GetAccount(ctx context.Context, query *bson.D) (*Account, error) + GetAccounts(ctx context.Context, query *bson.D) ([]*Account, error) + SaveAccount(ctx context.Context, in *Account) error +} + +func NewAccount(in *pb.Account) *Account { + return &Account{ + Address: utils.ToAddressString(account.GetAccountId().GetAddress()), + Balance: Amount(account.GetStateCurrent().GetBalance().GetValue()), + } +} + diff --git a/model/app.go b/model/app.go new file mode 100644 index 0000000..f94a41a --- /dev/null +++ b/model/app.go @@ -0,0 +1,15 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" +) + +type App struct { + Address string +} + +type AppService interface { + GetAccount(ctx context.Context, query *bson.D) (*App, error) + GetApps(ctx context.Context, query *bson.D) ([]*App, error) + SaveApp(ctx context.Context, in *App) error +} diff --git a/model/atx.go b/model/atx.go new file mode 100644 index 0000000..7da0e10 --- /dev/null +++ b/model/atx.go @@ -0,0 +1,34 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" + pb "github.com/spacemeshos/api/release/go/spacemesh/v1" + "github.com/spacemeshos/explorer-backend/utils" +) + +type Activation struct { + Id string + Layer uint64 // the layer that this activation is part of + SmesherId string // id of smesher who created the ATX + Coinbase string // coinbase account id + PrevAtx string // previous ATX pointed to + CommitmentSize uint64 // commitment size in bytes +} + +type ActivationService interface { + GetActivation(ctx context.Context, query *bson.D) (*Activation, error) + GetActivations(ctx context.Context, query *bson.D) ([]*Activation, error) + SaveActivation(ctx context.Context, in *Activation) error +} + +func NewActivation(atx *pb.Activation) *Activation { + return &Activation{ + Id: utils.BytesToHex(atx.GetId().GetId()), + Layer: LayerID(atx.GetLayer().GetNumber()), + SmesherId: utils.BytesToHex(atx.GetSmesherId().GetId()), + Coinbase: utils.BytesToAddressString(atx.GetCoinbase().GetAddress()), + PrevAtx: utils.BytesToHex(atx.GetPrevAtx().GetId()), + CommitmentSize: atx.GetCommitmentSize(), + } +} + diff --git a/model/block.go b/model/block.go new file mode 100644 index 0000000..b767727 --- /dev/null +++ b/model/block.go @@ -0,0 +1,16 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" +) + +type Block struct { + Id string + Layer uint64 +} + +type BlockService interface { + GetBlock(ctx context.Context, query *bson.D) (*Block, error) + GetBlocks(ctx context.Context, query *bson.D) ([]*Block, error) + SaveBlock(ctx context.Context, in *Block) error +} diff --git a/model/epoch.go b/model/epoch.go new file mode 100644 index 0000000..21d37c6 --- /dev/null +++ b/model/epoch.go @@ -0,0 +1,32 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" +) + +type Statistics struct { + Capacity uint64 // Average tx/s rate over capacity considering all layers in the current epoch. + Decentral uint64 // Distribution of storage between all active smeshers. + Smeshers uint64 // Number of active smeshers in the current epoch. + Transactions uint64 // Total number of transactions processed by the state transition function. + Accounts uint64 // Total number of on-mesh accounts with a non-zero coin balance as of the current epoch. + Circulation uint64 // Total number of Smesh coins in circulation. This is the total balances of all on-mesh accounts. + Rewards uint64 // Total amount of Smesh minted as mining rewards as of the last known reward distribution event. + Security uint64 // Total amount of storage committed to the network based on the ATXs in the previous epoch. +} + +type Stats struct { + Current Statistics + Cumulative Statistics +} + +type Epoch struct { + Number uint64 + Stats Stats +} + +type EpochService interface { + GetEpoch(ctx context.Context, query *bson.D) (*Epoch, error) + GetEpochs(ctx context.Context, query *bson.D) ([]*Epoch, error) + SaveEpoch(ctx context.Context, in *Epoch) error +} diff --git a/model/layer.go b/model/layer.go new file mode 100644 index 0000000..711f1bf --- /dev/null +++ b/model/layer.go @@ -0,0 +1,56 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" + pb "github.com/spacemeshos/api/release/go/spacemesh/v1" + "github.com/spacemeshos/explorer-backend/utils" +) + +type Layer struct { + Number uint64 + Status int +} + +type LayerService interface { + GetLayer(ctx context.Context, query *bson.D) (*Layer, error) + GetLayers(ctx context.Context, query *bson.D) ([]*Layer, error) + SaveLayer(ctx context.Context, in *Layer) error +} + +func NewLayer(l *pb.Layer) (*Layer, []*Block, []*Activation, make(map[string]*Transaction) { + pbBlocks := l.GetBlocks() + pbAtxs := l.GetActivations() + layer := &Layer{ + Number: LayerID(l.GetNumber().GetNumber()), + Status: l.GetStatus(), + } + + blocks := make([]*Block, len(blocks)), + atxs := make([]*Activation, len(atxs)), + txs := make(map[string]Transaction), + + for i, b := range pbBlocks { + blocks[i] := &Block{ + Id: utils.ToHex(b.GetId()), + Layer: layer.Number, + } + for _, t := range b.GetTransactions() { + tx := NewTransaction(t, layer.Number, blocks[i].Id) + txs[tx.Id] = tx + } + } + + for i, a := range pbAtxs { + atxs[i] = NewActivation(a) + } + + return layer, blocks, atxs, txs +} + +func IsApprovedLayer(l *pb.Layer) bool { + return l.GetStatus() >= pb.Layer_LAYER_STATUS_APPROVED +} + +func IsConfirmedLayer(l *pb.Layer) bool { + return l.GetStatus() == pb.Layer_LAYER_STATUS_CONFIRMED +} diff --git a/model/reward.go b/model/reward.go new file mode 100644 index 0000000..3ffd64e --- /dev/null +++ b/model/reward.go @@ -0,0 +1,36 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" + pb "github.com/spacemeshos/api/release/go/spacemesh/v1" + "github.com/spacemeshos/explorer-backend/utils" +) + +type Reward struct { + Layer uint64 + Total uint64 + LayerReward uint64 + LayerComputed uint64 // layer number of the layer when reward was computed + // tx_fee = total - layer_reward + Coinbase string // account awarded this reward + Smesher string // it will be nice to always have this in reward events +} + +type RewardService interface { + GetReward(ctx context.Context, query *bson.D) (*Reward, error) + GetRewards(ctx context.Context, query *bson.D) ([]*Reward, error) + SaveReward(ctx context.Context, in *Reward) error +} + +func NewReward(reward *pb.Reward) *Reward { + var smesherId SmesherID + copy(smesherId[:], reward.GetSmesher().GetId()) + return &Reward{ + Layer: LayerID(reward.GetLayer().GetNumber()), + Total: Amount(reward.GetTotal().GetValue()), + Layer_reward: Amount(reward.GetLayerReward().GetValue()), + Layer_computed: LayerID(reward.GetLayerComputed().GetNumber()), + Coinbase: BytesToAddress(reward.GetCoinbase().GetAddress()), + Smesher: smesherId, + } +} diff --git a/model/smesher.go b/model/smesher.go new file mode 100644 index 0000000..858fbc6 --- /dev/null +++ b/model/smesher.go @@ -0,0 +1,24 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" + pb "github.com/spacemeshos/api/release/go/spacemesh/v1" + "github.com/spacemeshos/explorer-backend/utils" +) + +type Geo struct { + Name string `json:"name"` + Coordinates [2]float64 `json:"coordinates"` +} + +type Smesher struct { + Id string + Geo Geo + CommitmentSize uint64 // commitment size in bytes +} + +type SmesherService interface { + GetSmesher(ctx context.Context, query *bson.D) (*Smesher, error) + GetSmeshers(ctx context.Context, query *bson.D) ([]*Smesher, error) + SaveSmesher(ctx context.Context, in *Smesher) error +} diff --git a/model/tx.go b/model/tx.go new file mode 100644 index 0000000..e91d4c3 --- /dev/null +++ b/model/tx.go @@ -0,0 +1,90 @@ +package model + +import ( + "go.mongodb.org/mongo-driver/bson" + pb "github.com/spacemeshos/api/release/go/spacemesh/v1" + "github.com/spacemeshos/explorer-backend/utils" +) + +type Transaction struct { + Id string + + Layer uint64 + Block string + Index uint32 // the index of the tx in the ordered list of txs to be executed by stf in the layer + Result int + + GasProvided uint64 + GasPrice uint64 + GasUsed uint64 // gas units used by the transaction (gas price in tx) + Fee uint64 // transaction fee charged for the transaction + + Amount uint64 // amount of coin transfered in this tx by sender + Counter uint64 // tx counter aka nonce + + Type int + Scheme int // the signature's scheme + Signature string // the signature itself + PublicKey string // included in schemes which require signer to provide a public key + + Sender string // tx originator, should match signer inside Signature + Receiver string + SvmData string // svm binary data. Decode with svm-codec +} + +type TransactionReceipt struct { + Id string + Layer uint64 + Index uint32 // the index of the tx in the ordered list of txs to be executed by stf in the layer + Result int + GasUsed uint64 // gas units used by the transaction (gas price in tx) + Fee uint64 // transaction fee charged for the transaction + SvmData string // svm binary data. Decode with svm-codec +} + +type TransactionService interface { + GetTransaction(ctx context.Context, query *bson.D) (*Transaction, error) + GetTransactions(ctx context.Context, query *bson.D) ([]*Transaction, error) + SaveTransaction(ctx context.Context, in *Transaction) error +} + +func NewTransactionReceipt(txReceipt *pb.TransactionReceipt) *TransactionReceipt { + return &TransactionReceipt{ + Id: utils.BytesToHex(in.GetId().GetId()), + Result: txReceipt.GetResult(), + GasUsed: txReceipt.GetGasUsed(), + Fee: txReceipt.GetFee().GetValue(), + Layer: txReceipt.GetLayerNumber(), + Index: txReceipt.GetIndex(), + SvmData: txReceipt.GetSvmData(), + } +} + +func NewTransaction(in *pb.Transaction, layer uint64, blockId string) *Transaction { + gas := in.GetGasOffered() + sig := in.GetSignature() + + tx := &Transaction{ + Id: utils.BytesToHex(in.GetId().GetId()), + Sender: utils.BytesToAddressString(in.GetSender().GetAddress()), + Amount: uint64(in.GetAmount().GetValue()), + Counter: in.GetCounter(), + Layer: layer, + Block: blockId, + GasProvided: gas.GetGasProvided(), + GasPrice: gas.GetGasPrice(), + Scheme: sig.GetScheme(), + Signature: utils.BytesToHex(sig.GetSignature()), + PublicKey: utils.BytesToHex(sig.GetPublicKey()), + } + + if data := t.GetCoinTransfer(); data != nil { + tx.Receiver = utils.BytesToAddressString(data.GetReceiver().GetAddress()), + } else if data := t.GetSmartContract(); data != nil { + tx.Type = data.GetType(), + tx.SvmData = data.GetData(), + tx.Receiver = utils.BytesToAddressString(data.GetAccountId().GetAddress()), + } + + return tx +} diff --git a/storage/account.go b/storage/account.go new file mode 100644 index 0000000..29697aa --- /dev/null +++ b/storage/account.go @@ -0,0 +1,68 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +func (s *Storage) GetAccount(parent context.Context, query *bson.D) (*Account, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("accounts").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + account := &model.Account{ + Address: doc.Lookup("address").String(), + Balance: uint64(doc.Lookup("balance").Int64()), + } + return account, nil +} + +func (s *Storage) GetAccounts(parent context.Context, query *bson.D) ([]*Account, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("accounts").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + accounts := make([]*model.Account, len(docs), len(docs)) + for i, doc := range docs { + accounts[i] = &model.Account{ + Address: doc.Lookup("address").String(), + Balance: uint64(doc.Lookup("balance").Int64()), + } + } + return accounts, nil +} + +func (s *Storage) SaveAccount(parent context.Context, in *Account) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("accounts").InsertOne(ctx, bson.D{ + {"address", in.Address}, + {"balance", in.Balance}, + }) + return err +} diff --git a/storage/app.go b/storage/app.go new file mode 100644 index 0000000..3e699fd --- /dev/null +++ b/storage/app.go @@ -0,0 +1,65 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +func (s *Storage) GetApp(parent context.Context, query *bson.D) (*model.App, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("apps").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + account := &model.App{ + Address: doc.Lookup("address").String(), + } + return account, nil +} + +func (s *Storage) GetApps(parent context.Context, query *bson.D) ([]*model.App, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("apps").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + accounts := make([]*model.App, len(docs), len(docs)) + for i, doc := range docs { + accounts[i] = &model.App{ + Address: doc.Lookup("address").String(), + } + } + return accounts, nil +} + +func (s *Storage) SaveApp(parent context.Context, in *model.App) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("apps").InsertOne(ctx, bson.D{ + {"address", in.Address}, + }) + return err +} diff --git a/storage/atx.go b/storage/atx.go new file mode 100644 index 0000000..c177df1 --- /dev/null +++ b/storage/atx.go @@ -0,0 +1,99 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +func (s *Storage) GetActivation(parent context.Context, query *bson.D) (*model.Activation, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("atxs").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + account := &model.Activation{ + Id: doc.Lookup("id").String(), + Layer: uint64(doc.Lookup("layer").Int64()), + SmesherId: doc.Lookup("smesher").String(), + Coinbase: doc.Lookup("coinbase").String(), + PrevAtx: doc.Lookup("prevAtx").String(), + CommitmentSize: uint64(doc.Lookup("cSize").Int64()), + } + return account, nil +} + +func (s *Storage) GetActivations(parent context.Context, query *bson.D) ([]*model.Activation, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("atxs").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + accounts := make([]*model.Activation, len(docs), len(docs)) + for i, doc := range docs { + accounts[i] = &model.Activation{ + Id: doc.Lookup("id").String(), + Layer: uint64(doc.Lookup("layer").Int64()), + SmesherId: doc.Lookup("smesher").String(), + Coinbase: doc.Lookup("coinbase").String(), + PrevAtx: doc.Lookup("prevAtx").String(), + CommitmentSize: uint64(doc.Lookup("cSize").Int64()), + } + } + return accounts, nil +} + +func (s *Storage) SaveActivation(parent context.Context, in *model.Activation) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("atxs").InsertOne(ctx, bson.D{ + {"id", in.Id}, + {"layer", in.Layer}, + {"smesher", in.SmesherId}, + {"coinbase", in.Coinbase}, + {"prevAtx", in.PrevAtx}, + {"cSize", in.CommitmentSize}, + }) + return err +} + +func (s *Storage) SaveActivations(parent context.Context, in []*model.Activation) error { + ctx, cancel := context.WithTimeout(parent, 10*time.Second) + defer cancel() + for _, atx := range in { + res, err := s.db.Collection("atxs").InsertOne(ctx, bson.D{ + {"id", atx.Id}, + {"layer", atx.Layer}, + {"smesher", atx.SmesherId}, + {"coinbase", atx.Coinbase}, + {"prevAtx", atx.PrevAtx}, + {"cSize", atx.CommitmentSize}, + }) + if err != nil { + return err + } + } + return nil +} diff --git a/storage/block.go b/storage/block.go new file mode 100644 index 0000000..f4adf0b --- /dev/null +++ b/storage/block.go @@ -0,0 +1,88 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +type BlockService interface { + GetBlock(ctx context.Context, query *bson.D) (*Block, error) + GetBlocks(ctx context.Context, query *bson.D) ([]*Block, error) + SaveBlock(ctx context.Context, in *Block) error +} +func (s *Storage) GetBlock(parent context.Context, query *bson.D) (*model.Block, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("blocks").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + account := &model.Block{ + Id: doc.Lookup("id").String(), + Layer: uint64(doc.Lookup("layer").Int64()), + } + return account, nil +} + +func (s *Storage) GetBlocks(parent context.Context, query *bson.D) ([]*model.Block, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("blocks").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + blocks := make([]*model.Block, len(docs), len(docs)) + for i, doc := range docs { + blocks[i] = &model.Block{ + Id: doc.Lookup("id").String(), + Layer: uint64(doc.Lookup("layer").Int64()), + } + } + return blocks, nil +} + +func (s *Storage) SaveBlock(parent context.Context, in *model.Block) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("blocks").InsertOne(ctx, bson.D{ + {"id", in.Id}, + {"layer", in.Layer}, + }) + return err +} + +func (s *Storage) SaveBlocks(parent context.Context, in []*model.Block) error { + ctx, cancel := context.WithTimeout(parent, 10*time.Second) + defer cancel() + for _, block := range in { + res, err := s.db.Collection("blocks").InsertOne(ctx, bson.D{ + {"id", block.Id}, + {"layer", block.Layer}, + }) + if err != nil { + return err + } + } + return nil +} diff --git a/storage/epoch.go b/storage/epoch.go new file mode 100644 index 0000000..af62eaa --- /dev/null +++ b/storage/epoch.go @@ -0,0 +1,125 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +func (s *Storage) GetEpoch(parent context.Context, query *bson.D) (*model.Epoch, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("epochs").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + epoch := &model.Epoch{ + number: Number, + } + stats := doc.Lookup("stats").Document() + current := stats.Lookup("current").Document() + epoch.Stats.Current.Capacity = uint64(current.Lookup("capacity").Int64()) + epoch.Stats.Current.Decentral = uint64(current.Lookup("decentral").Int64()) + epoch.Stats.Current.Smeshers = uint64(current.Lookup("smeshers").Int64()) + epoch.Stats.Current.Transactions = uint64(current.Lookup("transactions").Int64()) + epoch.Stats.Current.Accounts = uint64(current.Lookup("accounts").Int64()) + epoch.Stats.Current.Circulation = uint64(current.Lookup("circulation").Int64()) + epoch.Stats.Current.Rewards = uint64(current.Lookup("rewards").Int64()) + epoch.Stats.Current.Security = uint64(current.Lookup("security").Int64()) + cumulative := stats.Lookup("cumulative").Document() + epoch.Stats.Cumulative.Capacity = uint64(cumulative.Lookup("capacity").Int64()) + epoch.Stats.Cumulative.Decentral = uint64(cumulative.Lookup("decentral").Int64()) + epoch.Stats.Cumulative.Smeshers = uint64(cumulative.Lookup("smeshers").Int64()) + epoch.Stats.Cumulative.Transactions = uint64(cumulative.Lookup("transactions").Int64()) + epoch.Stats.Cumulative.Accounts = uint64(cumulative.Lookup("accounts").Int64()) + epoch.Stats.Cumulative.Circulation = uint64(cumulative.Lookup("circulation").Int64()) + epoch.Stats.Cumulative.Rewards = uint64(cumulative.Lookup("rewards").Int64()) + epoch.Stats.Cumulative.Security = uint64(cumulative.Lookup("security").Int64()) + return epoch, nil +} + +func (s *Storage) GetEpochs(parent context.Context, query *bson.D) ([]*model.Epoch, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("epochs").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + accounts := make([]*model.Epoch, len(docs), len(docs)) + for i, doc := range docs { + epochs[i] := &model.Epoch{ + Number: number, + } + stats := doc.Lookup("stats").Document() + current := stats.Lookup("current").Document() + epoch.Stats.Current.Capacity = uint64(current.Lookup("capacity").Int64()) + epoch.Stats.Current.Decentral = uint64(current.Lookup("decentral").Int64()) + epoch.Stats.Current.Smeshers = uint64(current.Lookup("smeshers").Int64()) + epoch.Stats.Current.Transactions = uint64(current.Lookup("transactions").Int64()) + epoch.Stats.Current.Accounts = uint64(current.Lookup("accounts").Int64()) + epoch.Stats.Current.Circulation = uint64(current.Lookup("circulation").Int64()) + epoch.Stats.Current.Rewards = uint64(current.Lookup("rewards").Int64()) + epoch.Stats.Current.Security = uint64(current.Lookup("security").Int64()) + cumulative := stats.Lookup("cumulative").Document() + epoch.Stats.Cumulative.Capacity = uint64(cumulative.Lookup("capacity").Int64()) + epoch.Stats.Cumulative.Decentral = uint64(cumulative.Lookup("decentral").Int64()) + epoch.Stats.Cumulative.Smeshers = uint64(cumulative.Lookup("smeshers").Int64()) + epoch.Stats.Cumulative.Transactions = uint64(cumulative.Lookup("transactions").Int64()) + epoch.Stats.Cumulative.Accounts = uint64(cumulative.Lookup("accounts").Int64()) + epoch.Stats.Cumulative.Circulation = uint64(cumulative.Lookup("circulation").Int64()) + epoch.Stats.Cumulative.Rewards = uint64(cumulative.Lookup("rewards").Int64()) + epoch.Stats.Cumulative.Security = uint64(cumulative.Lookup("security").Int64()) + } + return epochs, nil +} + +func (s *Storage) SaveEpoch(parent context.Context, in *model.Epoch) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + _, err := s.db.Collection("epochs").InsertOne(ctx, bson.D{ + {"number", epoch.Number}, + {"stats", bson.D{ + {"current", bson.D{ + {"capacity", epoch.Stats.Current.Capacity}, + {"decentral", epoch.Stats.Current.Decentral}, + {"smeshers", epoch.Stats.Current.Smeshers}, + {"transactions", epoch.Stats.Current.Transactions}, + {"accounts", epoch.Stats.Current.Accounts}, + {"circulation", epoch.Stats.Current.Circulation}, + {"rewards", epoch.Stats.Current.Rewards}, + {"security", epoch.Stats.Current.Security}, + }}, + {"cumulative", bson.D{ + {"capacity", epoch.Stats.Cumulative.Capacity}, + {"decentral", epoch.Stats.Cumulative.Decentral}, + {"smeshers", epoch.Stats.Cumulative.Smeshers}, + {"transactions", epoch.Stats.Cumulative.Transactions}, + {"accounts", epoch.Stats.Cumulative.Accounts}, + {"circulation", epoch.Stats.Cumulative.Circulation}, + {"rewards", epoch.Stats.Cumulative.Rewards}, + {"security", epoch.Stats.Cumulative.Security}, + }}, + }}, + }) + return err +} diff --git a/storage/layer.go b/storage/layer.go new file mode 100644 index 0000000..e18acf2 --- /dev/null +++ b/storage/layer.go @@ -0,0 +1,73 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +type Layer struct { + Number uint64 + Status int +} + +func (s *Storage) GetLayer(parent context.Context, query *bson.D) (*model.Layer, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("layers").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + account := &model.Layer{ + Number: uint64(doc.Lookup("number").Int64()), + Status: doc.Lookup("status").Int(), + } + return account, nil +} + +func (s *Storage) GetLayers(parent context.Context, query *bson.D) ([]*model.Layer, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("layers").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + layers := make([]*model.Layer, len(docs), len(docs)) + for i, doc := range docs { + layers[i] = &model.Layer{ + Number: uint64(doc.Lookup("number").Int64()), + Status: doc.Lookup("status").Int(), + } + } + return layers, nil +} + +func (s *Storage) SaveLayer(parent context.Context, in *model.Layer) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("layers").InsertOne(ctx, bson.D{ + {"number", in.Number}, + {"status", in.Status}, + }) + return err +} diff --git a/storage/reward.go b/storage/reward.go new file mode 100644 index 0000000..0e1f224 --- /dev/null +++ b/storage/reward.go @@ -0,0 +1,80 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +func (s *Storage) GetReward(parent context.Context, query *bson.D) (*model.Reward, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("rewards").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + account := &model.Reward{ + Layer: uint64(doc.Lookup("layer").Int64()), + Total: uint64(doc.Lookup("total").Int64()), + LayerReward: uint64(doc.Lookup("layerReward").Int64()), + LayerComputed: uint64(doc.Lookup("layerComputed").Int64()), + Coinbase: doc.Lookup("coinbase").String(), + Smesher: doc.Lookup("smesher").String(), + } + return account, nil +} + +func (s *Storage) GetRewards(parent context.Context, query *bson.D) ([]*model.Reward, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("rewards").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + rewards := make([]*model.Reward, len(docs), len(docs)) + for i, doc := range docs { + rewards[i] = &model.Reward{ + Layer: uint64(doc.Lookup("layer").Int64()), + Total: uint64(doc.Lookup("total").Int64()), + LayerReward: uint64(doc.Lookup("layerReward").Int64()), + LayerComputed: uint64(doc.Lookup("layerComputed").Int64()), + Coinbase: doc.Lookup("coinbase").String(), + Smesher: doc.Lookup("smesher").String(), + } + } + return rewards, nil +} + +func (s *Storage) SaveReward(parent context.Context, in *model.Reward) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("rewards").InsertOne(ctx, bson.D{ + {"layer", in.Layer}, + {"total", in.Total}, + {"layerReward", in.LayerReward}, + {"layerComputed", in.LayerComputed}, + {"coinbase", in.Coinbase}, + {"smesher", in.Smesher}, + }) + return err +} diff --git a/storage/smesher.go b/storage/smesher.go new file mode 100644 index 0000000..b1e4663 --- /dev/null +++ b/storage/smesher.go @@ -0,0 +1,90 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +type Geo struct { + Name string `json:"name"` + Coordinates [2]float64 `json:"coordinates"` +} + +type Smesher struct { + Id string + Geo Geo + CommitmentSize uint64 // commitment size in bytes +} + +func (s *Storage) GetSmesher(parent context.Context, query *bson.D) (*model.Smesher, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("smeshers").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + account := &model.Smesher{ + Id: doc.Lookup("id").String(), + Geo: model.Geo{ + doc.Lookup("name").String(), + { doc.Lookup("lon").Float64(), doc.Lookup("lat").Float64() }, + }, + CommitmentSize: uint64(doc.Lookup("cSize").Int64()), + } + return account, nil +} + +func (s *Storage) GetSmeshers(parent context.Context, query *bson.D) ([]*model.Smesher, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("smeshers").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + smeshers := make([]*model.Smesher, len(docs), len(docs)) + for i, doc := range docs { + smeshers[i] = &model.Smesher{ + Id: doc.Lookup("id").String(), + Geo: model.Geo{ + doc.Lookup("name").String(), + { doc.Lookup("lon").Float64(), doc.Lookup("lat").Float64() }, + }, + CommitmentSize: uint64(doc.Lookup("cSize").Int64()), + } + } + return smeshers, nil +} + +func (s *Storage) SaveSmesher(parent context.Context, in *model.Smesher) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("smeshers").InsertOne(ctx, bson.D{ + {"id", in.Id}, + {"name", in.Geo.Name}, + {"lon", in.Geo.Coordinates[0]}, + {"lat", in.Geo.Coordinates[1]}, + {"cSize", in.CommitmentSize}, + }) + return err +} diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000..64289a4 --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,84 @@ +package storage + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + "github.com/spacemeshos/explorer-backend/model" +) + +type Storage struct { + client *mongo.Client + db *mongo.Database +} + +func New() *Storage { + return &Storage{} +} + +func (s *Storage) Open(dbUrl string, dbName string) error { + ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) + client, err := mongo.Connect(ctx, options.Client().ApplyURI(dbUrl)) + + if err != nil { + return err + } + + err = client.Ping(ctx, nil) + + if err != nil { + return err + } + + s.client = client + s.db = client.Database(dbName) + + return nil +} + +func (s *Storage) Close() { + if s.client != nil { + ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) + s.db = nil + s.client.Disconnect(ctx) + } +} + +func (s *Storage) OnNetworkInfo(netId uint64, genesisTime uint64, epochNumLayers uint64, maxTransactionsPerSecond uint64, layerDuration uint64) { +} + +func (s *Storage) OnLayer(in *pb.Layer) { + if model.IsConfirmedLayer(in) { + layer, blocks, atxs, txs := model.NewLayer(in) + _ := s.SaveLayer(context.Background(), layer) + _ := s.SaveBlocks(context.Background(), blocks) + _ := s.SaveActivations(context.Background(), atxs) + _ := s.SaveTransactiobs(context.Background(), txs) + } +} + +func (s *Storage) OnAccount(in *pb.Account) { + account := model.NewAccount(in) + if account == nil { + return + } + _ := s.SaveAccount(context.Background(), account) +} + +func (s *Storage) OnReward(in *pb.Reward) { + reward := model.NewReward(in) + if reward == nil { + return + } + _ := s.SaveReward(context.Background(), reward) +} + +func (s *Storage) OnTransactionReceipt(in *pb.TransactionReceipt) { + _ := s.UpdateTransaction(context.Background(), model.NewTransactionReceipt(in)) +} diff --git a/storage/tx.go b/storage/tx.go new file mode 100644 index 0000000..4b19b37 --- /dev/null +++ b/storage/tx.go @@ -0,0 +1,160 @@ +package model + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/spacemeshos/go-spacemesh/log" + + "github.com/spacemeshos/explorer-backend/model" +) + +func (s *Storage) GetTransaction(parent context.Context, query *bson.D) (*model.Transaction, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("txs").Find(ctx, query) + if err != nil { + return nil, err + } + if !cursor.Next(ctx) { + return nil, errors.New("Empty result") + } + doc := cursor.Current + account := &model.Transaction{ + Id: doc.Lookup("id").String(), + Layer: uint64(doc.Lookup("layer").Int64()), + Block: doc.Lookup("block").String(), + Index: uint32(doc.Lookup("index").Int32()), + Result: uint64(doc.Lookup("result").Int()), + GasProvided: uint64(doc.Lookup("gasProvided").Int64()), + GasPrice: uint64(doc.Lookup("gasPrice").Int64()), + GasUsed: uint64(doc.Lookup("gasUsed").Int64()), + Fee: uint64(doc.Lookup("fee").Int64()), + Amount: uint64(doc.Lookup("amount").Int64()), + Counter: uint64(doc.Lookup("counter").Int64()), + Type: doc.Lookup("type").Int(), + Scheme: doc.Lookup("scheme").Int(), + Signature: doc.Lookup("signature").String(), + PublicKey: doc.Lookup("pubKey").String(), + Sender: doc.Lookup("sender").String(), + Receiver: doc.Lookup("receiver").String(), + SvmData: doc.Lookup("svmData").String(), + } + return account, nil +} + +func (s *Storage) GetTransactions(parent context.Context, query *bson.D) ([]*model.Transaction, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + cursor, err := s.db.Collection("txs").Find(ctx, query) + if err != nil { + return nil, err + } + var docs interface{} = []bson.D{} + err = cursor.All(ctx, &docs) + if err != nil { + return nil, err + } + if len(docs) == 0 { + return nil, nil + } + txs := make([]*model.Transaction, len(docs), len(docs)) + for i, doc := range docs { + txs[i] = &model.Transaction{ + Id: doc.Lookup("id").String(), + Layer: uint64(doc.Lookup("layer").Int64()), + Block: doc.Lookup("block").String(), + Index: uint32(doc.Lookup("index").Int32()), + Result: uint64(doc.Lookup("result").Int()), + GasProvided: uint64(doc.Lookup("gasProvided").Int64()), + GasPrice: uint64(doc.Lookup("gasPrice").Int64()), + GasUsed: uint64(doc.Lookup("gasUsed").Int64()), + Fee: uint64(doc.Lookup("fee").Int64()), + Amount: uint64(doc.Lookup("amount").Int64()), + Counter: uint64(doc.Lookup("counter").Int64()), + Type: doc.Lookup("type").Int(), + Scheme: doc.Lookup("scheme").Int(), + Signature: doc.Lookup("signature").String(), + PublicKey: doc.Lookup("pubKey").String(), + Sender: doc.Lookup("sender").String(), + Receiver: doc.Lookup("receiver").String(), + SvmData: doc.Lookup("svmData").String(), + } + } + return txs, nil +} + +func (s *Storage) SaveTransaction(parent context.Context, in *model.Transaction) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("txs").InsertOne(ctx, bson.D{ + {"id", in.Id}, + {"layer", in.Layer}, + {"block", in.Block}, + {"index", in.Index}, + {"result", in.Result}, + {"gasProvided", in.GasProvided}, + {"gasPrice", in.GasPrice}, + {"gasUsed", in.GasUsed}, + {"fee", in.Fee}, + {"amount", in.Amount}, + {"counter", in.Counter}, + {"type", in.Type}, + {"scheme", in.Scheme}, + {"signature", in.Signature}, + {"pubKey", in.PublicKey}, + {"sender", in.Sender}, + {"receiver", in.Receiver}, + {"svmData", in.SvmData}, + }) + return err +} + +func (s *Storage) SaveTransactions(parent context.Context, in map[string]*model.Transaction) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + for _, tx := range in { + res, err := s.db.Collection("txs").InsertOne(ctx, bson.D{ + {"id", tx.Id}, + {"layer", tx.Layer}, + {"block", tx.Block}, + {"index", tx.Index}, + {"result", tx.Result}, + {"gasProvided", tx.GasProvided}, + {"gasPrice", tx.GasPrice}, + {"gasUsed", tx.GasUsed}, + {"fee", tx.Fee}, + {"amount", tx.Amount}, + {"counter", tx.Counter}, + {"type", tx.Type}, + {"scheme", tx.Scheme}, + {"signature", tx.Signature}, + {"pubKey", tx.PublicKey}, + {"sender", tx.Sender}, + {"receiver", tx.Receiver}, + {"svmData", tx.SvmData}, + }) + if err != nil { + return err + } + } + return nil +} + +func (s *Storage) UpdateTransaction(parent context.Context, in *model.TransactionReceipt) error { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + res, err := s.db.Collection("txs").UpdateOne(ctx, bson.D{{{"id", in.Id}}}, bson.D{ + {"index", in.Index}, + {"result", in.Result}, + {"gasUsed", in.GasUsed}, + {"fee", in.Fee}, + {"svmData", in.SvmData}, + }) + return err +} diff --git a/utils/bytes-to-string.go b/utils/bytes-to-string.go new file mode 100644 index 0000000..b44759c --- /dev/null +++ b/utils/bytes-to-string.go @@ -0,0 +1,31 @@ +package utils + +import ( + "encoding/hex" + "github.com/spacemeshos/go-spacemesh/crypto/sha3" + "github.com/spacemeshos/go-spacemesh/common/util" +) + +// Hex returns an EIP55-compliant hex string representation of the address. +func BytesToAddressString(a []byte) string { + unchecksummed := hex.EncodeToString(a[:]) + sha := sha3.NewKeccak256() + sha.Write([]byte(unchecksummed)) + hash := sha.Sum(nil) + + result := []byte(unchecksummed) + for i := 0; i < len(result); i++ { + hashByte := hash[i/2] + if i%2 == 0 { + hashByte = hashByte >> 4 + } else { + hashByte &= 0xf + } + if result[i] > '9' && hashByte > 7 { + result[i] -= 32 + } + } + return "0x" + string(result) +} + +func BytesToHex(a []byte) string { return util.Encode(a[:]) }