From 3908f274ae2d72eb225859dfc61c863f1afc7156 Mon Sep 17 00:00:00 2001 From: D-Stacks <78099568+D-Stacks@users.noreply.github.com> Date: Sun, 5 Jun 2022 15:42:08 +0200 Subject: [PATCH] [Ready] - RPC & UtxoIndex: keep track of, query and test circulating supply. (#2070) * start * pass tests, (cannot get before put!) and clean up. * add rpc call. * create and pass tests, fix bugs. fully implement rpc * As always fmt * remover old test * clean up proto comment * put the logger back in place. * revert back to 10 sec limit. * migration, change utxoChanged removal to whole utxoEntryPair, add methods to update circulating supply, intialize circulating supply from reset. * Update utxoindex.go * ad * rename to max, change comment * one more total to max Co-authored-by: Ori Newman --- app/appmessage/message.go | 8 +- app/appmessage/rpc_get_coin_supply.go | 40 +++++ app/rpc/rpc.go | 1 + app/rpc/rpccontext/notificationmanager.go | 12 +- app/rpc/rpchandlers/get_coin_supply.go | 29 ++++ cmd/kaspactl/commands.go | 1 + .../consensus/model/externalapi/utxoentry.go | 2 +- domain/utxoindex/model.go | 2 +- domain/utxoindex/store.go | 158 ++++++++++++++--- domain/utxoindex/utxoindex.go | 28 ++- .../grpcserver/protowire/messages.pb.go | 96 ++++++++--- .../grpcserver/protowire/messages.proto | 2 + .../server/grpcserver/protowire/rpc.md | 29 ++++ .../server/grpcserver/protowire/rpc.pb.go | 161 ++++++++++++++++-- .../server/grpcserver/protowire/rpc.proto | 9 + .../grpcserver/protowire/rpc_get_supply.go | 54 ++++++ .../server/grpcserver/protowire/wire.go | 14 ++ .../network/rpcclient/rpc_get_coin_supply.go | 20 +++ testing/integration/utxo_index_test.go | 21 +++ 19 files changed, 617 insertions(+), 70 deletions(-) create mode 100644 app/appmessage/rpc_get_coin_supply.go create mode 100644 app/rpc/rpchandlers/get_coin_supply.go create mode 100644 infrastructure/network/netadapter/server/grpcserver/protowire/rpc_get_supply.go create mode 100644 infrastructure/network/rpcclient/rpc_get_coin_supply.go diff --git a/app/appmessage/message.go b/app/appmessage/message.go index 71c04c4c3..8f778b117 100644 --- a/app/appmessage/message.go +++ b/app/appmessage/message.go @@ -161,6 +161,8 @@ const ( CmdNewBlockTemplateNotificationMessage CmdGetMempoolEntriesByAddressesRequestMessage CmdGetMempoolEntriesByAddressesResponseMessage + CmdGetCoinSupplyRequestMessage + CmdGetCoinSupplyResponseMessage ) // ProtocolMessageCommandToString maps all MessageCommands to their string representation @@ -294,8 +296,10 @@ var RPCMessageCommandToString = map[MessageCommand]string{ CmdNotifyNewBlockTemplateRequestMessage: "NotifyNewBlockTemplateRequest", CmdNotifyNewBlockTemplateResponseMessage: "NotifyNewBlockTemplateResponse", CmdNewBlockTemplateNotificationMessage: "NewBlockTemplateNotification", - CmdGetMempoolEntriesByAddressesRequestMessage: "CmdGetMempoolEntriesByAddressesRequest", - CmdGetMempoolEntriesByAddressesResponseMessage: "CmdGetMempoolEntriesByAddressesResponse", + CmdGetMempoolEntriesByAddressesRequestMessage: "GetMempoolEntriesByAddressesRequest", + CmdGetMempoolEntriesByAddressesResponseMessage: "GetMempoolEntriesByAddressesResponse", + CmdGetCoinSupplyRequestMessage: "GetCoinSupplyRequest", + CmdGetCoinSupplyResponseMessage: "GetCoinSupplyResponse", } // Message is an interface that describes a kaspa message. A type that diff --git a/app/appmessage/rpc_get_coin_supply.go b/app/appmessage/rpc_get_coin_supply.go new file mode 100644 index 000000000..848977d18 --- /dev/null +++ b/app/appmessage/rpc_get_coin_supply.go @@ -0,0 +1,40 @@ +package appmessage + +// GetCoinSupplyRequestMessage is an appmessage corresponding to +// its respective RPC message +type GetCoinSupplyRequestMessage struct { + baseMessage +} + +// Command returns the protocol command string for the message +func (msg *GetCoinSupplyRequestMessage) Command() MessageCommand { + return CmdGetCoinSupplyRequestMessage +} + +// NewGetCoinSupplyRequestMessage returns a instance of the message +func NewGetCoinSupplyRequestMessage() *GetCoinSupplyRequestMessage { + return &GetCoinSupplyRequestMessage{} +} + +// GetCoinSupplyResponseMessage is an appmessage corresponding to +// its respective RPC message +type GetCoinSupplyResponseMessage struct { + baseMessage + MaxSompi uint64 + CirculatingSompi uint64 + + Error *RPCError +} + +// Command returns the protocol command string for the message +func (msg *GetCoinSupplyResponseMessage) Command() MessageCommand { + return CmdGetCoinSupplyResponseMessage +} + +// NewGetCoinSupplyResponseMessage returns a instance of the message +func NewGetCoinSupplyResponseMessage(maxSompi uint64, circulatingSompi uint64) *GetCoinSupplyResponseMessage { + return &GetCoinSupplyResponseMessage{ + MaxSompi: maxSompi, + CirculatingSompi: circulatingSompi, + } +} diff --git a/app/rpc/rpc.go b/app/rpc/rpc.go index e0182625e..770b9eb22 100644 --- a/app/rpc/rpc.go +++ b/app/rpc/rpc.go @@ -49,6 +49,7 @@ var handlers = map[appmessage.MessageCommand]handler{ appmessage.CmdEstimateNetworkHashesPerSecondRequestMessage: rpchandlers.HandleEstimateNetworkHashesPerSecond, appmessage.CmdNotifyVirtualDaaScoreChangedRequestMessage: rpchandlers.HandleNotifyVirtualDaaScoreChanged, appmessage.CmdNotifyNewBlockTemplateRequestMessage: rpchandlers.HandleNotifyNewBlockTemplate, + appmessage.CmdGetCoinSupplyRequestMessage: rpchandlers.HandleGetCoinSupply, appmessage.CmdGetMempoolEntriesByAddressesRequestMessage: rpchandlers.HandleGetMempoolEntriesByAddresses, } diff --git a/app/rpc/rpccontext/notificationmanager.go b/app/rpc/rpccontext/notificationmanager.go index 679008af1..e9b3ef98c 100644 --- a/app/rpc/rpccontext/notificationmanager.go +++ b/app/rpc/rpccontext/notificationmanager.go @@ -378,9 +378,9 @@ func (nl *NotificationListener) convertUTXOChangesToUTXOsChangedNotification( notification.Added = append(notification.Added, utxosByAddressesEntries...) } } - for scriptPublicKeyString, removedOutpoints := range utxoChanges.Removed { + for scriptPublicKeyString, removedPairs := range utxoChanges.Removed { if listenerAddress, ok := nl.propagateUTXOsChangedNotificationAddresses[scriptPublicKeyString]; ok { - utxosByAddressesEntries := convertUTXOOutpointsToUTXOsByAddressesEntries(listenerAddress.Address, removedOutpoints) + utxosByAddressesEntries := ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(listenerAddress.Address, removedPairs) notification.Removed = append(notification.Removed, utxosByAddressesEntries...) } } @@ -391,8 +391,8 @@ func (nl *NotificationListener) convertUTXOChangesToUTXOsChangedNotification( utxosByAddressesEntries := ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(listenerAddress.Address, addedPairs) notification.Added = append(notification.Added, utxosByAddressesEntries...) } - if removedOutpoints, ok := utxoChanges.Removed[listenerScriptPublicKeyString]; ok { - utxosByAddressesEntries := convertUTXOOutpointsToUTXOsByAddressesEntries(listenerAddress.Address, removedOutpoints) + if removedPairs, ok := utxoChanges.Removed[listenerScriptPublicKeyString]; ok { + utxosByAddressesEntries := ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(listenerAddress.Address, removedPairs) notification.Removed = append(notification.Removed, utxosByAddressesEntries...) } } @@ -406,13 +406,13 @@ func (nl *NotificationListener) convertUTXOChangesToUTXOsChangedNotification( utxosByAddressesEntries := ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(addressString, addedPairs) notification.Added = append(notification.Added, utxosByAddressesEntries...) } - for scriptPublicKeyString, removedOutpoints := range utxoChanges.Removed { + for scriptPublicKeyString, removedPAirs := range utxoChanges.Removed { addressString, err := nl.scriptPubKeyStringToAddressString(scriptPublicKeyString) if err != nil { return nil, err } - utxosByAddressesEntries := convertUTXOOutpointsToUTXOsByAddressesEntries(addressString, removedOutpoints) + utxosByAddressesEntries := ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(addressString, removedPAirs) notification.Removed = append(notification.Removed, utxosByAddressesEntries...) } } diff --git a/app/rpc/rpchandlers/get_coin_supply.go b/app/rpc/rpchandlers/get_coin_supply.go new file mode 100644 index 000000000..757b8a7f6 --- /dev/null +++ b/app/rpc/rpchandlers/get_coin_supply.go @@ -0,0 +1,29 @@ +package rpchandlers + +import ( + "github.com/kaspanet/kaspad/app/appmessage" + "github.com/kaspanet/kaspad/app/rpc/rpccontext" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" + "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" +) + +// HandleGetCoinSupply handles the respectively named RPC command +func HandleGetCoinSupply(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) { + if !context.Config.UTXOIndex { + errorMessage := &appmessage.GetCoinSupplyResponseMessage{} + errorMessage.Error = appmessage.RPCErrorf("Method unavailable when kaspad is run without --utxoindex") + return errorMessage, nil + } + + circulatingSompiSupply, err := context.UTXOIndex.GetCirculatingSompiSupply() + if err != nil { + return nil, err + } + + response := appmessage.NewGetCoinSupplyResponseMessage( + constants.MaxSompi, + circulatingSompiSupply, + ) + + return response, nil +} diff --git a/cmd/kaspactl/commands.go b/cmd/kaspactl/commands.go index b75655b92..d56d1b886 100644 --- a/cmd/kaspactl/commands.go +++ b/cmd/kaspactl/commands.go @@ -37,6 +37,7 @@ var commandTypes = []reflect.Type{ reflect.TypeOf(protowire.KaspadMessage_GetUtxosByAddressesRequest{}), reflect.TypeOf(protowire.KaspadMessage_GetBalanceByAddressRequest{}), + reflect.TypeOf(protowire.KaspadMessage_GetCoinSupplyRequest{}), reflect.TypeOf(protowire.KaspadMessage_BanRequest{}), reflect.TypeOf(protowire.KaspadMessage_UnbanRequest{}), diff --git a/domain/consensus/model/externalapi/utxoentry.go b/domain/consensus/model/externalapi/utxoentry.go index d3b42db60..fb628b607 100644 --- a/domain/consensus/model/externalapi/utxoentry.go +++ b/domain/consensus/model/externalapi/utxoentry.go @@ -5,7 +5,7 @@ package externalapi // score of the block that accepts the tx, its public key script, and how // much it pays. type UTXOEntry interface { - Amount() uint64 + Amount() uint64 // Utxo amount in Sompis ScriptPublicKey() *ScriptPublicKey // The public key script for the output. BlockDAAScore() uint64 // Daa score of the block accepting the tx. IsCoinbase() bool diff --git a/domain/utxoindex/model.go b/domain/utxoindex/model.go index d877f2cd6..2ceb65607 100644 --- a/domain/utxoindex/model.go +++ b/domain/utxoindex/model.go @@ -20,7 +20,7 @@ type UTXOOutpoints map[externalapi.DomainOutpoint]interface{} // a successful update type UTXOChanges struct { Added map[ScriptPublicKeyString]UTXOOutpointEntryPairs - Removed map[ScriptPublicKeyString]UTXOOutpoints + Removed map[ScriptPublicKeyString]UTXOOutpointEntryPairs } // ConvertScriptPublicKeyToString converts the given scriptPublicKey to a string diff --git a/domain/utxoindex/store.go b/domain/utxoindex/store.go index 3891d6efb..dbca42b7b 100644 --- a/domain/utxoindex/store.go +++ b/domain/utxoindex/store.go @@ -2,6 +2,8 @@ package utxoindex import ( "encoding/binary" + + "github.com/kaspanet/kaspad/domain/consensus/database/binaryserialization" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/infrastructure/db/database" "github.com/kaspanet/kaspad/infrastructure/logger" @@ -10,11 +12,13 @@ import ( var utxoIndexBucket = database.MakeBucket([]byte("utxo-index")) var virtualParentsKey = database.MakeBucket([]byte("")).Key([]byte("utxo-index-virtual-parents")) +var circulatingSupplyKey = database.MakeBucket([]byte("")).Key([]byte("utxo-index-circulating-supply")) type utxoIndexStore struct { - database database.Database - toAdd map[ScriptPublicKeyString]UTXOOutpointEntryPairs - toRemove map[ScriptPublicKeyString]UTXOOutpoints + database database.Database + toAdd map[ScriptPublicKeyString]UTXOOutpointEntryPairs + toRemove map[ScriptPublicKeyString]UTXOOutpointEntryPairs + virtualParents []*externalapi.DomainHash } @@ -22,7 +26,7 @@ func newUTXOIndexStore(database database.Database) *utxoIndexStore { return &utxoIndexStore{ database: database, toAdd: make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs), - toRemove: make(map[ScriptPublicKeyString]UTXOOutpoints), + toRemove: make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs), } } @@ -61,7 +65,7 @@ func (uis *utxoIndexStore) add(scriptPublicKey *externalapi.ScriptPublicKey, out return nil } -func (uis *utxoIndexStore) remove(scriptPublicKey *externalapi.ScriptPublicKey, outpoint *externalapi.DomainOutpoint) error { +func (uis *utxoIndexStore) remove(scriptPublicKey *externalapi.ScriptPublicKey, outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry) error { key := ConvertScriptPublicKeyToString(scriptPublicKey) log.Tracef("Removing outpoint %s:%d from scriptPublicKey %s", outpoint.TransactionID, outpoint.Index, key) @@ -76,19 +80,19 @@ func (uis *utxoIndexStore) remove(scriptPublicKey *externalapi.ScriptPublicKey, } } - // Create a UTXOOutpoints entry in `toRemove` if it doesn't exist + // Create a UTXOOutpointEntryPair in `toRemove` if it doesn't exist if _, ok := uis.toRemove[key]; !ok { log.Tracef("Creating key %s in `toRemove`", key) - uis.toRemove[key] = make(UTXOOutpoints) + uis.toRemove[key] = make(UTXOOutpointEntryPairs) } // Return an error if the outpoint already exists in `toRemove` - toRemoveOutpointsOfKey := uis.toRemove[key] - if _, ok := toRemoveOutpointsOfKey[*outpoint]; ok { + toRemovePairsOfKey := uis.toRemove[key] + if _, ok := toRemovePairsOfKey[*outpoint]; ok { return errors.Errorf("cannot remove outpoint %s because it's being removed already", outpoint) } - toRemoveOutpointsOfKey[*outpoint] = struct{}{} + toRemovePairsOfKey[*outpoint] = utxoEntry log.Tracef("Removed outpoint %s:%d from scriptPublicKey %s", outpoint.TransactionID, outpoint.Index, key) @@ -101,7 +105,7 @@ func (uis *utxoIndexStore) updateVirtualParents(virtualParents []*externalapi.Do func (uis *utxoIndexStore) discard() { uis.toAdd = make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs) - uis.toRemove = make(map[ScriptPublicKeyString]UTXOOutpoints) + uis.toRemove = make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs) uis.virtualParents = nil } @@ -115,10 +119,12 @@ func (uis *utxoIndexStore) commit() error { } defer dbTransaction.RollbackUnlessClosed() - for scriptPublicKeyString, toRemoveOutpointsOfKey := range uis.toRemove { + toRemoveSompiSupply := uint64(0) + + for scriptPublicKeyString, toRemoveUTXOOutpointEntryPairs := range uis.toRemove { scriptPublicKey := ConvertStringToScriptPublicKey(scriptPublicKeyString) bucket := uis.bucketForScriptPublicKey(scriptPublicKey) - for outpointToRemove := range toRemoveOutpointsOfKey { + for outpointToRemove, utxoEntryToRemove := range toRemoveUTXOOutpointEntryPairs { key, err := uis.convertOutpointToKey(bucket, &outpointToRemove) if err != nil { return err @@ -127,9 +133,12 @@ func (uis *utxoIndexStore) commit() error { if err != nil { return err } + toRemoveSompiSupply = toRemoveSompiSupply + utxoEntryToRemove.Amount() } } + toAddSompiSupply := uint64(0) + for scriptPublicKeyString, toAddUTXOOutpointEntryPairs := range uis.toAdd { scriptPublicKey := ConvertStringToScriptPublicKey(scriptPublicKeyString) bucket := uis.bucketForScriptPublicKey(scriptPublicKey) @@ -146,6 +155,7 @@ func (uis *utxoIndexStore) commit() error { if err != nil { return err } + toAddSompiSupply = toAddSompiSupply + utxoEntryToAdd.Amount() } } @@ -155,6 +165,11 @@ func (uis *utxoIndexStore) commit() error { return err } + err = uis.updateCirculatingSompiSupply(dbTransaction, toAddSompiSupply, toRemoveSompiSupply) + if err != nil { + return err + } + err = dbTransaction.Commit() if err != nil { return err @@ -165,20 +180,29 @@ func (uis *utxoIndexStore) commit() error { } func (uis *utxoIndexStore) addAndCommitOutpointsWithoutTransaction(utxoPairs []*externalapi.OutpointAndUTXOEntryPair) error { + toAddSompiSupply := uint64(0) for _, pair := range utxoPairs { bucket := uis.bucketForScriptPublicKey(pair.UTXOEntry.ScriptPublicKey()) key, err := uis.convertOutpointToKey(bucket, pair.Outpoint) if err != nil { return err } + serializedUTXOEntry, err := serializeUTXOEntry(pair.UTXOEntry) if err != nil { return err } + err = uis.database.Put(key, serializedUTXOEntry) if err != nil { return err } + toAddSompiSupply = toAddSompiSupply + pair.UTXOEntry.Amount() + } + + err := uis.updateCirculatingSompiSupplyWithoutTransaction(toAddSompiSupply, uint64(0)) + if err != nil { + return err } return nil @@ -211,7 +235,7 @@ func (uis *utxoIndexStore) convertKeyToOutpoint(key *database.Key) (*externalapi func (uis *utxoIndexStore) stagedData() ( toAdd map[ScriptPublicKeyString]UTXOOutpointEntryPairs, - toRemove map[ScriptPublicKeyString]UTXOOutpoints, + toRemove map[ScriptPublicKeyString]UTXOOutpointEntryPairs, virtualParents []*externalapi.DomainHash) { toAddClone := make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs, len(uis.toAdd)) @@ -223,13 +247,13 @@ func (uis *utxoIndexStore) stagedData() ( toAddClone[scriptPublicKeyString] = toAddUTXOOutpointEntryPairsClone } - toRemoveClone := make(map[ScriptPublicKeyString]UTXOOutpoints, len(uis.toRemove)) - for scriptPublicKeyString, toRemoveOutpoints := range uis.toRemove { - toRemoveOutpointsClone := make(UTXOOutpoints, len(toRemoveOutpoints)) - for outpoint := range toRemoveOutpoints { - toRemoveOutpointsClone[outpoint] = struct{}{} + toRemoveClone := make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs, len(uis.toRemove)) + for scriptPublicKeyString, toRemoveUTXOOutpointEntryPairs := range uis.toRemove { + toRemoveUTXOOutpointEntryPairsClone := make(UTXOOutpointEntryPairs, len(toRemoveUTXOOutpointEntryPairs)) + for outpoint, utxoEntry := range toRemoveUTXOOutpointEntryPairs { + toRemoveUTXOOutpointEntryPairsClone[outpoint] = utxoEntry } - toRemoveClone[scriptPublicKeyString] = toRemoveOutpointsClone + toRemoveClone[scriptPublicKeyString] = toRemoveUTXOOutpointEntryPairsClone } return toAddClone, toRemoveClone, uis.virtualParents @@ -294,6 +318,11 @@ func (uis *utxoIndexStore) deleteAll() error { return err } + err = uis.database.Delete(circulatingSupplyKey) + if err != nil { + return err + } + cursor, err := uis.database.Cursor(utxoIndexBucket) if err != nil { return err @@ -313,3 +342,92 @@ func (uis *utxoIndexStore) deleteAll() error { return nil } + +func (uis *utxoIndexStore) initializeCirculatingSompiSupply() error { + + cursor, err := uis.database.Cursor(utxoIndexBucket) + if err != nil { + return err + } + defer cursor.Close() + + circulatingSompiSupplyInDatabase := uint64(0) + for cursor.Next() { + serializedUTXOEntry, err := cursor.Value() + if err != nil { + return err + } + utxoEntry, err := deserializeUTXOEntry(serializedUTXOEntry) + if err != nil { + return err + } + + circulatingSompiSupplyInDatabase = circulatingSompiSupplyInDatabase + utxoEntry.Amount() + } + + err = uis.database.Put( + circulatingSupplyKey, + binaryserialization.SerializeUint64(circulatingSompiSupplyInDatabase), + ) + + if err != nil { + return err + } + + return nil +} + +func (uis *utxoIndexStore) updateCirculatingSompiSupply(dbTransaction database.Transaction, toAddSompiSupply uint64, toRemoveSompiSupply uint64) error { + if toAddSompiSupply != toRemoveSompiSupply { + circulatingSupplyBytes, err := dbTransaction.Get(circulatingSupplyKey) + if err != nil { + return err + } + + circulatingSupply, err := binaryserialization.DeserializeUint64(circulatingSupplyBytes) + if err != nil { + return err + } + err = dbTransaction.Put( + circulatingSupplyKey, + binaryserialization.SerializeUint64(circulatingSupply+toAddSompiSupply-toRemoveSompiSupply), + ) + if err != nil { + return err + } + } + return nil +} + +func (uis *utxoIndexStore) updateCirculatingSompiSupplyWithoutTransaction(toAddSompiSupply uint64, toRemoveSompiSupply uint64) error { + if toAddSompiSupply != toRemoveSompiSupply { + circulatingSupplyBytes, err := uis.database.Get(circulatingSupplyKey) + if err != nil { + return err + } + + circulatingSupply, err := binaryserialization.DeserializeUint64(circulatingSupplyBytes) + if err != nil { + return err + } + err = uis.database.Put( + circulatingSupplyKey, + binaryserialization.SerializeUint64(circulatingSupply+toAddSompiSupply-toRemoveSompiSupply), + ) + if err != nil { + return err + } + } + return nil +} + +func (uis *utxoIndexStore) getCirculatingSompiSupply() (uint64, error) { + if uis.isAnythingStaged() { + return 0, errors.Errorf("cannot get circulatingSupply while staging isn't empty") + } + circulatingSupply, err := uis.database.Get(circulatingSupplyKey) + if err != nil { + return 0, err + } + return binaryserialization.DeserializeUint64(circulatingSupply) +} diff --git a/domain/utxoindex/utxoindex.go b/domain/utxoindex/utxoindex.go index 8646a4f5b..a5c4353ad 100644 --- a/domain/utxoindex/utxoindex.go +++ b/domain/utxoindex/utxoindex.go @@ -25,14 +25,20 @@ func New(domain domain.Domain, database database.Database) (*UTXOIndex, error) { domain: domain, store: newUTXOIndexStore(database), } - isSynced, err := utxoIndex.isSynced() if err != nil { return nil, err } - if !isSynced { - err = utxoIndex.Reset() + ///Has check is for migration to circulating supply, can be removed eventually. + hasCirculatingSupplyKey, err := utxoIndex.store.database.Has(circulatingSupplyKey) + if err != nil { + return nil, err + } + + if !isSynced || !hasCirculatingSupplyKey { + + err := utxoIndex.Reset() if err != nil { return nil, err } @@ -56,6 +62,11 @@ func (ui *UTXOIndex) Reset() error { return err } + err = ui.store.initializeCirculatingSompiSupply() //At this point the database is empty, so the sole purpose of this call is to initialize the circulating supply key + if err != nil { + return err + } + var fromOutpoint *externalapi.DomainOutpoint for { const step = 1000 @@ -161,7 +172,7 @@ func (ui *UTXOIndex) removeUTXOs(toRemove externalapi.UTXOCollection) error { } log.Tracef("Removing outpoint %s from UTXO index", outpoint) - err = ui.store.remove(entry.ScriptPublicKey(), outpoint) + err = ui.store.remove(entry.ScriptPublicKey(), outpoint, entry) if err != nil { return err } @@ -179,3 +190,12 @@ func (ui *UTXOIndex) UTXOs(scriptPublicKey *externalapi.ScriptPublicKey) (UTXOOu return ui.store.getUTXOOutpointEntryPairs(scriptPublicKey) } + +// GetCirculatingSompiSupply returns the current circulating supply of sompis in the network +func (ui *UTXOIndex) GetCirculatingSompiSupply() (uint64, error) { + + ui.mutex.Lock() + defer ui.mutex.Unlock() + + return ui.store.getCirculatingSompiSupply() +} diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/messages.pb.go b/infrastructure/network/netadapter/server/grpcserver/protowire/messages.pb.go index 0f65f432d..c273e10d7 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/messages.pb.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/messages.pb.go @@ -154,6 +154,8 @@ type KaspadMessage struct { // *KaspadMessage_NewBlockTemplateNotification // *KaspadMessage_GetMempoolEntriesByAddressesRequest // *KaspadMessage_GetMempoolEntriesByAddressesResponse + // *KaspadMessage_GetCoinSupplyRequest + // *KaspadMessage_GetCoinSupplyResponse Payload isKaspadMessage_Payload `protobuf_oneof:"payload"` } @@ -1092,6 +1094,20 @@ func (x *KaspadMessage) GetGetMempoolEntriesByAddressesResponse() *GetMempoolEnt return nil } +func (x *KaspadMessage) GetGetCoinSupplyRequest() *GetCoinSupplyRequestMessage { + if x, ok := x.GetPayload().(*KaspadMessage_GetCoinSupplyRequest); ok { + return x.GetCoinSupplyRequest + } + return nil +} + +func (x *KaspadMessage) GetGetCoinSupplyResponse() *GetCoinSupplyResponseMessage { + if x, ok := x.GetPayload().(*KaspadMessage_GetCoinSupplyResponse); ok { + return x.GetCoinSupplyResponse + } + return nil +} + type isKaspadMessage_Payload interface { isKaspadMessage_Payload() } @@ -1608,6 +1624,14 @@ type KaspadMessage_GetMempoolEntriesByAddressesResponse struct { GetMempoolEntriesByAddressesResponse *GetMempoolEntriesByAddressesResponseMessage `protobuf:"bytes,1085,opt,name=getMempoolEntriesByAddressesResponse,proto3,oneof"` } +type KaspadMessage_GetCoinSupplyRequest struct { + GetCoinSupplyRequest *GetCoinSupplyRequestMessage `protobuf:"bytes,1086,opt,name=getCoinSupplyRequest,proto3,oneof"` +} + +type KaspadMessage_GetCoinSupplyResponse struct { + GetCoinSupplyResponse *GetCoinSupplyResponseMessage `protobuf:"bytes,1087,opt,name=getCoinSupplyResponse,proto3,oneof"` +} + func (*KaspadMessage_Addresses) isKaspadMessage_Payload() {} func (*KaspadMessage_Block) isKaspadMessage_Payload() {} @@ -1864,13 +1888,17 @@ func (*KaspadMessage_GetMempoolEntriesByAddressesRequest) isKaspadMessage_Payloa func (*KaspadMessage_GetMempoolEntriesByAddressesResponse) isKaspadMessage_Payload() {} +func (*KaspadMessage_GetCoinSupplyRequest) isKaspadMessage_Payload() {} + +func (*KaspadMessage_GetCoinSupplyResponse) isKaspadMessage_Payload() {} + var File_messages_proto protoreflect.FileDescriptor var file_messages_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x1a, 0x09, 0x70, 0x32, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x09, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0xfe, 0x6b, 0x0a, 0x0d, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, 0x65, 0x73, 0x73, + 0x6f, 0x22, 0xbf, 0x6d, 0x0a, 0x0d, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x4d, 0x65, 0x73, 0x73, @@ -2733,21 +2761,33 @@ var file_messages_proto_rawDesc = []byte{ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x24, 0x67, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x70, 0x6f, 0x6f, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, - 0x61, 0x64, 0x32, 0x50, 0x0a, 0x03, 0x50, 0x32, 0x50, 0x12, 0x49, 0x0a, 0x0d, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, - 0x2e, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, - 0x28, 0x01, 0x30, 0x01, 0x32, 0x50, 0x0a, 0x03, 0x52, 0x50, 0x43, 0x12, 0x49, 0x0a, 0x0d, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, - 0x72, 0x65, 0x2e, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, - 0x73, 0x70, 0x61, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x14, 0x67, 0x65, 0x74, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0xbe, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, + 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, + 0x52, 0x14, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x60, 0x0a, 0x15, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x69, + 0x6e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, + 0xbf, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, + 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, + 0x00, 0x52, 0x15, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, + 0x6f, 0x61, 0x64, 0x32, 0x50, 0x0a, 0x03, 0x50, 0x32, 0x50, 0x12, 0x49, 0x0a, 0x0d, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, + 0x65, 0x2e, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x00, 0x28, 0x01, 0x30, 0x01, 0x32, 0x50, 0x0a, 0x03, 0x52, 0x50, 0x43, 0x12, 0x49, 0x0a, 0x0d, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, + 0x69, 0x72, 0x65, 0x2e, 0x4b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, + 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2892,6 +2932,8 @@ var file_messages_proto_goTypes = []interface{}{ (*NewBlockTemplateNotificationMessage)(nil), // 125: protowire.NewBlockTemplateNotificationMessage (*GetMempoolEntriesByAddressesRequestMessage)(nil), // 126: protowire.GetMempoolEntriesByAddressesRequestMessage (*GetMempoolEntriesByAddressesResponseMessage)(nil), // 127: protowire.GetMempoolEntriesByAddressesResponseMessage + (*GetCoinSupplyRequestMessage)(nil), // 128: protowire.GetCoinSupplyRequestMessage + (*GetCoinSupplyResponseMessage)(nil), // 129: protowire.GetCoinSupplyResponseMessage } var file_messages_proto_depIdxs = []int32{ 1, // 0: protowire.KaspadMessage.addresses:type_name -> protowire.AddressesMessage @@ -3022,15 +3064,17 @@ var file_messages_proto_depIdxs = []int32{ 125, // 125: protowire.KaspadMessage.newBlockTemplateNotification:type_name -> protowire.NewBlockTemplateNotificationMessage 126, // 126: protowire.KaspadMessage.getMempoolEntriesByAddressesRequest:type_name -> protowire.GetMempoolEntriesByAddressesRequestMessage 127, // 127: protowire.KaspadMessage.getMempoolEntriesByAddressesResponse:type_name -> protowire.GetMempoolEntriesByAddressesResponseMessage - 0, // 128: protowire.P2P.MessageStream:input_type -> protowire.KaspadMessage - 0, // 129: protowire.RPC.MessageStream:input_type -> protowire.KaspadMessage - 0, // 130: protowire.P2P.MessageStream:output_type -> protowire.KaspadMessage - 0, // 131: protowire.RPC.MessageStream:output_type -> protowire.KaspadMessage - 130, // [130:132] is the sub-list for method output_type - 128, // [128:130] is the sub-list for method input_type - 128, // [128:128] is the sub-list for extension type_name - 128, // [128:128] is the sub-list for extension extendee - 0, // [0:128] is the sub-list for field type_name + 128, // 128: protowire.KaspadMessage.getCoinSupplyRequest:type_name -> protowire.GetCoinSupplyRequestMessage + 129, // 129: protowire.KaspadMessage.getCoinSupplyResponse:type_name -> protowire.GetCoinSupplyResponseMessage + 0, // 130: protowire.P2P.MessageStream:input_type -> protowire.KaspadMessage + 0, // 131: protowire.RPC.MessageStream:input_type -> protowire.KaspadMessage + 0, // 132: protowire.P2P.MessageStream:output_type -> protowire.KaspadMessage + 0, // 133: protowire.RPC.MessageStream:output_type -> protowire.KaspadMessage + 132, // [132:134] is the sub-list for method output_type + 130, // [130:132] is the sub-list for method input_type + 130, // [130:130] is the sub-list for extension type_name + 130, // [130:130] is the sub-list for extension extendee + 0, // [0:130] is the sub-list for field type_name } func init() { file_messages_proto_init() } @@ -3183,6 +3227,8 @@ func file_messages_proto_init() { (*KaspadMessage_NewBlockTemplateNotification)(nil), (*KaspadMessage_GetMempoolEntriesByAddressesRequest)(nil), (*KaspadMessage_GetMempoolEntriesByAddressesResponse)(nil), + (*KaspadMessage_GetCoinSupplyRequest)(nil), + (*KaspadMessage_GetCoinSupplyResponse)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/messages.proto b/infrastructure/network/netadapter/server/grpcserver/protowire/messages.proto index 63e06c295..50be89138 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/messages.proto +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/messages.proto @@ -137,6 +137,8 @@ message KaspadMessage { NewBlockTemplateNotificationMessage newBlockTemplateNotification = 1083; GetMempoolEntriesByAddressesRequestMessage getMempoolEntriesByAddressesRequest = 1084; GetMempoolEntriesByAddressesResponseMessage getMempoolEntriesByAddressesResponse = 1085; + GetCoinSupplyRequestMessage getCoinSupplyRequest = 1086; + GetCoinSupplyResponseMessage getCoinSupplyResponse= 1087; } } diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.md b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.md index 1749a4037..7bce86dee 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.md +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.md @@ -110,6 +110,8 @@ - [MempoolEntryByAddress](#protowire.MempoolEntryByAddress) - [GetMempoolEntriesByAddressesRequestMessage](#protowire.GetMempoolEntriesByAddressesRequestMessage) - [GetMempoolEntriesByAddressesResponseMessage](#protowire.GetMempoolEntriesByAddressesResponseMessage) + - [GetCoinSupplyRequestMessage](#protowire.GetCoinSupplyRequestMessage) + - [GetCoinSupplyResponseMessage](#protowire.GetCoinSupplyResponseMessage) - [SubmitBlockResponseMessage.RejectReason](#protowire.SubmitBlockResponseMessage.RejectReason) @@ -1817,6 +1819,33 @@ See NotifyNewBlockTemplateRequestMessage + + + +### GetCoinSupplyRequestMessage + + + + + + + + + +### GetCoinSupplyResponseMessage + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| maxSompi | [uint64](#uint64) | | note: this is a hard coded maxSupply, actual maxSupply is expected to deviate by upto -5%, but cannot be measured exactly. | +| circulatingSompi | [uint64](#uint64) | | | +| error | [RPCError](#protowire.RPCError) | | | + + + + + diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go index b5788d1d9..e8f37359d 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go @@ -5953,6 +5953,107 @@ func (x *GetMempoolEntriesByAddressesResponseMessage) GetError() *RPCError { return nil } +type GetCoinSupplyRequestMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetCoinSupplyRequestMessage) Reset() { + *x = GetCoinSupplyRequestMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_proto_msgTypes[106] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCoinSupplyRequestMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCoinSupplyRequestMessage) ProtoMessage() {} + +func (x *GetCoinSupplyRequestMessage) ProtoReflect() protoreflect.Message { + mi := &file_rpc_proto_msgTypes[106] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCoinSupplyRequestMessage.ProtoReflect.Descriptor instead. +func (*GetCoinSupplyRequestMessage) Descriptor() ([]byte, []int) { + return file_rpc_proto_rawDescGZIP(), []int{106} +} + +type GetCoinSupplyResponseMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MaxSompi uint64 `protobuf:"varint,1,opt,name=maxSompi,proto3" json:"maxSompi,omitempty"` // note: this is a hard coded maxSupply, actual maxSupply is expected to deviate by upto -5%, but cannot be measured exactly. + CirculatingSompi uint64 `protobuf:"varint,2,opt,name=circulatingSompi,proto3" json:"circulatingSompi,omitempty"` + Error *RPCError `protobuf:"bytes,1000,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *GetCoinSupplyResponseMessage) Reset() { + *x = GetCoinSupplyResponseMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCoinSupplyResponseMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCoinSupplyResponseMessage) ProtoMessage() {} + +func (x *GetCoinSupplyResponseMessage) ProtoReflect() protoreflect.Message { + mi := &file_rpc_proto_msgTypes[107] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCoinSupplyResponseMessage.ProtoReflect.Descriptor instead. +func (*GetCoinSupplyResponseMessage) Descriptor() ([]byte, []int) { + return file_rpc_proto_rawDescGZIP(), []int{107} +} + +func (x *GetCoinSupplyResponseMessage) GetMaxSompi() uint64 { + if x != nil { + return x.MaxSompi + } + return 0 +} + +func (x *GetCoinSupplyResponseMessage) GetCirculatingSompi() uint64 { + if x != nil { + return x.CirculatingSompi + } + return 0 +} + +func (x *GetCoinSupplyResponseMessage) GetError() *RPCError { + if x != nil { + return x.Error + } + return nil +} + var File_rpc_proto protoreflect.FileDescriptor var file_rpc_proto_rawDesc = []byte{ @@ -6724,10 +6825,21 @@ var file_rpc_proto_rawDesc = []byte{ 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, - 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, - 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x1d, 0x0a, 0x1b, 0x47, 0x65, + 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x1c, 0x47, 0x65, + 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, + 0x78, 0x53, 0x6f, 0x6d, 0x70, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, + 0x78, 0x53, 0x6f, 0x6d, 0x70, 0x69, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x69, 0x72, 0x63, 0x75, 0x6c, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x6f, 0x6d, 0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x10, 0x63, 0x69, 0x72, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x6f, 0x6d, + 0x70, 0x69, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, + 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x26, + 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, + 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -6743,7 +6855,7 @@ func file_rpc_proto_rawDescGZIP() []byte { } var file_rpc_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 106) +var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 108) var file_rpc_proto_goTypes = []interface{}{ (SubmitBlockResponseMessage_RejectReason)(0), // 0: protowire.SubmitBlockResponseMessage.RejectReason (*RPCError)(nil), // 1: protowire.RPCError @@ -6852,6 +6964,8 @@ var file_rpc_proto_goTypes = []interface{}{ (*MempoolEntryByAddress)(nil), // 104: protowire.MempoolEntryByAddress (*GetMempoolEntriesByAddressesRequestMessage)(nil), // 105: protowire.GetMempoolEntriesByAddressesRequestMessage (*GetMempoolEntriesByAddressesResponseMessage)(nil), // 106: protowire.GetMempoolEntriesByAddressesResponseMessage + (*GetCoinSupplyRequestMessage)(nil), // 107: protowire.GetCoinSupplyRequestMessage + (*GetCoinSupplyResponseMessage)(nil), // 108: protowire.GetCoinSupplyResponseMessage } var file_rpc_proto_depIdxs = []int32{ 3, // 0: protowire.RpcBlock.header:type_name -> protowire.RpcBlockHeader @@ -6929,11 +7043,12 @@ var file_rpc_proto_depIdxs = []int32{ 33, // 72: protowire.MempoolEntryByAddress.receiving:type_name -> protowire.MempoolEntry 104, // 73: protowire.GetMempoolEntriesByAddressesResponseMessage.entries:type_name -> protowire.MempoolEntryByAddress 1, // 74: protowire.GetMempoolEntriesByAddressesResponseMessage.error:type_name -> protowire.RPCError - 75, // [75:75] is the sub-list for method output_type - 75, // [75:75] is the sub-list for method input_type - 75, // [75:75] is the sub-list for extension type_name - 75, // [75:75] is the sub-list for extension extendee - 0, // [0:75] is the sub-list for field type_name + 1, // 75: protowire.GetCoinSupplyResponseMessage.error:type_name -> protowire.RPCError + 76, // [76:76] is the sub-list for method output_type + 76, // [76:76] is the sub-list for method input_type + 76, // [76:76] is the sub-list for extension type_name + 76, // [76:76] is the sub-list for extension extendee + 0, // [0:76] is the sub-list for field type_name } func init() { file_rpc_proto_init() } @@ -8214,6 +8329,30 @@ func file_rpc_proto_init() { return nil } } + file_rpc_proto_msgTypes[106].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCoinSupplyRequestMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_proto_msgTypes[107].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCoinSupplyResponseMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -8221,7 +8360,7 @@ func file_rpc_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_rpc_proto_rawDesc, NumEnums: 1, - NumMessages: 106, + NumMessages: 108, NumExtensions: 0, NumServices: 0, }, diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto index ca1e3662e..c80826744 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto @@ -705,3 +705,12 @@ message GetMempoolEntriesByAddressesResponseMessage{ RPCError error = 1000; } +message GetCoinSupplyRequestMessage{ +} + +message GetCoinSupplyResponseMessage{ + uint64 maxSompi = 1; // note: this is a hard coded maxSupply, actual maxSupply is expected to deviate by upto -5%, but cannot be measured exactly. + uint64 circulatingSompi = 2; + + RPCError error = 1000; +} diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_get_supply.go b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_get_supply.go new file mode 100644 index 000000000..50b4127fa --- /dev/null +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_get_supply.go @@ -0,0 +1,54 @@ +package protowire + +import ( + "github.com/kaspanet/kaspad/app/appmessage" + "github.com/pkg/errors" +) + +func (x *KaspadMessage_GetCoinSupplyRequest) toAppMessage() (appmessage.Message, error) { + return &appmessage.GetCoinSupplyRequestMessage{}, nil +} + +func (x *KaspadMessage_GetCoinSupplyRequest) fromAppMessage(_ *appmessage.GetCoinSupplyRequestMessage) error { + x.GetCoinSupplyRequest = &GetCoinSupplyRequestMessage{} + return nil +} + +func (x *KaspadMessage_GetCoinSupplyResponse) toAppMessage() (appmessage.Message, error) { + if x == nil { + return nil, errors.Wrapf(errorNil, "KaspadMessage_GetCoinSupplyResponse is nil") + } + return x.GetCoinSupplyResponse.toAppMessage() +} + +func (x *KaspadMessage_GetCoinSupplyResponse) fromAppMessage(message *appmessage.GetCoinSupplyResponseMessage) error { + var err *RPCError + if message.Error != nil { + err = &RPCError{Message: message.Error.Message} + } + x.GetCoinSupplyResponse = &GetCoinSupplyResponseMessage{ + MaxSompi: message.MaxSompi, + CirculatingSompi: message.CirculatingSompi, + + Error: err, + } + return nil +} + +func (x *GetCoinSupplyResponseMessage) toAppMessage() (appmessage.Message, error) { + if x == nil { + return nil, errors.Wrapf(errorNil, "GetCoinSupplyResponseMessage is nil") + } + rpcErr, err := x.Error.toAppMessage() + // Error is an optional field + if err != nil && !errors.Is(err, errorNil) { + return nil, err + } + + return &appmessage.GetCoinSupplyResponseMessage{ + MaxSompi: x.MaxSompi, + CirculatingSompi: x.CirculatingSompi, + + Error: rpcErr, + }, nil +} diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/wire.go b/infrastructure/network/netadapter/server/grpcserver/protowire/wire.go index 7794dd378..30dd18b6e 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/wire.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/wire.go @@ -954,6 +954,20 @@ func toRPCPayload(message appmessage.Message) (isKaspadMessage_Payload, error) { return nil, err } return payload, nil + case *appmessage.GetCoinSupplyRequestMessage: + payload := new(KaspadMessage_GetCoinSupplyRequest) + err := payload.fromAppMessage(message) + if err != nil { + return nil, err + } + return payload, nil + case *appmessage.GetCoinSupplyResponseMessage: + payload := new(KaspadMessage_GetCoinSupplyResponse) + err := payload.fromAppMessage(message) + if err != nil { + return nil, err + } + return payload, nil default: return nil, nil } diff --git a/infrastructure/network/rpcclient/rpc_get_coin_supply.go b/infrastructure/network/rpcclient/rpc_get_coin_supply.go new file mode 100644 index 000000000..92b8f8c47 --- /dev/null +++ b/infrastructure/network/rpcclient/rpc_get_coin_supply.go @@ -0,0 +1,20 @@ +package rpcclient + +import "github.com/kaspanet/kaspad/app/appmessage" + +// GetCoinSupply sends an RPC request respective to the function's name and returns the RPC server's response +func (c *RPCClient) GetCoinSupply() (*appmessage.GetCoinSupplyResponseMessage, error) { + err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewGetCoinSupplyRequestMessage()) + if err != nil { + return nil, err + } + response, err := c.route(appmessage.CmdGetCoinSupplyResponseMessage).DequeueWithTimeout(c.timeout) + if err != nil { + return nil, err + } + geCoinSupplyResponse := response.(*appmessage.GetCoinSupplyResponseMessage) + if geCoinSupplyResponse.Error != nil { + return nil, c.convertRPCError(geCoinSupplyResponse.Error) + } + return geCoinSupplyResponse, nil +} diff --git a/testing/integration/utxo_index_test.go b/testing/integration/utxo_index_test.go index 1a313465e..772c96646 100644 --- a/testing/integration/utxo_index_test.go +++ b/testing/integration/utxo_index_test.go @@ -49,6 +49,27 @@ func TestUTXOIndex(t *testing.T) { mineNextBlock(t, kaspad) } + //check if rewards corrosponds to circulating supply. + getCoinSupplyResponse, err := kaspad.rpcClient.GetCoinSupply() + if err != nil { + t.Fatalf("Error Retriving Coin supply: %s", err) + } + + rewardsMinedSompi := uint64(blockAmountToMine * constants.SompiPerKaspa * 500) + getBlockCountResponse, err := kaspad.rpcClient.GetBlockCount() + if err != nil { + t.Fatalf("Error Retriving BlockCount: %s", err) + } + rewardsMinedViaBlockCountSompi := uint64( + (getBlockCountResponse.BlockCount - 2) * constants.SompiPerKaspa * 500, // -2 because of genesis and virtual. + ) + + if getCoinSupplyResponse.CirculatingSompi != rewardsMinedSompi { + t.Fatalf("Error: Circulating supply Mismatch - Circulating Sompi: %d Sompi Mined: %d", getCoinSupplyResponse.CirculatingSompi, rewardsMinedSompi) + } else if getCoinSupplyResponse.CirculatingSompi != rewardsMinedViaBlockCountSompi { + t.Fatalf("Error: Circulating supply Mismatch - Circulating Sompi: %d Sompi Mined via Block count: %d", getCoinSupplyResponse.CirculatingSompi, rewardsMinedViaBlockCountSompi) + } + // Collect the UTXO and make sure there's nothing in Removed // Note that we expect blockAmountToMine-1 messages because // the last block won't be accepted until the next block is