Compare commits

..

2 Commits

Author SHA1 Message Date
msutton
6316c3c2a8 Fix daa score to chain block 2022-07-19 12:14:21 +03:00
msutton
877c46bfec Calc new pruning point utxo diff through chain acceptance data 2022-07-19 11:38:51 +03:00
164 changed files with 1206 additions and 2928 deletions

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.21
go-version: 1.18
- name: Build on Linux
if: runner.os == 'Linux'

View File

@@ -22,7 +22,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.18
- name: Set scheduled branch name
shell: bash

View File

@@ -33,7 +33,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.18
# Source: https://github.com/actions/cache/blob/main/examples.md#go---modules
@@ -58,7 +58,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.18
- name: Checkout
uses: actions/checkout@v2
@@ -86,7 +86,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.18
- name: Delete the stability tests from coverage
run: rm -r stability-tests

View File

@@ -1,43 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainers on this [Google form][gform]. The project maintainers will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[gform]: https://forms.gle/dnKXMJL7VxdUjt3x5
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -7,6 +7,8 @@ Kaspad
Kaspad is the reference full node Kaspa implementation written in Go (golang).
This project is currently under active development and is in Beta state.
## What is kaspa
Kaspa is an attempt at a proof-of-work cryptocurrency with instant confirmations and sub-second block times. It is based on [the PHANTOM protocol](https://eprint.iacr.org/2018/104.pdf), a generalization of Nakamoto consensus.

View File

@@ -6,7 +6,7 @@ supported kaspa messages to and from the appmessage. This package does not deal
with the specifics of message handling such as what to do when a message is
received. This provides the caller with a high level of flexibility.
# Kaspa Message Overview
Kaspa Message Overview
The kaspa protocol consists of exchanging messages between peers. Each
message is preceded by a header which identifies information about it such as
@@ -22,7 +22,7 @@ messages, all of the details of marshalling and unmarshalling to and from the
appmessage using kaspa encoding are handled so the caller doesn't have to concern
themselves with the specifics.
# Message Interaction
Message Interaction
The following provides a quick summary of how the kaspa messages are intended
to interact with one another. As stated above, these interactions are not
@@ -45,13 +45,13 @@ interactions in no particular order.
notfound message (MsgNotFound)
ping message (MsgPing) pong message (MsgPong)
# Common Parameters
Common Parameters
There are several common parameters that arise when using this package to read
and write kaspa messages. The following sections provide a quick overview of
these parameters so the next sections can build on them.
# Protocol Version
Protocol Version
The protocol version should be negotiated with the remote peer at a higher
level than this package via the version (MsgVersion) message exchange, however,
@@ -60,18 +60,18 @@ latest protocol version this package supports and is typically the value to use
for all outbound connections before a potentially lower protocol version is
negotiated.
# Kaspa Network
Kaspa Network
The kaspa network is a magic number which is used to identify the start of a
message and which kaspa network the message applies to. This package provides
the following constants:
appmessage.Mainnet
appmessage.Testnet (Test network)
appmessage.Simnet (Simulation test network)
appmessage.Devnet (Development network)
appmessage.Mainnet
appmessage.Testnet (Test network)
appmessage.Simnet (Simulation test network)
appmessage.Devnet (Development network)
# Determining Message Type
Determining Message Type
As discussed in the kaspa message overview section, this package reads
and writes kaspa messages using a generic interface named Message. In
@@ -89,7 +89,7 @@ switch or type assertion. An example of a type switch follows:
fmt.Printf("Number of tx in block: %d", msg.Header.TxnCount)
}
# Reading Messages
Reading Messages
In order to unmarshall kaspa messages from the appmessage, use the ReadMessage
function. It accepts any io.Reader, but typically this will be a net.Conn to
@@ -104,7 +104,7 @@ a remote node running a kaspa peer. Example syntax is:
// Log and handle the error
}
# Writing Messages
Writing Messages
In order to marshall kaspa messages to the appmessage, use the WriteMessage
function. It accepts any io.Writer, but typically this will be a net.Conn to
@@ -122,7 +122,7 @@ from a remote peer is:
// Log and handle the error
}
# Errors
Errors
Errors returned by this package are either the raw errors provided by underlying
calls to read/write from streams such as io.EOF, io.ErrUnexpectedEOF, and

View File

@@ -218,7 +218,7 @@ func RPCTransactionToDomainTransaction(rpcTransaction *RPCTransaction) (*externa
Outputs: outputs,
LockTime: rpcTransaction.LockTime,
SubnetworkID: *subnetworkID,
Gas: rpcTransaction.Gas,
Gas: rpcTransaction.LockTime,
Payload: payload,
}, nil
}
@@ -286,7 +286,7 @@ func DomainTransactionToRPCTransaction(transaction *externalapi.DomainTransactio
Outputs: outputs,
LockTime: transaction.LockTime,
SubnetworkID: subnetworkID,
Gas: transaction.Gas,
Gas: transaction.LockTime,
Payload: payload,
}
}

View File

@@ -132,7 +132,7 @@ func TestConvertToPartial(t *testing.T) {
}
}
// blockOne is the first block in the mainnet block DAG.
//blockOne is the first block in the mainnet block DAG.
var blockOne = MsgBlock{
Header: MsgBlockHeader{
Version: 0,

View File

@@ -133,8 +133,8 @@ func TestTx(t *testing.T) {
// TestTxHash tests the ability to generate the hash of a transaction accurately.
func TestTxHashAndID(t *testing.T) {
txHash1Str := "b06f8b650115b5cf4d59499e10764a9312742930cb43c9b4ff6495d76f332ed7"
txID1Str := "e20225c3d065ee41743607ee627db44d01ef396dc9779b05b2caf55bac50e12d"
txHash1Str := "93663e597f6c968d32d229002f76408edf30d6a0151ff679fc729812d8cb2acc"
txID1Str := "24079c6d2bdf602fc389cc307349054937744a9c8dc0f07c023e6af0e949a4e7"
wantTxID1, err := transactionid.FromString(txID1Str)
if err != nil {
t.Fatalf("NewTxIDFromStr: %v", err)
@@ -185,7 +185,7 @@ func TestTxHashAndID(t *testing.T) {
spew.Sprint(tx1ID), spew.Sprint(wantTxID1))
}
hash2Str := "fa16a8ce88d52ca1ff45187bbba0d33044e9f5fe309e8d0b22d4812dcf1782b7"
hash2Str := "8dafd1bec24527d8e3b443ceb0a3b92fffc0d60026317f890b2faf5e9afc177a"
wantHash2, err := externalapi.NewDomainHashFromString(hash2Str)
if err != nil {
t.Errorf("NewTxIDFromStr: %v", err)

View File

@@ -27,15 +27,18 @@ func HandleIBDBlockRequests(context HandleIBDBlockRequestsContext, incomingRoute
log.Debugf("Got request for %d ibd blocks", len(msgRequestIBDBlocks.Hashes))
for i, hash := range msgRequestIBDBlocks.Hashes {
// Fetch the block from the database.
block, found, err := context.Domain().Consensus().GetBlock(hash)
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
if err != nil {
return err
}
if !blockInfo.HasBody() {
return protocolerrors.Errorf(true, "block %s not found (v5)", hash)
}
block, err := context.Domain().Consensus().GetBlock(hash)
if err != nil {
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
}
if !found {
return protocolerrors.Errorf(false, "IBD block %s not found", hash)
}
// TODO (Partial nodes): Convert block to partial block if needed
blockMessage := appmessage.DomainBlockToMsgBlock(block)

View File

@@ -119,15 +119,11 @@ func HandlePruningPointAndItsAnticoneRequests(context PruningPointAndItsAnticone
}
for i, blockHash := range pointAndItsAnticone {
block, found, err := context.Domain().Consensus().GetBlock(blockHash)
block, err := context.Domain().Consensus().GetBlock(blockHash)
if err != nil {
return err
}
if !found {
return protocolerrors.Errorf(false, "pruning point anticone block %s not found", blockHash)
}
err = outgoingRoute.Enqueue(appmessage.DomainBlockWithTrustedDataToBlockWithTrustedDataV4(block, trustedDataDAABlockIndexes[*blockHash], trustedDataGHOSTDAGDataIndexes[*blockHash]))
if err != nil {
return err

View File

@@ -28,15 +28,18 @@ func HandleRelayBlockRequests(context RelayBlockRequestsContext, incomingRoute *
log.Debugf("Got request for relay blocks with hashes %s", getRelayBlocksMessage.Hashes)
for _, hash := range getRelayBlocksMessage.Hashes {
// Fetch the block from the database.
block, found, err := context.Domain().Consensus().GetBlock(hash)
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
if err != nil {
return err
}
if !blockInfo.HasBody() {
return protocolerrors.Errorf(true, "block %s not found", hash)
}
block, err := context.Domain().Consensus().GetBlock(hash)
if err != nil {
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
}
if !found {
return protocolerrors.Errorf(false, "Relay block %s not found", hash)
}
// TODO (Partial nodes): Convert block to partial block if needed
err = outgoingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(block))

View File

@@ -211,14 +211,10 @@ func (flow *handleRelayInvsFlow) start() error {
continue
}
virtualHasNewParents = true
block, found, err := flow.Domain().Consensus().GetBlock(parent)
block, err := flow.Domain().Consensus().GetBlock(parent)
if err != nil {
return err
}
if !found {
return protocolerrors.Errorf(false, "Virtual parent %s not found", parent)
}
blockHash := consensushashing.BlockHash(block)
log.Debugf("Relaying block %s", blockHash)
err = flow.relayBlock(block)

View File

@@ -1,7 +1,6 @@
package blockrelay
import (
"fmt"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/protocol/common"
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
@@ -71,17 +70,16 @@ func (flow *handleIBDFlow) runIBDIfNotRunning(block *externalapi.DomainBlock) er
}
isFinishedSuccessfully := false
var err error
defer func() {
flow.UnsetIBDRunning()
flow.logIBDFinished(isFinishedSuccessfully, err)
flow.logIBDFinished(isFinishedSuccessfully)
}()
relayBlockHash := consensushashing.BlockHash(block)
log.Infof("IBD started with peer %s and relayBlockHash %s", flow.peer, relayBlockHash)
log.Infof("Syncing blocks up to %s", relayBlockHash)
log.Infof("Trying to find highest known syncer chain block from peer %s with relay hash %s", flow.peer, relayBlockHash)
log.Debugf("IBD started with peer %s and relayBlockHash %s", flow.peer, relayBlockHash)
log.Debugf("Syncing blocks up to %s", relayBlockHash)
log.Debugf("Trying to find highest known syncer chain block from peer %s with relay hash %s", flow.peer, relayBlockHash)
syncerHeaderSelectedTipHash, highestKnownSyncerChainHash, err := flow.negotiateMissingSyncerChainSegment()
if err != nil {
@@ -100,7 +98,7 @@ func (flow *handleIBDFlow) runIBDIfNotRunning(block *externalapi.DomainBlock) er
if shouldDownloadHeadersProof {
log.Infof("Starting IBD with headers proof")
err = flow.ibdWithHeadersProof(syncerHeaderSelectedTipHash, relayBlockHash, block.Header.DAAScore())
err := flow.ibdWithHeadersProof(syncerHeaderSelectedTipHash, relayBlockHash, block.Header.DAAScore())
if err != nil {
return err
}
@@ -175,11 +173,6 @@ func (flow *handleIBDFlow) negotiateMissingSyncerChainSegment() (*externalapi.Do
chainNegotiationRestartCounter := 0
chainNegotiationZoomCounts := 0
initialLocatorLen := len(locatorHashes)
pruningPoint, err := flow.Domain().Consensus().PruningPoint()
if err != nil {
return nil, nil, err
}
for {
var lowestUnknownSyncerChainHash, currentHighestKnownSyncerChainHash *externalapi.DomainHash
for _, syncerChainHash := range locatorHashes {
@@ -188,25 +181,8 @@ func (flow *handleIBDFlow) negotiateMissingSyncerChainSegment() (*externalapi.Do
return nil, nil, err
}
if info.Exists {
if info.BlockStatus == externalapi.StatusInvalid {
return nil, nil, protocolerrors.Errorf(true, "Sent invalid chain block %s", syncerChainHash)
}
isPruningPointOnSyncerChain, err := flow.Domain().Consensus().IsInSelectedParentChainOf(pruningPoint, syncerChainHash)
if err != nil {
log.Errorf("Error checking isPruningPointOnSyncerChain: %s", err)
}
// We're only interested in syncer chain blocks that have our pruning
// point in their selected chain. Otherwise, it means one of the following:
// 1) We will not switch the virtual selected chain to the syncers chain since it will violate finality
// (hence we can ignore it unless merged by others).
// 2) syncerChainHash is actually in the past of our pruning point so there's no
// point in syncing from it.
if err == nil && isPruningPointOnSyncerChain {
currentHighestKnownSyncerChainHash = syncerChainHash
break
}
currentHighestKnownSyncerChainHash = syncerChainHash
break
}
lowestUnknownSyncerChainHash = syncerChainHash
}
@@ -285,7 +261,7 @@ func (flow *handleIBDFlow) negotiateMissingSyncerChainSegment() (*externalapi.Do
}
}
log.Infof("Found highest known syncer chain block %s from peer %s",
log.Debugf("Found highest known syncer chain block %s from peer %s",
highestKnownSyncerChainHash, flow.peer)
return syncerHeaderSelectedTipHash, highestKnownSyncerChainHash, nil
@@ -300,14 +276,10 @@ func (flow *handleIBDFlow) isGenesisVirtualSelectedParent() (bool, error) {
return virtualSelectedParent.Equal(flow.Config().NetParams().GenesisHash), nil
}
func (flow *handleIBDFlow) logIBDFinished(isFinishedSuccessfully bool, err error) {
func (flow *handleIBDFlow) logIBDFinished(isFinishedSuccessfully bool) {
successString := "successfully"
if !isFinishedSuccessfully {
if err != nil {
successString = fmt.Sprintf("(interrupted: %s)", err)
} else {
successString = fmt.Sprintf("(interrupted)")
}
successString = "(interrupted)"
}
log.Infof("IBD with peer %s finished %s", flow.peer, successString)
}
@@ -646,12 +618,6 @@ func (flow *handleIBDFlow) syncMissingBlockBodies(highHash *externalapi.DomainHa
progressReporter := newIBDProgressReporter(lowBlockHeader.DAAScore(), highBlockHeader.DAAScore(), "blocks")
highestProcessedDAAScore := lowBlockHeader.DAAScore()
// If the IBD is small, we want to update the virtual after each block in order to avoid complications and possible bugs.
updateVirtual, err := flow.Domain().Consensus().IsNearlySynced()
if err != nil {
return err
}
for offset := 0; offset < len(hashes); offset += ibdBatchSize {
var hashesToRequest []*externalapi.DomainHash
if offset+ibdBatchSize < len(hashes) {
@@ -688,7 +654,7 @@ func (flow *handleIBDFlow) syncMissingBlockBodies(highHash *externalapi.DomainHa
return err
}
err = flow.Domain().Consensus().ValidateAndInsertBlock(block, updateVirtual)
err = flow.Domain().Consensus().ValidateAndInsertBlock(block, false)
if err != nil {
if errors.Is(err, ruleerrors.ErrDuplicateBlock) {
log.Debugf("Skipping IBD Block %s as it has already been added to the DAG", blockHash)
@@ -707,15 +673,7 @@ func (flow *handleIBDFlow) syncMissingBlockBodies(highHash *externalapi.DomainHa
progressReporter.reportProgress(len(hashesToRequest), highestProcessedDAAScore)
}
// We need to resolve virtual only if it wasn't updated while syncing block bodies
if !updateVirtual {
err := flow.resolveVirtual(highestProcessedDAAScore)
if err != nil {
return err
}
}
return flow.OnNewBlockTemplate()
return flow.resolveVirtual(highestProcessedDAAScore)
}
func (flow *handleIBDFlow) banIfBlockIsHeaderOnly(block *externalapi.DomainBlock) error {
@@ -747,5 +705,9 @@ func (flow *handleIBDFlow) resolveVirtual(estimatedVirtualDAAScoreTarget uint64)
}
log.Infof("Resolved virtual")
err = flow.OnNewBlockTemplate()
if err != nil {
return err
}
return nil
}

View File

@@ -25,7 +25,7 @@ func (flow *handleIBDFlow) ibdWithHeadersProof(
return err
}
log.Infof("IBD with pruning proof from %s was unsuccessful. Deleting the staging consensus. (%s)", flow.peer, err)
log.Infof("IBD with pruning proof from %s was unsuccessful. Deleting the staging consensus.", flow.peer)
deleteStagingConsensusErr := flow.Domain().DeleteStagingConsensus()
if deleteStagingConsensusErr != nil {
return deleteStagingConsensusErr
@@ -55,12 +55,7 @@ func (flow *handleIBDFlow) shouldSyncAndShouldDownloadHeadersProof(
var highestSharedBlockFound, isPruningPointInSharedBlockChain bool
if highestKnownSyncerChainHash != nil {
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(highestKnownSyncerChainHash)
if err != nil {
return false, false, err
}
highestSharedBlockFound = blockInfo.HasBody()
highestSharedBlockFound = true
pruningPoint, err := flow.Domain().Consensus().PruningPoint()
if err != nil {
return false, false, err
@@ -85,33 +80,28 @@ func (flow *handleIBDFlow) shouldSyncAndShouldDownloadHeadersProof(
return true, true, nil
}
if highestKnownSyncerChainHash == nil {
log.Infof("Stopping IBD since IBD from this node will cause a finality conflict")
return false, false, nil
}
return false, true, nil
return false, false, nil
}
return false, true, nil
}
func (flow *handleIBDFlow) checkIfHighHashHasMoreBlueWorkThanSelectedTipAndPruningDepthMoreBlueScore(relayBlock *externalapi.DomainBlock) (bool, error) {
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
headersSelectedTip, err := flow.Domain().Consensus().GetHeadersSelectedTip()
if err != nil {
return false, err
}
virtualSelectedTipInfo, err := flow.Domain().Consensus().GetBlockInfo(virtualSelectedParent)
headersSelectedTipInfo, err := flow.Domain().Consensus().GetBlockInfo(headersSelectedTip)
if err != nil {
return false, err
}
if relayBlock.Header.BlueScore() < virtualSelectedTipInfo.BlueScore+flow.Config().NetParams().PruningDepth() {
if relayBlock.Header.BlueScore() < headersSelectedTipInfo.BlueScore+flow.Config().NetParams().PruningDepth() {
return false, nil
}
return relayBlock.Header.BlueWork().Cmp(virtualSelectedTipInfo.BlueWork) > 0, nil
return relayBlock.Header.BlueWork().Cmp(headersSelectedTipInfo.BlueWork) > 0, nil
}
func (flow *handleIBDFlow) syncAndValidatePruningPointProof() (*externalapi.DomainHash, error) {
@@ -291,13 +281,7 @@ func (flow *handleIBDFlow) processBlockWithTrustedData(
}
err := consensus.ValidateAndInsertBlockWithTrustedData(blockWithTrustedData, false)
if err != nil {
if errors.As(err, &ruleerrors.RuleError{}) {
return protocolerrors.Wrapf(true, err, "failed validating block with trusted data")
}
return err
}
return nil
return err
}
func (flow *handleIBDFlow) receiveBlockWithTrustedData() (*appmessage.MsgBlockWithTrustedDataV4, bool, error) {

View File

@@ -102,7 +102,7 @@ func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *externalapi.DomainTransactionID) bool {
// Ask the transaction memory pool if the transaction is known
// to it in any form (main pool or orphan).
if _, _, ok := flow.Domain().MiningManager().GetTransaction(txID, true, true); ok {
if _, ok := flow.Domain().MiningManager().GetTransaction(txID); ok {
return true
}

View File

@@ -30,7 +30,7 @@ func (flow *handleRequestedTransactionsFlow) start() error {
}
for _, transactionID := range msgRequestTransactions.IDs {
tx, _, ok := flow.Domain().MiningManager().GetTransaction(transactionID, true, false)
tx, ok := flow.Domain().MiningManager().GetTransaction(transactionID)
if !ok {
msgTransactionNotFound := appmessage.NewMsgTransactionNotFound(transactionID)
@@ -40,6 +40,7 @@ func (flow *handleRequestedTransactionsFlow) start() error {
}
continue
}
err := flow.outgoingRoute.Enqueue(appmessage.DomainTransactionToMsgTx(tx))
if err != nil {
return err

View File

@@ -223,9 +223,18 @@ func (m *Manager) notifyVirtualSelectedParentChainChanged(virtualChangeSet *exte
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyVirtualSelectedParentChainChanged")
defer onEnd()
hasListeners, includeAcceptedTransactionIDs := m.context.NotificationManager.HasListenersThatPropagateVirtualSelectedParentChainChanged()
listenersThatPropagateSelectedParentChanged :=
m.context.NotificationManager.AllListenersThatPropagateVirtualSelectedParentChainChanged()
if len(listenersThatPropagateSelectedParentChanged) > 0 {
// Generating acceptedTransactionIDs is a heavy operation, so we check if it's needed by any listener.
includeAcceptedTransactionIDs := false
for _, listener := range listenersThatPropagateSelectedParentChanged {
if listener.IncludeAcceptedTransactionIDsInVirtualSelectedParentChainChangedNotifications() {
includeAcceptedTransactionIDs = true
break
}
}
if hasListeners {
notification, err := m.context.ConvertVirtualSelectedParentChainChangesToChainChangedNotificationMessage(
virtualChangeSet.VirtualSelectedParentChainChanges, includeAcceptedTransactionIDs)
if err != nil {

View File

@@ -5,7 +5,6 @@ import (
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/app/appmessage"
@@ -142,29 +141,16 @@ func (nm *NotificationManager) NotifyVirtualSelectedParentChainChanged(
return nil
}
// HasListenersThatPropagateVirtualSelectedParentChainChanged returns whether there's any listener that is
// subscribed to VirtualSelectedParentChainChanged notifications as well as checks if any such listener requested
// to include AcceptedTransactionIDs.
func (nm *NotificationManager) HasListenersThatPropagateVirtualSelectedParentChainChanged() (hasListeners, hasListenersThatRequireAcceptedTransactionIDs bool) {
nm.RLock()
defer nm.RUnlock()
hasListeners = false
hasListenersThatRequireAcceptedTransactionIDs = false
// AllListenersThatPropagateVirtualSelectedParentChainChanged returns true if there's any listener that is
// subscribed to VirtualSelectedParentChainChanged notifications.
func (nm *NotificationManager) AllListenersThatPropagateVirtualSelectedParentChainChanged() []*NotificationListener {
var listenersThatPropagate []*NotificationListener
for _, listener := range nm.listeners {
if listener.propagateVirtualSelectedParentChainChangedNotifications {
hasListeners = true
// Generating acceptedTransactionIDs is a heavy operation, so we check if it's needed by any listener.
if listener.includeAcceptedTransactionIDsInVirtualSelectedParentChainChangedNotifications {
hasListenersThatRequireAcceptedTransactionIDs = true
break
}
listenersThatPropagate = append(listenersThatPropagate, listener)
}
}
return hasListeners, hasListenersThatRequireAcceptedTransactionIDs
return listenersThatPropagate
}
// NotifyFinalityConflict notifies the notification manager that there's a finality conflict in the DAG
@@ -351,11 +337,7 @@ func (nl *NotificationListener) PropagateFinalityConflictResolvedNotifications()
// to the remote listener for the given addresses. Subsequent calls instruct the listener to
// send UTXOs changed notifications for those addresses along with the old ones. Duplicate addresses
// are ignored.
func (nm *NotificationManager) PropagateUTXOsChangedNotifications(nl *NotificationListener, addresses []*UTXOsChangedNotificationAddress) {
// Apply a write-lock since the internal listener address map is modified
nm.Lock()
defer nm.Unlock()
func (nl *NotificationListener) PropagateUTXOsChangedNotifications(addresses []*UTXOsChangedNotificationAddress) {
if !nl.propagateUTXOsChangedNotifications {
nl.propagateUTXOsChangedNotifications = true
nl.propagateUTXOsChangedNotificationAddresses =
@@ -370,11 +352,7 @@ func (nm *NotificationManager) PropagateUTXOsChangedNotifications(nl *Notificati
// StopPropagatingUTXOsChangedNotifications instructs the listener to stop sending UTXOs
// changed notifications to the remote listener for the given addresses. Addresses for which
// notifications are not currently sent are ignored.
func (nm *NotificationManager) StopPropagatingUTXOsChangedNotifications(nl *NotificationListener, addresses []*UTXOsChangedNotificationAddress) {
// Apply a write-lock since the internal listener address map is modified
nm.Lock()
defer nm.Unlock()
func (nl *NotificationListener) StopPropagatingUTXOsChangedNotifications(addresses []*UTXOsChangedNotificationAddress) {
if !nl.propagateUTXOsChangedNotifications {
return
}
@@ -443,7 +421,7 @@ func (nl *NotificationListener) convertUTXOChangesToUTXOsChangedNotification(
}
func (nl *NotificationListener) scriptPubKeyStringToAddressString(scriptPublicKeyString utxoindex.ScriptPublicKeyString) (string, error) {
scriptPubKey := externalapi.NewScriptPublicKeyFromString(string(scriptPublicKeyString))
scriptPubKey := utxoindex.ConvertStringToScriptPublicKey(scriptPublicKeyString)
// ignore error because it is often returned when the script is of unknown type
scriptType, address, err := txscript.ExtractScriptPubKeyAddress(scriptPubKey, nl.params)

View File

@@ -32,6 +32,22 @@ func ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(address string, pair
return utxosByAddressesEntries
}
// convertUTXOOutpointsToUTXOsByAddressesEntries converts
// UTXOOutpoints to a slice of UTXOsByAddressesEntry
func convertUTXOOutpointsToUTXOsByAddressesEntries(address string, outpoints utxoindex.UTXOOutpoints) []*appmessage.UTXOsByAddressesEntry {
utxosByAddressesEntries := make([]*appmessage.UTXOsByAddressesEntry, 0, len(outpoints))
for outpoint := range outpoints {
utxosByAddressesEntries = append(utxosByAddressesEntries, &appmessage.UTXOsByAddressesEntry{
Address: address,
Outpoint: &appmessage.RPCOutpoint{
TransactionID: outpoint.TransactionID.String(),
Index: outpoint.Index,
},
})
}
return utxosByAddressesEntries
}
// ConvertAddressStringsToUTXOsChangedNotificationAddresses converts address strings
// to UTXOsChangedNotificationAddresses
func (ctx *Context) ConvertAddressStringsToUTXOsChangedNotificationAddresses(
@@ -47,7 +63,7 @@ func (ctx *Context) ConvertAddressStringsToUTXOsChangedNotificationAddresses(
if err != nil {
return nil, errors.Errorf("Could not create a scriptPublicKey for address '%s': %s", addressString, err)
}
scriptPublicKeyString := utxoindex.ScriptPublicKeyString(scriptPublicKey.String())
scriptPublicKeyString := utxoindex.ConvertScriptPublicKeyToString(scriptPublicKey)
addresses[i] = &UTXOsChangedNotificationAddress{
Address: addressString,
ScriptPublicKeyString: scriptPublicKeyString,

View File

@@ -122,7 +122,6 @@ func (ctx *Context) PopulateTransactionWithVerboseData(
}
ctx.Domain.Consensus().PopulateMass(domainTransaction)
transaction.VerboseData = &appmessage.RPCTransactionVerboseData{
TransactionID: consensushashing.TransactionID(domainTransaction).String(),
Hash: consensushashing.TransactionHash(domainTransaction).String(),

View File

@@ -37,7 +37,7 @@ func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appm
return nil, err
}
if !blockInfo.HasHeader() {
if !blockInfo.Exists {
return &appmessage.GetBlocksResponseMessage{
Error: appmessage.RPCErrorf("Could not find lowHash %s", getBlocksRequest.LowHash),
}, nil

View File

@@ -16,7 +16,7 @@ func HandleGetInfo(context *rpccontext.Context, _ *router.Router, _ appmessage.M
response := appmessage.NewGetInfoResponseMessage(
context.NetAdapter.ID().String(),
uint64(context.Domain.MiningManager().TransactionCount(true, false)),
uint64(context.Domain.MiningManager().TransactionCount()),
version.Version(),
context.Config.UTXOIndex,
context.ProtocolManager.Context().HasPeers() && isNearlySynced,

View File

@@ -12,36 +12,58 @@ func HandleGetMempoolEntries(context *rpccontext.Context, _ *router.Router, requ
entries := make([]*appmessage.MempoolEntry, 0)
transactionPoolTransactions, orphanPoolTransactions := context.Domain.MiningManager().AllTransactions(!getMempoolEntriesRequest.FilterTransactionPool, getMempoolEntriesRequest.IncludeOrphanPool)
if !getMempoolEntriesRequest.FilterTransactionPool {
for _, transaction := range transactionPoolTransactions {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
entries = append(entries, &appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: false,
})
transactionPoolEntries, err := getTransactionPoolMempoolEntries(context)
if err != nil {
return nil, err
}
entries = append(entries, transactionPoolEntries...)
}
if getMempoolEntriesRequest.IncludeOrphanPool {
for _, transaction := range orphanPoolTransactions {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
entries = append(entries, &appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: true,
})
orphanPoolEntries, err := getOrphanPoolMempoolEntries(context)
if err != nil {
return nil, err
}
entries = append(entries, orphanPoolEntries...)
}
return appmessage.NewGetMempoolEntriesResponseMessage(entries), nil
}
func getTransactionPoolMempoolEntries(context *rpccontext.Context) ([]*appmessage.MempoolEntry, error) {
transactions := context.Domain.MiningManager().AllTransactions()
entries := make([]*appmessage.MempoolEntry, 0, len(transactions))
for _, transaction := range transactions {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
entries = append(entries, &appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: false,
})
}
return entries, nil
}
func getOrphanPoolMempoolEntries(context *rpccontext.Context) ([]*appmessage.MempoolEntry, error) {
orphanTransactions := context.Domain.MiningManager().AllOrphanTransactions()
entries := make([]*appmessage.MempoolEntry, 0, len(orphanTransactions))
for _, orphanTransaction := range orphanTransactions {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(orphanTransaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
entries = append(entries, &appmessage.MempoolEntry{
Fee: orphanTransaction.Fee,
Transaction: rpcTransaction,
IsOrphan: true,
})
}
return entries, nil
}

View File

@@ -1,10 +1,14 @@
package rpchandlers
import (
"errors"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
)
@@ -16,107 +20,132 @@ func HandleGetMempoolEntriesByAddresses(context *rpccontext.Context, _ *router.R
mempoolEntriesByAddresses := make([]*appmessage.MempoolEntryByAddress, 0)
sendingInTransactionPool, receivingInTransactionPool, sendingInOrphanPool, receivingInOrphanPool, err := context.Domain.MiningManager().GetTransactionsByAddresses(!getMempoolEntriesByAddressesRequest.FilterTransactionPool, getMempoolEntriesByAddressesRequest.IncludeOrphanPool)
if err != nil {
return nil, err
if !getMempoolEntriesByAddressesRequest.FilterTransactionPool {
transactionPoolTransactions := context.Domain.MiningManager().AllTransactions()
transactionPoolEntriesByAddresses, err := extractMempoolEntriesByAddressesFromTransactions(
context,
getMempoolEntriesByAddressesRequest.Addresses,
transactionPoolTransactions,
false,
)
if err != nil {
rpcError := &appmessage.RPCError{}
if !errors.As(err, &rpcError) {
return nil, err
}
errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{}
errorMessage.Error = rpcError
return errorMessage, nil
}
mempoolEntriesByAddresses = append(mempoolEntriesByAddresses, transactionPoolEntriesByAddresses...)
}
for _, addressString := range getMempoolEntriesByAddressesRequest.Addresses {
if getMempoolEntriesByAddressesRequest.IncludeOrphanPool {
address, err := util.DecodeAddress(addressString, context.Config.NetParams().Prefix)
orphanPoolTransactions := context.Domain.MiningManager().AllOrphanTransactions()
orphanPoolEntriesByAddress, err := extractMempoolEntriesByAddressesFromTransactions(
context,
getMempoolEntriesByAddressesRequest.Addresses,
orphanPoolTransactions,
true,
)
if err != nil {
errorMessage := &appmessage.GetMempoolEntriesByAddressesResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not decode address '%s': %s", addressString, err)
rpcError := &appmessage.RPCError{}
if !errors.As(err, &rpcError) {
return nil, err
}
errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{}
errorMessage.Error = rpcError
return errorMessage, nil
}
mempoolEntriesByAddresses = append(mempoolEntriesByAddresses, orphanPoolEntriesByAddress...)
}
return appmessage.NewGetMempoolEntriesByAddressesResponseMessage(mempoolEntriesByAddresses), nil
}
//TO DO: optimize extractMempoolEntriesByAddressesFromTransactions
func extractMempoolEntriesByAddressesFromTransactions(context *rpccontext.Context, addresses []string, transactions []*externalapi.DomainTransaction, areOrphans bool) ([]*appmessage.MempoolEntryByAddress, error) {
mempoolEntriesByAddresses := make([]*appmessage.MempoolEntryByAddress, 0)
for _, addressString := range addresses {
_, err := util.DecodeAddress(addressString, context.Config.ActiveNetParams.Prefix)
if err != nil {
return nil, appmessage.RPCErrorf("Could not decode address '%s': %s", addressString, err)
}
sending := make([]*appmessage.MempoolEntry, 0)
receiving := make([]*appmessage.MempoolEntry, 0)
scriptPublicKey, err := txscript.PayToAddrScript(address)
if err != nil {
errorMessage := &appmessage.GetMempoolEntriesByAddressesResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not extract scriptPublicKey from address '%s': %s", addressString, err)
return errorMessage, nil
}
for _, transaction := range transactions {
if !getMempoolEntriesByAddressesRequest.FilterTransactionPool {
for i, input := range transaction.Inputs {
if input.UTXOEntry == nil {
if !areOrphans { // Orphans can legitimately have `input.UTXOEntry == nil`
// TODO: Fix the underlying cause of the bug for non-orphan entries
log.Debugf(
"Couldn't find UTXO entry for input %d in mempool transaction %s. This is a bug and should be fixed.",
i, consensushashing.TransactionID(transaction))
}
continue
}
if transaction, found := sendingInTransactionPool[scriptPublicKey.String()]; found {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
_, transactionSendingAddress, err := txscript.ExtractScriptPubKeyAddress(
input.UTXOEntry.ScriptPublicKey(),
context.Config.ActiveNetParams)
if err != nil {
return nil, err
}
sending = append(sending, &appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: false,
},
)
if addressString == transactionSendingAddress.String() {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
sending = append(
sending,
&appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: areOrphans,
},
)
break //one input is enough
}
}
if transaction, found := receivingInTransactionPool[scriptPublicKey.String()]; found {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
for _, output := range transaction.Outputs {
_, transactionReceivingAddress, err := txscript.ExtractScriptPubKeyAddress(
output.ScriptPublicKey,
context.Config.ActiveNetParams,
)
if err != nil {
return nil, err
}
receiving = append(receiving, &appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: false,
},
)
}
}
if getMempoolEntriesByAddressesRequest.IncludeOrphanPool {
if transaction, found := sendingInOrphanPool[scriptPublicKey.String()]; found {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
if addressString == transactionReceivingAddress.String() {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
receiving = append(
receiving,
&appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: areOrphans,
},
)
break //one output is enough
}
sending = append(sending, &appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: true,
},
)
}
if transaction, found := receivingInOrphanPool[scriptPublicKey.String()]; found {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
receiving = append(receiving, &appmessage.MempoolEntry{
Fee: transaction.Fee,
Transaction: rpcTransaction,
IsOrphan: true,
},
//Only append mempoolEntriesByAddress, if at least 1 mempoolEntry for the address is found.
//This mimics the behaviour of GetUtxosByAddresses RPC call.
if len(sending) > 0 || len(receiving) > 0 {
mempoolEntriesByAddresses = append(
mempoolEntriesByAddresses,
&appmessage.MempoolEntryByAddress{
Address: addressString,
Sending: sending,
Receiving: receiving,
},
)
}
}
if len(sending) > 0 || len(receiving) > 0 {
mempoolEntriesByAddresses = append(
mempoolEntriesByAddresses,
&appmessage.MempoolEntryByAddress{
Address: address.String(),
Sending: sending,
Receiving: receiving,
},
)
}
}
return appmessage.NewGetMempoolEntriesByAddressesResponseMessage(mempoolEntriesByAddresses), nil
return mempoolEntriesByAddresses, nil
}

View File

@@ -24,7 +24,14 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques
return errorMessage, nil
}
mempoolTransaction, isOrphan, found := context.Domain.MiningManager().GetTransaction(transactionID, !getMempoolEntryRequest.FilterTransactionPool, getMempoolEntryRequest.IncludeOrphanPool)
if !getMempoolEntryRequest.FilterTransactionPool {
transaction, found = context.Domain.MiningManager().GetTransaction(transactionID)
}
if getMempoolEntryRequest.IncludeOrphanPool && !found {
transaction, found = context.Domain.MiningManager().GetOrphanTransaction(transactionID)
isOrphan = true
}
if !found {
errorMessage := &appmessage.GetMempoolEntryResponseMessage{}
@@ -32,7 +39,7 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques
return errorMessage, nil
}
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(mempoolTransaction)
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err = context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err

View File

@@ -26,7 +26,7 @@ func HandleNotifyUTXOsChanged(context *rpccontext.Context, router *router.Router
if err != nil {
return nil, err
}
context.NotificationManager.PropagateUTXOsChangedNotifications(listener, addresses)
listener.PropagateUTXOsChangedNotifications(addresses)
response := appmessage.NewNotifyUTXOsChangedResponseMessage()
return response, nil

View File

@@ -26,7 +26,7 @@ func HandleStopNotifyingUTXOsChanged(context *rpccontext.Context, router *router
if err != nil {
return nil, err
}
context.NotificationManager.StopPropagatingUTXOsChangedNotifications(listener, addresses)
listener.StopPropagatingUTXOsChangedNotifications(addresses)
response := appmessage.NewStopNotifyingUTXOsChangedResponseMessage()
return response, nil

View File

@@ -28,8 +28,7 @@ func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, requ
}
log.Debugf("Rejected transaction %s: %s", transactionID, err)
// Return the ID also in the case of error, so that clients can match the response to the correct transaction submit request
errorMessage := appmessage.NewSubmitTransactionResponseMessage(transactionID.String())
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Rejected transaction %s: %s", transactionID, err)
return errorMessage, nil
}

View File

@@ -1,119 +1,3 @@
Kaspad v0.12.17 - 2024-02-19
===========================
* Wallet-related improvements and fixes (#2253, #2257, #2258, #2262)
Kaspad v0.12.16 - 2023-12-25
===========================
* Adapt wallet UTXO selection to dust patch (#2254)
Kaspad v0.12.15 - 2023-12-16
===========================
* Update ECDSA address test to use a valid public key (#2202)
* Fix off by small amounts in sent amount kaspa (#2220)
* Use removeRedeemers correctly by (#2235)
* Fix windows asset building by increasing go version (#2245)
* Added a mainnet dnsseeder (#2247)
* Fix extract atomic swap data pushes (#2203)
* Adapt kaspawallet to support testnet 11 (#2211)
* Fix type detection in RemoveInvalidTransactions (#2252)
Kaspad v0.12.14 - 2023-09-26
===========================
* Anti-spam measurements against dust attack (#2223)
Kaspad v0.12.13 - 2023-03-06
===========================
* Bump golang.org/x/crypto from 0.0.0-20210513164829-c07d793c2f9a to 0.1.0 (#2195)
* Bump golang.org/x/net from 0.0.0-20210405180319-a5a99cb37ef4 to 0.7.0 (#2194)
* Avoid sending transactions with no funds (#2193)
Kaspad v0.12.12 - 2023-03-06
===========================
* Rename last references to blockheight (#2089)
* Add code of conduct (#2183)
* Extend TestGetPreciseSigOps with more tests (#2188)
* Add Dockerfile to kaspawallet (#2187)
* Add `--send-all` to `kaspawallet send` command (#2181)
* Bump golang.org/x/text from 0.3.5 to 0.3.8 (#2190)
* Upgrade to go 1.19 (#2191)
Kaspad v0.12.11 - 2022-12-1
===========================
* Fix IBD sync conditions (#2174)
Kaspad v0.12.10 - 2022-11-23
===========================
* Increase devnet's initial difficulty (#2167)
Bug fixes:
* Check rule errors when validating blocks with trusted data (#2171)
* Compare blue score with selected tip when checking if a pruning point proof is needed (#2169)
* Add found to GetBlock (#2165)
Wallet new features:
* Use one of the From addresses as a change address (#2164)
Kaspad v0.12.9 - 2022-10-23
===========================
* Create directory before locking lock file (#2160)
Kaspad v0.12.8 - 2022-10-23
===========================
* Remove hard fork activation rules (#2152)
* Add lock file to kaspawallet (#2154)
* Add a new testnet DNS seeder (#2156)
* Use utxo diff algo for pruning point move and use acceptance data method only as a fall-back (#2157)
* Make more checks if status is invalid even if the block exists (#2158)
Kaspad v0.12.7 - 2022-09-21
===========================
* Security Fix + Hard fork - Full details can be seen here: https://medium.com/@michaelsuttonil/kaspa-security-patch-and-hard-fork-september-2022-12da617b0094
Kaspad v0.12.6 - 2022-09-09
===========================
* Remove tests from docker files (#2133)
Wallet new features:
* Optionally show serialized transactions on send (#2135)
Bug fixes:
* Update virtual on IBD if nearly synced (#2134)
Kaspad v0.12.5 - 2022-08-28
===========================
* Add tests for hash writers (#2120)
* Replace daglabs's dnsseeder with Wolfie's (#2119)
* Change testnet dnsseeder (#2126)
* Add RPC timeout parameter to wallet daemon (#2104)
Wallet new features:
* Add UseExistingChangeAddress option to the wallet (#2127)
Bug fixes:
* Call update pruning point if required on resolve virtual or startup (#2129)
* Add missing locks to notification listener modifications (#2124)
* Calculate pruning point utxo set from acceptance data (#2123)
* Fix RPC client memory/goroutine leak (#2122)
* Fix a subtle lock sync issue in consensus insert block (#2121)
* Mempool: Retrieve stable state of the mempool. Optimze get mempool entries by addresses (#2111)
* Kaspawallet.send(): Make separate context for Broadcast, to prolong timeout (#2131)
Kaspad v0.12.4 - 2022-07-17
===========================

View File

@@ -4,7 +4,7 @@ kaspactl is an RPC client for kaspad
## Requirements
Go 1.19 or later.
Go 1.18 or later.
## Installation

View File

@@ -1,11 +1,13 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.19-alpine AS build
FROM golang:1.18-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
WORKDIR /go/src/github.com/kaspanet/kaspad
RUN apk add --no-cache curl git openssh binutils gcc musl-dev
RUN go get -u golang.org/x/lint/golint \
honnef.co/go/tools/cmd/staticcheck
COPY go.mod .
COPY go.sum .
@@ -16,6 +18,10 @@ COPY . .
WORKDIR /go/src/github.com/kaspanet/kaspad/cmd/kaspactl
RUN GOFMT_RESULT=`go fmt ./...`; echo $GOFMT_RESULT; test -z "$GOFMT_RESULT"
RUN go vet ./...
RUN golint -set_exit_status ./...
RUN staticcheck -checks SA4006 ./...
RUN GOOS=linux go build -a -installsuffix cgo -o kaspactl .
# --- multistage docker build: stage #2: runtime image

View File

@@ -4,7 +4,7 @@ Kaspaminer is a CPU-based miner for kaspad
## Requirements
Go 1.19 or later.
Go 1.18 or later.
## Installation

View File

@@ -1,11 +1,13 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.19-alpine AS build
FROM golang:1.18-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
WORKDIR /go/src/github.com/kaspanet/kaspad
RUN apk add --no-cache curl git openssh binutils gcc musl-dev
RUN go get -u golang.org/x/lint/golint \
honnef.co/go/tools/cmd/staticcheck
COPY go.mod .
COPY go.sum .
@@ -15,6 +17,11 @@ RUN go mod download
COPY . .
WORKDIR /go/src/github.com/kaspanet/kaspad/cmd/kaspaminer
RUN GOFMT_RESULT=`go fmt ./...`; echo $GOFMT_RESULT; test -z "$GOFMT_RESULT"
RUN go vet ./...
RUN golint -set_exit_status ./...
RUN staticcheck -checks SA4006 ./...
RUN GOOS=linux go build -a -installsuffix cgo -o kaspaminer .
# --- multistage docker build: stage #2: runtime image

View File

@@ -22,8 +22,6 @@ const (
newAddressSubCmd = "new-address"
dumpUnencryptedDataSubCmd = "dump-unencrypted-data"
startDaemonSubCmd = "start-daemon"
versionSubCmd = "version"
getDaemonVersionSubCmd = "get-daemon-version"
)
const (
@@ -32,7 +30,6 @@ const (
)
type configFlags struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
config.NetworkFlags
}
@@ -55,15 +52,12 @@ type balanceConfig struct {
}
type sendConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Password string `long:"password" short:"p" description:"Wallet password"`
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Repeat multiple times (adding -a before each) to accept several addresses" required:"false"`
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount). If --from-address was used, will send all only from the specified addresses."`
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Password string `long:"password" short:"p" description:"Wallet password"`
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"`
config.NetworkFlags
}
@@ -74,12 +68,10 @@ type sweepConfig struct {
}
type createUnsignedTransactionConfig struct {
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"`
config.NetworkFlags
}
@@ -116,13 +108,11 @@ type newAddressConfig struct {
}
type startDaemonConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Password string `long:"password" short:"p" description:"Wallet password"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Listen string `long:"listen" short:"l" description:"Address to listen on (default: 0.0.0.0:8082)"`
Timeout uint32 `long:"wait-timeout" short:"w" description:"Waiting timeout for RPC calls, seconds (default: 30 s)"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
MinFeePerTx uint64 `long:"min-fee-per-tx" description:"Minimum fee per transaction (in sompis) (default: 0)"`
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Password string `long:"password" short:"p" description:"Wallet password"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Listen string `short:"l" long:"listen" description:"Address to listen on (default: 0.0.0.0:8082)"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
config.NetworkFlags
}
@@ -133,13 +123,6 @@ type dumpUnencryptedDataConfig struct {
config.NetworkFlags
}
type versionConfig struct {
}
type getDaemonVersionConfig struct {
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
}
func parseCommandLine() (subCommand string, config interface{}) {
cfg := &configFlags{}
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
@@ -196,11 +179,9 @@ func parseCommandLine() (subCommand string, config interface{}) {
Listen: defaultListen,
}
parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf)
parser.AddCommand(versionSubCmd, "Get the wallet version", "Get the wallet version", &versionConfig{})
getDaemonVersionConf := &getDaemonVersionConfig{DaemonAddress: defaultListen}
parser.AddCommand(getDaemonVersionSubCmd, "Get the wallet daemon version", "Get the wallet daemon version", getDaemonVersionConf)
_, err := parser.Parse()
if err != nil {
var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
@@ -232,10 +213,6 @@ func parseCommandLine() (subCommand string, config interface{}) {
if err != nil {
printErrorAndExit(err)
}
err = validateSendConfig(sendConf)
if err != nil {
printErrorAndExit(err)
}
config = sendConf
case sweepSubCmd:
combineNetworkFlags(&sweepConf.NetworkFlags, &cfg.NetworkFlags)
@@ -250,10 +227,6 @@ func parseCommandLine() (subCommand string, config interface{}) {
if err != nil {
printErrorAndExit(err)
}
err = validateCreateUnsignedTransactionConf(createUnsignedTransactionConf)
if err != nil {
printErrorAndExit(err)
}
config = createUnsignedTransactionConf
case signSubCmd:
combineNetworkFlags(&signConf.NetworkFlags, &cfg.NetworkFlags)
@@ -304,32 +277,11 @@ func parseCommandLine() (subCommand string, config interface{}) {
printErrorAndExit(err)
}
config = startDaemonConf
case versionSubCmd:
case getDaemonVersionSubCmd:
config = getDaemonVersionConf
}
return parser.Command.Active.Name, config
}
func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig) error {
if (!conf.IsSendAll && conf.SendAmount == "") ||
(conf.IsSendAll && conf.SendAmount != "") {
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
}
return nil
}
func validateSendConfig(conf *sendConfig) error {
if (!conf.IsSendAll && conf.SendAmount == "") ||
(conf.IsSendAll && conf.SendAmount != "") {
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
}
return nil
}
func combineNetworkFlags(dst, src *config.NetworkFlags) {
dst.Testnet = dst.Testnet || src.Testnet
dst.Simnet = dst.Simnet || src.Simnet

View File

@@ -78,11 +78,6 @@ func create(conf *createConfig) error {
return err
}
err = file.TryLock()
if err != nil {
return err
}
err = file.Save()
if err != nil {
return err

View File

@@ -7,7 +7,7 @@ import (
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
)
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
@@ -20,18 +20,11 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
defer cancel()
sendAmountSompi, err := utils.KasToSompi(conf.SendAmount)
if err != nil {
return err
}
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
From: conf.FromAddresses,
Address: conf.ToAddress,
Amount: sendAmountSompi,
IsSendAll: conf.IsSendAll,
UseExistingChangeAddress: conf.UseExistingChangeAddress,
From: conf.FromAddresses,
Address: conf.ToAddress,
Amount: sendAmountSompi,
})
if err != nil {
return err

View File

@@ -2,7 +2,6 @@ package client
import (
"context"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
"time"
"github.com/pkg/errors"
@@ -17,7 +16,7 @@ func Connect(address string) (pb.KaspawalletdClient, func(), error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
conn, err := grpc.DialContext(ctx, address, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(server.MaxDaemonSendMsgSize)))
conn, err := grpc.DialContext(ctx, address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, nil, errors.New("kaspawallet daemon is not running, start it with `kaspawallet start-daemon`")

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.12.3
// protoc-gen-go v1.28.0
// protoc v3.17.2
// source: kaspawalletd.proto
package pb
@@ -189,11 +189,9 @@ type CreateUnsignedTransactionsRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
From []string `protobuf:"bytes,3,rep,name=from,proto3" json:"from,omitempty"`
UseExistingChangeAddress bool `protobuf:"varint,4,opt,name=useExistingChangeAddress,proto3" json:"useExistingChangeAddress,omitempty"`
IsSendAll bool `protobuf:"varint,5,opt,name=isSendAll,proto3" json:"isSendAll,omitempty"`
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
From []string `protobuf:"bytes,3,rep,name=from,proto3" json:"from,omitempty"`
}
func (x *CreateUnsignedTransactionsRequest) Reset() {
@@ -249,20 +247,6 @@ func (x *CreateUnsignedTransactionsRequest) GetFrom() []string {
return nil
}
func (x *CreateUnsignedTransactionsRequest) GetUseExistingChangeAddress() bool {
if x != nil {
return x.UseExistingChangeAddress
}
return false
}
func (x *CreateUnsignedTransactionsRequest) GetIsSendAll() bool {
if x != nil {
return x.IsSendAll
}
return false
}
type CreateUnsignedTransactionsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1002,12 +986,10 @@ type SendRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ToAddress string `protobuf:"bytes,1,opt,name=toAddress,proto3" json:"toAddress,omitempty"`
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
From []string `protobuf:"bytes,4,rep,name=from,proto3" json:"from,omitempty"`
UseExistingChangeAddress bool `protobuf:"varint,5,opt,name=useExistingChangeAddress,proto3" json:"useExistingChangeAddress,omitempty"`
IsSendAll bool `protobuf:"varint,6,opt,name=isSendAll,proto3" json:"isSendAll,omitempty"`
ToAddress string `protobuf:"bytes,1,opt,name=toAddress,proto3" json:"toAddress,omitempty"`
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
From []string `protobuf:"bytes,4,rep,name=from,proto3" json:"from,omitempty"`
}
func (x *SendRequest) Reset() {
@@ -1070,27 +1052,12 @@ func (x *SendRequest) GetFrom() []string {
return nil
}
func (x *SendRequest) GetUseExistingChangeAddress() bool {
if x != nil {
return x.UseExistingChangeAddress
}
return false
}
func (x *SendRequest) GetIsSendAll() bool {
if x != nil {
return x.IsSendAll
}
return false
}
type SendResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
TxIDs []string `protobuf:"bytes,1,rep,name=txIDs,proto3" json:"txIDs,omitempty"`
SignedTransactions [][]byte `protobuf:"bytes,2,rep,name=signedTransactions,proto3" json:"signedTransactions,omitempty"`
TxIDs []string `protobuf:"bytes,1,rep,name=txIDs,proto3" json:"txIDs,omitempty"`
}
func (x *SendResponse) Reset() {
@@ -1132,13 +1099,6 @@ func (x *SendResponse) GetTxIDs() []string {
return nil
}
func (x *SendResponse) GetSignedTransactions() [][]byte {
if x != nil {
return x.SignedTransactions
}
return nil
}
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
type SignRequest struct {
state protoimpl.MessageState
@@ -1242,91 +1202,6 @@ func (x *SignResponse) GetSignedTransactions() [][]byte {
return nil
}
type GetVersionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetVersionRequest) Reset() {
*x = GetVersionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_kaspawalletd_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetVersionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetVersionRequest) ProtoMessage() {}
func (x *GetVersionRequest) ProtoReflect() protoreflect.Message {
mi := &file_kaspawalletd_proto_msgTypes[23]
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 GetVersionRequest.ProtoReflect.Descriptor instead.
func (*GetVersionRequest) Descriptor() ([]byte, []int) {
return file_kaspawalletd_proto_rawDescGZIP(), []int{23}
}
type GetVersionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"`
}
func (x *GetVersionResponse) Reset() {
*x = GetVersionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_kaspawalletd_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetVersionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetVersionResponse) ProtoMessage() {}
func (x *GetVersionResponse) ProtoReflect() protoreflect.Message {
mi := &file_kaspawalletd_proto_msgTypes[24]
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 GetVersionResponse.ProtoReflect.Descriptor instead.
func (*GetVersionResponse) Descriptor() ([]byte, []int) {
return file_kaspawalletd_proto_rawDescGZIP(), []int{24}
}
func (x *GetVersionResponse) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
var File_kaspawalletd_proto protoreflect.FileDescriptor
var file_kaspawalletd_proto_rawDesc = []byte{
@@ -1349,179 +1224,155 @@ var file_kaspawalletd_proto_rawDesc = []byte{
0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x70, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x22, 0xc3, 0x01, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55,
0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02,
0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d,
0x12, 0x3a, 0x0a, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01,
0x28, 0x08, 0x52, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09,
0x69, 0x73, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x6c, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
0x09, 0x69, 0x73, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x6c, 0x6c, 0x22, 0x58, 0x0a, 0x22, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e,
0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e,
0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x14,
0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31, 0x0a, 0x15,
0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x22, 0x52, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x44, 0x6f,
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x44, 0x6f,
0x6d, 0x61, 0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x29, 0x0a, 0x11, 0x42, 0x72, 0x6f, 0x61,
0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a,
0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x74, 0x78,
0x49, 0x44, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f,
0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x08, 0x4f, 0x75,
0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05,
0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64,
0x65, 0x78, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61,
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74,
0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x09, 0x75, 0x74,
0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x55, 0x74, 0x78,
0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x22, 0x55, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69,
0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28,
0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xb2, 0x01, 0x0a, 0x09, 0x55, 0x74, 0x78,
0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x47,
0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62,
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1e, 0x0a,
0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x22, 0x3c, 0x0a,
0x20, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e,
0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x62, 0x0a, 0x21, 0x47,
0x64, 0x69, 0x6e, 0x67, 0x22, 0x69, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e,
0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20,
0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66,
0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x22,
0x58, 0x0a, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65,
0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65,
0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f,
0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x31, 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77,
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x52, 0x0a, 0x10, 0x42, 0x72, 0x6f,
0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
0x08, 0x69, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
0x08, 0x69, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52,
0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x29, 0x0a,
0x11, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74,
0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53,
0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x46, 0x0a, 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x55, 0x74, 0x78, 0x6f,
0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6f,
0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4f, 0x75, 0x74,
0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12,
0x35, 0x0a, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x64, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x75, 0x74, 0x78,
0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x55, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74,
0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62,
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xb2, 0x01,
0x0a, 0x09, 0x55, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f,
0x75, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62,
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b,
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x63, 0x72, 0x69,
0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0f, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f,
0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65,
0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61,
0x73, 0x65, 0x22, 0x3c, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x22, 0x62, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53,
0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x45, 0x6e, 0x74,
0x72, 0x69, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20,
0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x24, 0x0a, 0x0c, 0x53, 0x65, 0x6e,
0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49,
0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22,
0x5d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32,
0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e,
0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3e,
0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e,
0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x12, 0x73, 0x69, 0x67, 0x6e,
0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0xb3,
0x06, 0x0a, 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12,
0x51, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e,
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74,
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20,
0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65,
0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x12, 0x7e, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12,
0x2e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47,
0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61,
0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47,
0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61,
0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x3d, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64,
0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22,
0xcd, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a,
0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52,
0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x3a, 0x0a, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, 0x69, 0x73,
0x74, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, 0x69, 0x73,
0x74, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x73, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x6c, 0x6c, 0x18, 0x06,
0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x6c, 0x6c, 0x22,
0x54, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05,
0x74, 0x78, 0x49, 0x44, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0c, 0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x5d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e,
0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x22, 0x3e, 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c,
0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x47, 0x65, 0x74,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x86, 0x07, 0x0a, 0x0c, 0x6b, 0x61,
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x51, 0x0a, 0x0a, 0x47, 0x65,
0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61,
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70,
0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a,
0x19, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e,
0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, 0x2e, 0x2e, 0x6b, 0x61, 0x73,
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54,
0x58, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6b, 0x61, 0x73,
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54,
0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01,
0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x2e, 0x6b,
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e,
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65,
0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x00, 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x73, 0x12, 0x22, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a,
0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x2e, 0x6b, 0x61,
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b,
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x4b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x6b,
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74,
0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6b, 0x61,
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64,
0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a,
0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x6b, 0x61, 0x73,
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6b, 0x61, 0x73,
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a,
0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e,
0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f,
0x0a, 0x04, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64,
0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x51, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e,
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20,
0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65,
0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 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, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73,
0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x12, 0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64,
0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x30, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6b, 0x61,
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x12, 0x51, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e,
0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64,
0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77,
0x6e, 0x12, 0x1d, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64,
0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e,
0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x12, 0x4e, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12,
0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42,
0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42,
0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73,
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x19, 0x2e, 0x6b, 0x61,
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 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, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1536,7 +1387,7 @@ func file_kaspawalletd_proto_rawDescGZIP() []byte {
return file_kaspawalletd_proto_rawDescData
}
var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
var file_kaspawalletd_proto_goTypes = []interface{}{
(*GetBalanceRequest)(nil), // 0: kaspawalletd.GetBalanceRequest
(*GetBalanceResponse)(nil), // 1: kaspawalletd.GetBalanceResponse
@@ -1561,8 +1412,6 @@ var file_kaspawalletd_proto_goTypes = []interface{}{
(*SendResponse)(nil), // 20: kaspawalletd.SendResponse
(*SignRequest)(nil), // 21: kaspawalletd.SignRequest
(*SignResponse)(nil), // 22: kaspawalletd.SignResponse
(*GetVersionRequest)(nil), // 23: kaspawalletd.GetVersionRequest
(*GetVersionResponse)(nil), // 24: kaspawalletd.GetVersionResponse
}
var file_kaspawalletd_proto_depIdxs = []int32{
2, // 0: kaspawalletd.GetBalanceResponse.addressBalances:type_name -> kaspawalletd.AddressBalances
@@ -1579,19 +1428,17 @@ var file_kaspawalletd_proto_depIdxs = []int32{
9, // 11: kaspawalletd.kaspawalletd.Broadcast:input_type -> kaspawalletd.BroadcastRequest
19, // 12: kaspawalletd.kaspawalletd.Send:input_type -> kaspawalletd.SendRequest
21, // 13: kaspawalletd.kaspawalletd.Sign:input_type -> kaspawalletd.SignRequest
23, // 14: kaspawalletd.kaspawalletd.GetVersion:input_type -> kaspawalletd.GetVersionRequest
1, // 15: kaspawalletd.kaspawalletd.GetBalance:output_type -> kaspawalletd.GetBalanceResponse
18, // 16: kaspawalletd.kaspawalletd.GetExternalSpendableUTXOs:output_type -> kaspawalletd.GetExternalSpendableUTXOsResponse
4, // 17: kaspawalletd.kaspawalletd.CreateUnsignedTransactions:output_type -> kaspawalletd.CreateUnsignedTransactionsResponse
6, // 18: kaspawalletd.kaspawalletd.ShowAddresses:output_type -> kaspawalletd.ShowAddressesResponse
8, // 19: kaspawalletd.kaspawalletd.NewAddress:output_type -> kaspawalletd.NewAddressResponse
12, // 20: kaspawalletd.kaspawalletd.Shutdown:output_type -> kaspawalletd.ShutdownResponse
10, // 21: kaspawalletd.kaspawalletd.Broadcast:output_type -> kaspawalletd.BroadcastResponse
20, // 22: kaspawalletd.kaspawalletd.Send:output_type -> kaspawalletd.SendResponse
22, // 23: kaspawalletd.kaspawalletd.Sign:output_type -> kaspawalletd.SignResponse
24, // 24: kaspawalletd.kaspawalletd.GetVersion:output_type -> kaspawalletd.GetVersionResponse
15, // [15:25] is the sub-list for method output_type
5, // [5:15] is the sub-list for method input_type
1, // 14: kaspawalletd.kaspawalletd.GetBalance:output_type -> kaspawalletd.GetBalanceResponse
18, // 15: kaspawalletd.kaspawalletd.GetExternalSpendableUTXOs:output_type -> kaspawalletd.GetExternalSpendableUTXOsResponse
4, // 16: kaspawalletd.kaspawalletd.CreateUnsignedTransactions:output_type -> kaspawalletd.CreateUnsignedTransactionsResponse
6, // 17: kaspawalletd.kaspawalletd.ShowAddresses:output_type -> kaspawalletd.ShowAddressesResponse
8, // 18: kaspawalletd.kaspawalletd.NewAddress:output_type -> kaspawalletd.NewAddressResponse
12, // 19: kaspawalletd.kaspawalletd.Shutdown:output_type -> kaspawalletd.ShutdownResponse
10, // 20: kaspawalletd.kaspawalletd.Broadcast:output_type -> kaspawalletd.BroadcastResponse
20, // 21: kaspawalletd.kaspawalletd.Send:output_type -> kaspawalletd.SendResponse
22, // 22: kaspawalletd.kaspawalletd.Sign:output_type -> kaspawalletd.SignResponse
14, // [14:23] is the sub-list for method output_type
5, // [5:14] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
@@ -1879,30 +1726,6 @@ func file_kaspawalletd_proto_init() {
return nil
}
}
file_kaspawalletd_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetVersionRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_kaspawalletd_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetVersionResponse); 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{
@@ -1910,7 +1733,7 @@ func file_kaspawalletd_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_kaspawalletd_proto_rawDesc,
NumEnums: 0,
NumMessages: 25,
NumMessages: 23,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -15,7 +15,6 @@ service kaspawalletd {
rpc Send(SendRequest) returns (SendResponse) {}
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
rpc Sign(SignRequest) returns (SignResponse) {}
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
}
message GetBalanceRequest {
@@ -37,8 +36,6 @@ message CreateUnsignedTransactionsRequest {
string address = 1;
uint64 amount = 2;
repeated string from = 3;
bool useExistingChangeAddress = 4;
bool isSendAll = 5;
}
message CreateUnsignedTransactionsResponse {
@@ -60,8 +57,8 @@ message NewAddressResponse {
}
message BroadcastRequest {
bool isDomain = 1;
repeated bytes transactions = 2;
bool isDomain = 1;
repeated bytes transactions = 2;
}
message BroadcastResponse {
@@ -110,13 +107,10 @@ message SendRequest{
uint64 amount = 2;
string password = 3;
repeated string from = 4;
bool useExistingChangeAddress = 5;
bool isSendAll = 6;
}
message SendResponse{
repeated string txIDs = 1;
repeated bytes signedTransactions = 2;
}
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
@@ -128,10 +122,3 @@ message SignRequest{
message SignResponse{
repeated bytes signedTransactions = 1;
}
message GetVersionRequest{
}
message GetVersionResponse{
string version = 1;
}

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.12.3
// - protoc v3.17.2
// source: kaspawalletd.proto
package pb
@@ -33,7 +33,6 @@ type KaspawalletdClient interface {
Send(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error)
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error)
}
type kaspawalletdClient struct {
@@ -125,15 +124,6 @@ func (c *kaspawalletdClient) Sign(ctx context.Context, in *SignRequest, opts ...
return out, nil
}
func (c *kaspawalletdClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) {
out := new(GetVersionResponse)
err := c.cc.Invoke(ctx, "/kaspawalletd.kaspawalletd/GetVersion", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// KaspawalletdServer is the server API for Kaspawalletd service.
// All implementations must embed UnimplementedKaspawalletdServer
// for forward compatibility
@@ -149,7 +139,6 @@ type KaspawalletdServer interface {
Send(context.Context, *SendRequest) (*SendResponse, error)
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
Sign(context.Context, *SignRequest) (*SignResponse, error)
GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error)
mustEmbedUnimplementedKaspawalletdServer()
}
@@ -184,9 +173,6 @@ func (UnimplementedKaspawalletdServer) Send(context.Context, *SendRequest) (*Sen
func (UnimplementedKaspawalletdServer) Sign(context.Context, *SignRequest) (*SignResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Sign not implemented")
}
func (UnimplementedKaspawalletdServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
}
func (UnimplementedKaspawalletdServer) mustEmbedUnimplementedKaspawalletdServer() {}
// UnsafeKaspawalletdServer may be embedded to opt out of forward compatibility for this service.
@@ -362,24 +348,6 @@ func _Kaspawalletd_Sign_Handler(srv interface{}, ctx context.Context, dec func(i
return interceptor(ctx, in, info, handler)
}
func _Kaspawalletd_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetVersionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(KaspawalletdServer).GetVersion(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/kaspawalletd.kaspawalletd/GetVersion",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(KaspawalletdServer).GetVersion(ctx, req.(*GetVersionRequest))
}
return interceptor(ctx, in, info, handler)
}
// Kaspawalletd_ServiceDesc is the grpc.ServiceDesc for Kaspawalletd service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -423,10 +391,6 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{
MethodName: "Sign",
Handler: _Kaspawalletd_Sign_Handler,
},
{
MethodName: "GetVersion",
Handler: _Kaspawalletd_GetVersion_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "kaspawalletd.proto",

View File

@@ -10,33 +10,22 @@ import (
"github.com/pkg/errors"
)
func (s *server) changeAddress(useExisting bool, fromAddresses []*walletAddress) (util.Address, *walletAddress, error) {
var walletAddr *walletAddress
if len(fromAddresses) != 0 && useExisting {
walletAddr = fromAddresses[0]
} else {
internalIndex := uint32(0)
if !useExisting {
err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1)
if err != nil {
return nil, nil, err
}
err = s.keysFile.Save()
if err != nil {
return nil, nil, err
}
internalIndex = s.keysFile.LastUsedInternalIndex()
}
walletAddr = &walletAddress{
index: internalIndex,
cosignerIndex: s.keysFile.CosignerIndex,
keyChain: libkaspawallet.InternalKeychain,
}
func (s *server) changeAddress() (util.Address, *walletAddress, error) {
err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1)
if err != nil {
return nil, nil, err
}
err = s.keysFile.Save()
if err != nil {
return nil, nil, err
}
walletAddr := &walletAddress{
index: s.keysFile.LastUsedInternalIndex(),
cosignerIndex: s.keysFile.CosignerIndex,
keyChain: libkaspawallet.InternalKeychain,
}
path := s.walletAddressPath(walletAddr)
address, err := libkaspawallet.Address(s.params, s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, path, s.keysFile.ECDSA)
if err != nil {

View File

@@ -2,7 +2,6 @@ package server
import (
"context"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
@@ -15,15 +14,13 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
s.lock.RLock()
defer s.lock.RUnlock()
if !s.isSynced() {
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
}
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
if err != nil {
return nil, err
}
daaScore := dagInfo.VirtualDAAScore
maturity := s.params.BlockCoinbaseMaturity
balancesMap := make(balancesMapType, 0)
for _, entry := range s.utxosSortedByAmount {
amount := entry.UTXOEntry.Amount()
@@ -33,7 +30,7 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
balances = new(balancesType)
balancesMap[address] = balances
}
if s.isUTXOSpendable(entry, daaScore) {
if isUTXOSpendable(entry, daaScore, maturity) {
balances.available += amount
} else {
balances.pending += amount
@@ -58,8 +55,6 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
pending += balances.pending
}
log.Infof("GetBalance request scanned %d UTXOs overall over %d addresses", len(s.utxosSortedByAmount), len(balancesMap))
return &pb.GetBalanceResponse{
Available: available,
Pending: pending,
@@ -67,9 +62,9 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
}, nil
}
func (s *server) isUTXOSpendable(entry *walletUTXO, virtualDAAScore uint64) bool {
func isUTXOSpendable(entry *walletUTXO, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
if !entry.UTXOEntry.IsCoinbase() {
return true
}
return entry.UTXOEntry.BlockDAAScore()+s.coinbaseMaturity < virtualDAAScore
return entry.UTXOEntry.BlockDAAScore()+coinbaseMaturity < virtualDAAScore
}

View File

@@ -2,16 +2,14 @@ package server
import (
"context"
"time"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/pkg/errors"
"time"
)
func (s *server) Broadcast(_ context.Context, request *pb.BroadcastRequest) (*pb.BroadcastResponse, error) {
@@ -56,12 +54,16 @@ func (s *server) broadcast(transactions [][]byte, isDomain bool) ([]string, erro
}
}
s.forceSync()
err = s.refreshUTXOs()
if err != nil {
return nil, err
}
return txIDs, nil
}
func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx), consensushashing.TransactionID(tx).String(), false)
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx), false)
if err != nil {
return "", errors.Wrapf(err, "error submitting transaction")
}

View File

@@ -3,30 +3,24 @@ package server
import (
"context"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"golang.org/x/exp/slices"
"time"
)
// TODO: Implement a better fee estimation mechanism
const feePerInput = 10000
// The minimal change amount to target in order to avoid large storage mass (see KIP9 for more details).
// By having at least 0.2KAS in the change output we make sure that every transaction with send value >= 0.2KAS
// should succeed (at most 50K storage mass for each output, thus overall lower than standard mass upper bound which is 100K gram)
const minChangeTarget = constants.SompiPerKaspa / 5
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
*pb.CreateUnsignedTransactionsResponse, error,
) {
*pb.CreateUnsignedTransactionsResponse, error) {
s.lock.Lock()
defer s.lock.Unlock()
unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.IsSendAll,
request.From, request.UseExistingChangeAddress)
unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.From)
if err != nil {
return nil, err
}
@@ -34,12 +28,16 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
}
func (s *server) createUnsignedTransactions(address string, amount uint64, isSendAll bool, fromAddressesString []string, useExistingChangeAddress bool) ([][]byte, error) {
func (s *server) createUnsignedTransactions(address string, amount uint64, fromAddressesString []string) ([][]byte, error) {
if !s.isSynced() {
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
}
// make sure address string is correct before proceeding to a
// potentially long UTXO refreshment operation
err := s.refreshUTXOs()
if err != nil {
return nil, err
}
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
if err != nil {
return nil, err
@@ -49,28 +47,24 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
for _, from := range fromAddressesString {
fromAddress, exists := s.addressSet[from]
if !exists {
return nil, fmt.Errorf("specified from address %s does not exists", from)
return nil, fmt.Errorf("Specified from address %s does not exists", from)
}
fromAddresses = append(fromAddresses, fromAddress)
}
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOs(amount, isSendAll, feePerInput, fromAddresses)
selectedUTXOs, changeSompi, err := s.selectUTXOs(amount, feePerInput, fromAddresses)
if err != nil {
return nil, err
}
if len(selectedUTXOs) == 0 {
return nil, errors.Errorf("couldn't find funds to spend")
}
changeAddress, changeWalletAddress, err := s.changeAddress(useExistingChangeAddress, fromAddresses)
changeAddress, changeWalletAddress, err := s.changeAddress()
if err != nil {
return nil, err
}
payments := []*libkaspawallet.Payment{{
Address: toAddress,
Amount: spendValue,
Amount: amount,
}}
if changeSompi > 0 {
payments = append(payments, &libkaspawallet.Payment{
@@ -92,25 +86,25 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
return unsignedTransactions, nil
}
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uint64, fromAddresses []*walletAddress) (
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64, fromAddresses []*walletAddress) (
selectedUTXOs []*libkaspawallet.UTXO, changeSompi uint64, err error) {
selectedUTXOs = []*libkaspawallet.UTXO{}
totalValue := uint64(0)
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
if err != nil {
return nil, 0, 0, err
return nil, 0, err
}
for _, utxo := range s.utxosSortedByAmount {
if (fromAddresses != nil && !walletAddressesContain(fromAddresses, utxo.address)) ||
!s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
if (fromAddresses != nil && !slices.Contains(fromAddresses, utxo.address)) ||
!isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
continue
}
if broadcastTime, ok := s.usedOutpoints[*utxo.Outpoint]; ok {
if s.usedOutpointHasExpired(broadcastTime) {
if time.Since(broadcastTime) > time.Minute {
delete(s.usedOutpoints, *utxo.Outpoint)
} else {
continue
@@ -122,52 +116,21 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
UTXOEntry: utxo.UTXOEntry,
DerivationPath: s.walletAddressPath(utxo.address),
})
totalValue += utxo.UTXOEntry.Amount()
fee := feePerInput * uint64(len(selectedUTXOs))
if fee < s.minFeePerTx {
fee = s.minFeePerTx
}
totalSpend := spendAmount + fee
// Two break cases (if not send all):
// 1. totalValue == totalSpend, so there's no change needed -> number of outputs = 1, so a single input is sufficient
// 2. totalValue > totalSpend, so there will be change and 2 outputs, therefor in order to not struggle with --
// 2.1 go-nodes dust patch we try and find at least 2 inputs (even though the next one is not necessary in terms of spend value)
// 2.2 KIP9 we try and make sure that the change amount is not too small
if !isSendAll && (totalValue == totalSpend || (totalValue >= totalSpend+minChangeTarget && len(selectedUTXOs) > 1)) {
if totalValue >= totalSpend {
break
}
}
fee := feePerInput * uint64(len(selectedUTXOs))
if fee < s.minFeePerTx {
fee = s.minFeePerTx
}
var totalSpend uint64
if isSendAll {
totalSpend = totalValue
totalReceived = totalValue - fee
} else {
totalSpend = spendAmount + fee
totalReceived = spendAmount
}
totalSpend := spendAmount + fee
if totalValue < totalSpend {
return nil, 0, 0, errors.Errorf("Insufficient funds for send: %f required, while only %f available",
return nil, 0, errors.Errorf("Insufficient funds for send: %f required, while only %f available",
float64(totalSpend)/constants.SompiPerKaspa, float64(totalValue)/constants.SompiPerKaspa)
}
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
}
func walletAddressesContain(addresses []*walletAddress, contain *walletAddress) bool {
for _, address := range addresses {
if *address == *contain {
return true
}
}
return false
return selectedUTXOs, totalValue - totalSpend, nil
}

View File

@@ -1,26 +1,15 @@
package server
import (
"time"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
)
func connectToRPC(params *dagconfig.Params, rpcServer string, timeout uint32) (*rpcclient.RPCClient, error) {
func connectToRPC(params *dagconfig.Params, rpcServer string) (*rpcclient.RPCClient, error) {
rpcAddress, err := params.NormalizeRPCServerAddress(rpcServer)
if err != nil {
return nil, err
}
rpcClient, err := rpcclient.NewRPCClient(rpcAddress)
if err != nil {
return nil, err
}
if timeout != 0 {
rpcClient.SetTimeout(time.Duration(timeout) * time.Second)
}
return rpcClient, err
return rpcclient.NewRPCClient(rpcAddress)
}

View File

@@ -10,9 +10,7 @@ func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendRespo
s.lock.Lock()
defer s.lock.Unlock()
unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.IsSendAll,
request.From, request.UseExistingChangeAddress)
unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.From)
if err != nil {
return nil, err
}
@@ -27,5 +25,5 @@ func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendRespo
return nil, err
}
return &pb.SendResponse{TxIDs: txIDs, SignedTransactions: signedTransactions}, nil
return &pb.SendResponse{TxIDs: txIDs}, nil
}

View File

@@ -5,11 +5,8 @@ import (
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/version"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/txmass"
@@ -30,35 +27,25 @@ import (
type server struct {
pb.UnimplementedKaspawalletdServer
rpcClient *rpcclient.RPCClient // RPC client for ongoing user requests
backgroundRPCClient *rpcclient.RPCClient // RPC client dedicated for address and UTXO background fetching
params *dagconfig.Params
coinbaseMaturity uint64 // Is different from default if we use testnet-11
minFeePerTx uint64
rpcClient *rpcclient.RPCClient
params *dagconfig.Params
lock sync.RWMutex
utxosSortedByAmount []*walletUTXO
nextSyncStartIndex uint32
keysFile *keys.File
shutdown chan struct{}
forceSyncChan chan struct{}
startTimeOfLastCompletedRefresh time.Time
addressSet walletAddressSet
txMassCalculator *txmass.Calculator
usedOutpoints map[externalapi.DomainOutpoint]time.Time
firstSyncDone atomic.Bool
lock sync.RWMutex
utxosSortedByAmount []*walletUTXO
nextSyncStartIndex uint32
keysFile *keys.File
shutdown chan struct{}
addressSet walletAddressSet
txMassCalculator *txmass.Calculator
usedOutpoints map[externalapi.DomainOutpoint]time.Time
isLogFinalProgressLineShown bool
maxUsedAddressesForLog uint32
maxProcessedAddressesForLog uint32
}
// MaxDaemonSendMsgSize is the max send message size used for the daemon server.
// Currently, set to 100MB
const MaxDaemonSendMsgSize = 100_000_000
// Start starts the kaspawalletd server
func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string, timeout uint32, minFeePerTx uint64) error {
func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string) error {
initLog(defaultLogFile, defaultErrLogFile)
defer panics.HandlePanic(log, "MAIN", nil)
@@ -68,7 +55,6 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
profiling.Start(profile, log)
}
log.Infof("Version %s", version.Version())
listener, err := net.Listen("tcp", listen)
if err != nil {
return (errors.Wrapf(err, "Error listening to TCP on %s", listen))
@@ -76,14 +62,10 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
log.Infof("Listening to TCP on %s", listen)
log.Infof("Connecting to a node at %s...", rpcServer)
rpcClient, err := connectToRPC(params, rpcServer, timeout)
rpcClient, err := connectToRPC(params, rpcServer)
if err != nil {
return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer))
}
backgroundRPCClient, err := connectToRPC(params, rpcServer, timeout)
if err != nil {
return (errors.Wrapf(err, "Error making a second connection to RPC server %s", rpcServer))
}
log.Infof("Connected, reading keys file %s...", keysFilePath)
keysFile, err := keys.ReadKeysFile(params, keysFilePath)
@@ -91,32 +73,13 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
return (errors.Wrapf(err, "Error reading keys file %s", keysFilePath))
}
err = keysFile.TryLock()
if err != nil {
return err
}
dagInfo, err := rpcClient.GetBlockDAGInfo()
if err != nil {
return nil
}
coinbaseMaturity := params.BlockCoinbaseMaturity
if dagInfo.NetworkName == "kaspa-testnet-11" {
coinbaseMaturity = 1000
}
serverInstance := &server{
rpcClient: rpcClient,
backgroundRPCClient: backgroundRPCClient,
params: params,
coinbaseMaturity: coinbaseMaturity,
minFeePerTx: minFeePerTx,
utxosSortedByAmount: []*walletUTXO{},
nextSyncStartIndex: 0,
keysFile: keysFile,
shutdown: make(chan struct{}),
forceSyncChan: make(chan struct{}),
addressSet: make(walletAddressSet),
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
usedOutpoints: map[externalapi.DomainOutpoint]time.Time{},
@@ -126,14 +89,14 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
}
log.Infof("Read, syncing the wallet...")
spawn("serverInstance.syncLoop", func() {
err := serverInstance.syncLoop()
spawn("serverInstance.sync", func() {
err := serverInstance.sync()
if err != nil {
printErrorAndExit(errors.Wrap(err, "error syncing the wallet"))
}
})
grpcServer := grpc.NewServer(grpc.MaxSendMsgSize(MaxDaemonSendMsgSize))
grpcServer := grpc.NewServer()
pb.RegisterKaspawalletdServer(grpcServer, serverInstance)
spawn("grpcServer.Serve", func() {

View File

@@ -264,7 +264,7 @@ func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawa
if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
continue
}
if !s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
continue
}
additionalUTXOs = append(additionalUTXOs, &libkaspawallet.UTXO{

View File

@@ -121,7 +121,7 @@ func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *c
t.Fatalf("AddBlock: %+v", err)
}
block1, _, err := tc.GetBlock(block1Hash)
block1, err := tc.GetBlock(block1Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}

View File

@@ -23,7 +23,7 @@ func (was walletAddressSet) strings() []string {
return addresses
}
func (s *server) syncLoop() error {
func (s *server) sync() error {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
@@ -32,39 +32,29 @@ func (s *server) syncLoop() error {
return err
}
err = s.refreshUTXOs()
err = s.refreshExistingUTXOsWithLock()
if err != nil {
return err
}
s.firstSyncDone.Store(true)
log.Infof("Wallet is synced and ready for operation")
for {
select {
case <-ticker.C:
case <-s.forceSyncChan:
for range ticker.C {
err = s.collectFarAddresses()
if err != nil {
return err
}
err := s.sync()
err = s.collectRecentAddresses()
if err != nil {
return err
}
err = s.refreshExistingUTXOsWithLock()
if err != nil {
return err
}
}
}
func (s *server) sync() error {
err := s.collectFarAddresses()
if err != nil {
return err
}
err = s.collectRecentAddresses()
if err != nil {
return err
}
return s.refreshUTXOs()
return nil
}
const (
@@ -168,7 +158,7 @@ func (s *server) collectAddresses(start, end uint32) error {
return err
}
getBalancesByAddressesResponse, err := s.backgroundRPCClient.GetBalancesByAddresses(addressSet.strings())
getBalancesByAddressesResponse, err := s.rpcClient.GetBalancesByAddresses(addressSet.strings())
if err != nil {
return err
}
@@ -218,17 +208,15 @@ func (s *server) updateAddressesAndLastUsedIndexes(requestedAddressSet walletAdd
return s.keysFile.SetLastUsedInternalIndex(lastUsedInternalIndex)
}
func (s *server) usedOutpointHasExpired(outpointBroadcastTime time.Time) bool {
// If the node returns a UTXO we previously attempted to spend and enough time has passed, we assume
// that the network rejected or lost the previous transaction and allow a reuse. We set this time
// interval to a minute.
// We also verify that a full refresh UTXO operation started after this time point and has already
// completed, in order to make sure that indeed this state reflects a state obtained following the required wait time.
return s.startTimeOfLastCompletedRefresh.After(outpointBroadcastTime.Add(time.Minute))
func (s *server) refreshExistingUTXOsWithLock() error {
s.lock.Lock()
defer s.lock.Unlock()
return s.refreshUTXOs()
}
// updateUTXOSet clears the current UTXO set, and re-fills it with the given entries
func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, mempoolEntries []*appmessage.MempoolEntryByAddress, refreshStart time.Time) error {
func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, mempoolEntries []*appmessage.MempoolEntryByAddress) error {
utxos := make([]*walletUTXO, 0, len(entries))
exclude := make(map[appmessage.RPCOutpoint]struct{})
@@ -255,7 +243,6 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
return err
}
// No need to lock for reading since the only writer of this set is on `syncLoop` on the same goroutine.
address, ok := s.addressSet[entry.Address]
if !ok {
return errors.Errorf("Got result from address %s even though it wasn't requested", entry.Address)
@@ -269,56 +256,32 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
sort.Slice(utxos, func(i, j int) bool { return utxos[i].UTXOEntry.Amount() > utxos[j].UTXOEntry.Amount() })
s.lock.Lock()
s.startTimeOfLastCompletedRefresh = refreshStart
s.utxosSortedByAmount = utxos
// Cleanup expired used outpoints to avoid a memory leak
for outpoint, broadcastTime := range s.usedOutpoints {
if s.usedOutpointHasExpired(broadcastTime) {
delete(s.usedOutpoints, outpoint)
}
}
s.lock.Unlock()
return nil
}
func (s *server) refreshUTXOs() error {
refreshStart := time.Now()
// No need to lock for reading since the only writer of this set is on `syncLoop` on the same goroutine.
addresses := s.addressSet.strings()
// It's important to check the mempool before calling `GetUTXOsByAddresses`:
// If we would do it the other way around an output can be spent in the mempool
// and not in consensus, and between the calls its spending transaction will be
// added to consensus and removed from the mempool, so `getUTXOsByAddressesResponse`
// will include an obsolete output.
mempoolEntriesByAddresses, err := s.backgroundRPCClient.GetMempoolEntriesByAddresses(addresses, true, true)
mempoolEntriesByAddresses, err := s.rpcClient.GetMempoolEntriesByAddresses(s.addressSet.strings(), true, true)
if err != nil {
return err
}
getUTXOsByAddressesResponse, err := s.backgroundRPCClient.GetUTXOsByAddresses(addresses)
getUTXOsByAddressesResponse, err := s.rpcClient.GetUTXOsByAddresses(s.addressSet.strings())
if err != nil {
return err
}
return s.updateUTXOSet(getUTXOsByAddressesResponse.Entries, mempoolEntriesByAddresses.Entries, refreshStart)
}
func (s *server) forceSync() {
// Technically if two callers check the `if` simultaneously they will both spawn a
// goroutine, but we don't care about the small redundancy in such a rare case.
if len(s.forceSyncChan) == 0 {
go func() {
s.forceSyncChan <- struct{}{}
}()
}
return s.updateUTXOSet(getUTXOsByAddressesResponse.Entries, mempoolEntriesByAddresses.Entries)
}
func (s *server) isSynced() bool {
return s.nextSyncStartIndex > s.maxUsedIndex() && s.firstSyncDone.Load()
return s.nextSyncStartIndex > s.maxUsedIndex()
}
func (s *server) formatSyncStateReport() string {
@@ -328,11 +291,8 @@ func (s *server) formatSyncStateReport() string {
maxUsedIndex = s.nextSyncStartIndex
}
if s.nextSyncStartIndex < s.maxUsedIndex() {
return fmt.Sprintf("scanned %d out of %d addresses (%.2f%%)",
s.nextSyncStartIndex, maxUsedIndex, float64(s.nextSyncStartIndex)*100.0/float64(maxUsedIndex))
}
return "loading the wallet UTXO set"
return fmt.Sprintf("scanned %d out of %d addresses (%.2f%%)",
s.nextSyncStartIndex, maxUsedIndex, float64(s.nextSyncStartIndex)*100.0/float64(maxUsedIndex))
}
func (s *server) updateSyncingProgressLog(currProcessedAddresses, currMaxUsedAddresses uint32) {
@@ -351,7 +311,7 @@ func (s *server) updateSyncingProgressLog(currProcessedAddresses, currMaxUsedAdd
if s.maxProcessedAddressesForLog >= s.maxUsedAddressesForLog {
if !s.isLogFinalProgressLineShown {
log.Infof("Finished scanning recent addresses")
log.Infof("Wallet is synced, ready for queries")
s.isLogFinalProgressLineShown = true
}
} else {

View File

@@ -1,16 +0,0 @@
package server
import (
"context"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/version"
)
func (s *server) GetVersion(_ context.Context, _ *pb.GetVersionRequest) (*pb.GetVersionResponse, error) {
s.lock.RLock()
defer s.lock.RUnlock()
return &pb.GetVersionResponse{
Version: version.Version(),
}, nil
}

View File

@@ -1,29 +0,0 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.18-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
WORKDIR /go/src/github.com/kaspanet/kaspad
RUN apk add --no-cache curl git openssh binutils gcc musl-dev
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
WORKDIR /go/src/github.com/kaspanet/kaspad/cmd/kaspawallet
RUN GOOS=linux go build -a -installsuffix cgo -o kaspawallet .
# --- multistage docker build: stage #2: runtime image
FROM alpine
WORKDIR /app
RUN apk add --no-cache ca-certificates tini
COPY --from=build /go/src/github.com/kaspanet/kaspad/cmd/kaspawallet/kaspawallet /app/
USER nobody
ENTRYPOINT [ "/sbin/tini", "--" ]

View File

@@ -1,26 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
)
func getDaemonVersion(conf *getDaemonVersionConfig) error {
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
if err != nil {
return err
}
defer tearDown()
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
defer cancel()
response, err := daemonClient.GetVersion(ctx, &pb.GetVersionRequest{})
if err != nil {
return err
}
fmt.Println(response.Version)
return nil
}

View File

@@ -6,7 +6,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/gofrs/flock"
"os"
"path/filepath"
"runtime"
@@ -401,33 +400,3 @@ func decryptMnemonic(numThreads uint8, encryptedPrivateKey *EncryptedMnemonic, p
return string(decrypted), nil
}
// flockMap is a map that holds all lock file handlers. This map guarantees that
// the associated locked file handler will never get cleaned by the GC, because
// once they are cleaned the associated file will be unlocked.
var flockMap = make(map[string]*flock.Flock)
// TryLock tries to acquire an exclusive lock for the file.
func (d *File) TryLock() error {
if _, ok := flockMap[d.path]; ok {
return errors.Errorf("file %s is already locked", d.path)
}
lockFile := flock.New(d.path + ".lock")
err := createFileDirectoryIfDoesntExist(lockFile.Path())
if err != nil {
return err
}
flockMap[d.path] = lockFile
success, err := lockFile.TryLock()
if err != nil {
return err
}
if !success {
return errors.Errorf("%s is locked and cannot be used. Make sure that no other active wallet command is using it.", d.path)
}
return nil
}

View File

@@ -6,7 +6,7 @@
Package base58 provides an API for working with modified base58 and Base58Check
encodings.
# Modified Base58 Encoding
Modified Base58 Encoding
Standard base58 encoding is similar to standard base64 encoding except, as the
name implies, it uses a 58 character alphabet which results in an alphanumeric
@@ -17,7 +17,7 @@ The modified base58 alphabet used by Bitcoin, and hence this package, omits the
0, O, I, and l characters that look the same in many fonts and are therefore
hard to humans to distinguish.
# Base58Check Encoding Scheme
Base58Check Encoding Scheme
The Base58Check encoding scheme is primarily used for Bitcoin addresses at the
time of this writing, however it can be used to generically encode arbitrary

View File

@@ -10,7 +10,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
)
// KaspawalletdUTXOsTolibkaspawalletUTXOs converts a []*pb.UtxosByAddressesEntry to a []*libkaspawallet.UTXO
//KaspawalletdUTXOsTolibkaspawalletUTXOs converts a []*pb.UtxosByAddressesEntry to a []*libkaspawallet.UTXO
func KaspawalletdUTXOsTolibkaspawalletUTXOs(kaspawalletdUtxoEntires []*pb.UtxosByAddressesEntry) ([]*UTXO, error) {
UTXOs := make([]*UTXO, len(kaspawalletdUtxoEntires))
for i, entry := range kaspawalletdUtxoEntires {

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.21.12
// protoc-gen-go v1.28.0
// protoc v3.17.2
// source: wallet.proto
package protoserialization

View File

@@ -88,7 +88,7 @@ func SerializePartiallySignedTransaction(partiallySignedTransaction *PartiallySi
return proto.Marshal(partiallySignedTransactionToProto(partiallySignedTransaction))
}
// DeserializeDomainTransaction Deserialize a Transaction to an *externalapi.DomainTransaction
//DeserializeDomainTransaction Deserialize a Transaction to an *externalapi.DomainTransaction
func DeserializeDomainTransaction(serializedTransactionMessage []byte) (*externalapi.DomainTransaction, error) {
protoTransactionMessage := &protoserialization.TransactionMessage{}
err := proto.Unmarshal(serializedTransactionMessage, protoTransactionMessage)

View File

@@ -84,7 +84,7 @@ func TestMultisig(t *testing.T) {
t.Fatalf("AddBlock: %+v", err)
}
block1, _, err := tc.GetBlock(block1Hash)
block1, err := tc.GetBlock(block1Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
@@ -245,7 +245,7 @@ func TestP2PK(t *testing.T) {
t.Fatalf("AddBlock: %+v", err)
}
block1, _, err := tc.GetBlock(block1Hash)
block1, err := tc.GetBlock(block1Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
@@ -377,17 +377,17 @@ func TestMaxSompi(t *testing.T) {
t.Fatalf("AddBlock: %+v", err)
}
fundingBlock2, _, err := tc.GetBlock(fundingBlock2Hash)
fundingBlock2, err := tc.GetBlock(fundingBlock2Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
fundingBlock3, _, err := tc.GetBlock(fundingBlock3Hash)
fundingBlock3, err := tc.GetBlock(fundingBlock3Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
fundingBlock4, _, err := tc.GetBlock(fundingBlock4Hash)
fundingBlock4, err := tc.GetBlock(fundingBlock4Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
@@ -397,7 +397,7 @@ func TestMaxSompi(t *testing.T) {
t.Fatalf("AddBlock: %+v", err)
}
block1, _, err := tc.GetBlock(block1Hash)
block1, err := tc.GetBlock(block1Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}

View File

@@ -1,11 +1,10 @@
package main
import (
"github.com/pkg/errors"
)
import "github.com/pkg/errors"
func main() {
subCmd, config := parseCommandLine()
var err error
switch subCmd {
case createSubCmd:
@@ -32,10 +31,6 @@ func main() {
err = startDaemon(config.(*startDaemonConfig))
case sweepSubCmd:
err = sweep(config.(*sweepConfig))
case versionSubCmd:
showVersion()
case getDaemonVersionSubCmd:
err = getDaemonVersion(config.(*getDaemonVersionConfig))
default:
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
}

View File

@@ -3,14 +3,12 @@ package main
import (
"context"
"fmt"
"os"
"strings"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/pkg/errors"
)
@@ -33,22 +31,13 @@ func send(conf *sendConfig) error {
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
defer cancel()
var sendAmountSompi uint64
if !conf.IsSendAll {
sendAmountSompi, err = utils.KasToSompi(conf.SendAmount)
if err != nil {
return err
}
}
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
createUnsignedTransactionsResponse, err :=
daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
From: conf.FromAddresses,
Address: conf.ToAddress,
Amount: sendAmountSompi,
IsSendAll: conf.IsSendAll,
UseExistingChangeAddress: conf.UseExistingChangeAddress,
From: conf.FromAddresses,
Address: conf.ToAddress,
Amount: sendAmountSompi,
})
if err != nil {
return err
@@ -59,10 +48,6 @@ func send(conf *sendConfig) error {
}
mnemonics, err := keysFile.DecryptMnemonics(conf.Password)
if err != nil {
if strings.Contains(err.Error(), "message authentication failed") {
fmt.Fprintf(os.Stderr, "Password decryption failed. Sometimes this is a result of not "+
"specifying the same keys file used by the wallet daemon process.\n")
}
return err
}
@@ -75,36 +60,18 @@ func send(conf *sendConfig) error {
signedTransactions[i] = signedTransaction
}
fmt.Printf("Broadcasting %d transaction(s)\n", len(signedTransactions))
// Since we waited for user input when getting the password, which could take unbound amount of time -
// create a new context for broadcast, to reset the timeout.
broadcastCtx, broadcastCancel := context.WithTimeout(context.Background(), daemonTimeout)
defer broadcastCancel()
const chunkSize = 100 // To avoid sending a message bigger than the gRPC max message size, we split it to chunks
for offset := 0; offset < len(signedTransactions); offset += chunkSize {
end := len(signedTransactions)
if offset+chunkSize <= len(signedTransactions) {
end = offset + chunkSize
}
chunk := signedTransactions[offset:end]
response, err := daemonClient.Broadcast(broadcastCtx, &pb.BroadcastRequest{Transactions: chunk})
if err != nil {
return err
}
fmt.Printf("Broadcasted %d transaction(s) (broadcasted %.2f%% of the transactions so far)\n", len(chunk), 100*float64(end)/float64(len(signedTransactions)))
fmt.Println("Broadcasted Transaction ID(s): ")
for _, txID := range response.TxIDs {
fmt.Printf("\t%s\n", txID)
}
if len(signedTransactions) > 1 {
fmt.Printf("Broadcasting %d transactions\n", len(signedTransactions))
}
if conf.Verbose {
fmt.Println("Serialized Transaction(s) (can be parsed via the `parse` command or resent via `broadcast`): ")
for _, signedTx := range signedTransactions {
fmt.Printf("\t%x\n\n", signedTx)
}
response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transactions: signedTransactions})
if err != nil {
return err
}
fmt.Println("Transactions were sent successfully")
fmt.Println("Transaction ID(s): ")
for _, txID := range response.TxIDs {
fmt.Printf("\t%s\n", txID)
}
return nil

View File

@@ -26,8 +26,5 @@ func showAddresses(conf *showAddressesConfig) error {
for _, address := range response.Address {
fmt.Println(address)
}
fmt.Printf("\nNote: the above are only addresses that were manually created by the 'new-address' command. If you want to see a list of all addresses, including change addresses, " +
"that have a positive balance, use the command 'balance -v'\n")
return nil
}

View File

@@ -3,5 +3,5 @@ package main
import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
func startDaemon(conf *startDaemonConfig) error {
return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile, conf.Timeout, conf.MinFeePerTx)
return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile)
}

View File

@@ -2,13 +2,8 @@ package utils
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/pkg/errors"
)
// FormatKas takes the amount of sompis as uint64, and returns amount of KAS with 8 decimal places
@@ -19,50 +14,3 @@ func FormatKas(amount uint64) string {
}
return res
}
// KasToSompi takes in a string representation of the Kas value to convert to Sompi
func KasToSompi(amount string) (uint64, error) {
err := validateKASAmountFormat(amount)
if err != nil {
return 0, err
}
// after validation, amount can only be either an int OR
// a float with an int component and decimal places
parts := strings.Split(amount, ".")
amountStr := ""
if constants.SompiPerKaspa%10 != 0 {
return 0, errors.Errorf("Unable to convert to sompi when SompiPerKaspa is not a multiple of 10")
}
decimalPlaces := int(math.Log10(constants.SompiPerKaspa))
decimalStr := ""
if len(parts) == 2 {
decimalStr = parts[1]
}
amountStr = fmt.Sprintf("%s%-*s", parts[0], decimalPlaces, decimalStr) // Padded with spaces at the end to fill for missing decimals: Sample "0.01234 "
amountStr = strings.ReplaceAll(amountStr, " ", "0") // Make the spaces be 0s. Sample "0.012340000"
convertedAmount, err := strconv.ParseUint(amountStr, 10, 64)
return convertedAmount, err
}
func validateKASAmountFormat(amount string) error {
// Check whether it's an integer, or a float with max 8 digits
match, err := regexp.MatchString("^([1-9]\\d{0,11}|0)(\\.\\d{0,8})?$", amount)
if !match {
return errors.Errorf("Invalid amount")
}
if err != nil {
return err
}
return nil
}

View File

@@ -1,90 +0,0 @@
package utils
import "testing"
// Takes in a string representation of the Kas value to convert to Sompi
func TestKasToSompi(t *testing.T) {
type testVector struct {
originalAmount string
convertedAmount uint64
}
validCases := []testVector{
{originalAmount: "0", convertedAmount: 0},
{originalAmount: "1", convertedAmount: 100000000},
{originalAmount: "33184.1489732", convertedAmount: 3318414897320},
{originalAmount: "21.35808032", convertedAmount: 2135808032},
{originalAmount: "184467440737.09551615", convertedAmount: 18446744073709551615},
}
for _, currentTestVector := range validCases {
convertedAmount, err := KasToSompi(currentTestVector.originalAmount)
if err != nil {
t.Error(err)
} else if convertedAmount != currentTestVector.convertedAmount {
t.Errorf("Expected %s, to convert to %d. Got: %d", currentTestVector.originalAmount, currentTestVector.convertedAmount, convertedAmount)
}
}
invalidCases := []string{
"184467440737.09551616", // Bigger than max uint64
"-1",
"a",
"",
}
for _, currentTestVector := range invalidCases {
_, err := KasToSompi(currentTestVector)
if err == nil {
t.Errorf("Expected an error but succeeded validation for test case %s", currentTestVector)
}
}
}
func TestValidateAmountFormat(t *testing.T) {
validCases := []string{
"0",
"1",
"1.0",
"0.1",
"0.12345678",
"111111111111.11111111", // 12 digits to the left of decimal, 8 digits to the right
"184467440737.09551615", // Maximum input that can be represented in sompi later
"184467440737.09551616", // Cannot be represented in sompi, but we'll acccept for "correct format"
"999999999999.99999999", // Cannot be represented in sompi, but we'll acccept for "correct format"
}
for _, testCase := range validCases {
err := validateKASAmountFormat(testCase)
if err != nil {
t.Error(err)
}
}
invalidCases := []string{
"",
"a",
"-1",
"0.123456789", // 9 decimal digits
".1", // decimal but no integer component
"0a", // Extra character
"0000000000000", // 13 zeros
"012", // Int padded with zero
"00.1", // Decimal padded with zeros
"111111111111111111111", // all digits
"111111111111A11111111", // non-period/non-digit where decimal would be
"000000000000.00000000", // all zeros
"kaspa", // all text
}
for _, testCase := range invalidCases {
err := validateKASAmountFormat(testCase)
if err == nil {
t.Errorf("Expected an error but succeeded validation for test case %s", testCase)
}
}
}

View File

@@ -1,15 +0,0 @@
package main
import (
"fmt"
"github.com/kaspanet/kaspad/version"
"os"
"path/filepath"
"strings"
)
func showVersion() {
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
fmt.Println(appName, "version", version.Version())
}

6
doc.go
View File

@@ -13,12 +13,10 @@ the box' for most users. However, there are also a wide variety of flags that
can be used to control it.
Usage:
kaspad [OPTIONS]
kaspad [OPTIONS]
For an up-to-date help message:
kaspad --help
kaspad --help
The long form of all option flags (except -C) can be specified in a configuration
file that is automatically parsed when kaspad starts up. By default, the

View File

@@ -1,5 +1,5 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.19-alpine AS build
FROM golang:1.18-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
@@ -10,13 +10,19 @@ RUN apk add --no-cache curl git openssh binutils gcc musl-dev
COPY go.mod .
COPY go.sum .
RUN go get -u golang.org/x/lint/golint \
github.com/kisielk/errcheck \
github.com/opennota/check/cmd/aligncheck \
github.com/opennota/check/cmd/structcheck \
github.com/opennota/check/cmd/varcheck \
honnef.co/go/tools/cmd/staticcheck
# Cache kaspad dependencies
RUN go mod download
COPY . .
RUN go build $FLAGS -o kaspad .
RUN ./build_and_test.sh
# --- multistage docker build: stage #2: runtime image
FROM alpine

View File

@@ -64,12 +64,6 @@ type consensus struct {
virtualNotUpdated bool
}
// In order to prevent a situation that the consensus lock is held for too much time, we
// release the lock each time we resolve 100 blocks.
// Note: `virtualResolveChunk` should be smaller than `params.FinalityDuration` in order to avoid a situation
// where UpdatePruningPointByVirtual skips a pruning point.
const virtualResolveChunk = 100
func (s *consensus) ValidateAndInsertBlockWithTrustedData(block *externalapi.BlockWithTrustedData, validateUTXO bool) error {
s.lock.Lock()
defer s.lock.Unlock()
@@ -204,32 +198,15 @@ func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, updat
if updateVirtual {
s.lock.Lock()
if s.virtualNotUpdated {
// We enter the loop in locked state
for {
_, isCompletelyResolved, err := s.resolveVirtualChunkNoLock(virtualResolveChunk)
if err != nil {
s.lock.Unlock()
return err
}
if isCompletelyResolved {
// Make sure we enter the block insertion function w/o releasing the lock.
// Otherwise, we might actually enter it in `s.virtualNotUpdated == true` state
_, err = s.validateAndInsertBlockNoLock(block, updateVirtual)
// Finally, unlock for the last iteration and return
s.lock.Unlock()
if err != nil {
return err
}
return nil
}
// Unlock to allow other threads to enter consensus
s.lock.Unlock()
// Lock for the next iteration
s.lock.Lock()
s.lock.Unlock()
err := s.ResolveVirtual(nil)
if err != nil {
return err
}
return s.validateAndInsertBlockWithLock(block, updateVirtual)
}
defer s.lock.Unlock()
_, err := s.validateAndInsertBlockNoLock(block, updateVirtual)
s.lock.Unlock()
if err != nil {
return err
}
@@ -357,7 +334,7 @@ func (s *consensus) ValidateTransactionAndPopulateWithConsensusData(transaction
stagingArea, transaction, model.VirtualBlockHash)
}
func (s *consensus) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, bool, error) {
func (s *consensus) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) {
s.lock.Lock()
defer s.lock.Unlock()
@@ -366,11 +343,11 @@ func (s *consensus) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.Do
block, err := s.blockStore.Block(s.databaseContext, stagingArea, blockHash)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
return nil, false, nil
return nil, errors.Wrapf(err, "block %s does not exist", blockHash)
}
return nil, false, err
return nil, err
}
return block, true, nil
return block, nil
}
func (s *consensus) GetBlockEvenIfHeaderOnly(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) {
@@ -853,16 +830,12 @@ func (s *consensus) GetVirtualSelectedParentChainFromBlock(blockHash *externalap
}
func (s *consensus) validateBlockHashExists(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) error {
status, err := s.blockStatusStore.Get(s.databaseContext, stagingArea, blockHash)
if database.IsNotFoundError(err) {
return errors.Errorf("block %s does not exist", blockHash)
}
exists, err := s.blockStatusStore.Exists(s.databaseContext, stagingArea, blockHash)
if err != nil {
return err
}
if status == externalapi.StatusInvalid {
return errors.Errorf("block %s is invalid", blockHash)
if !exists {
return errors.Errorf("block %s does not exist", blockHash)
}
return nil
}
@@ -939,7 +912,11 @@ func (s *consensus) ResolveVirtual(progressReportCallback func(uint64, uint64))
progressReportCallback(virtualDAAScoreStart, virtualDAAScore)
}
_, isCompletelyResolved, err := s.resolveVirtualChunkWithLock(virtualResolveChunk)
// In order to prevent a situation that the consensus lock is held for too much time, we
// release the lock each time we resolve 100 blocks.
// Note: maxBlocksToResolve should be smaller than `params.FinalityDuration` in order to avoid a situation
// where UpdatePruningPointByVirtual skips a pruning point.
_, isCompletelyResolved, err := s.resolveVirtualChunkWithLock(100)
if err != nil {
return err
}
@@ -976,11 +953,6 @@ func (s *consensus) resolveVirtualChunkNoLock(maxBlocksToResolve uint64) (*exter
return nil, false, err
}
err = s.pruningManager.UpdatePruningPointIfRequired()
if err != nil {
return nil, false, err
}
err = s.sendVirtualChangedEvent(virtualChangeSet, true)
if err != nil {
return nil, false, err

View File

@@ -216,8 +216,6 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
transactionValidator := transactionvalidator.New(config.BlockCoinbaseMaturity,
config.EnableNonNativeSubnetworks,
config.MaxCoinbasePayloadLength,
config.K,
config.CoinbasePayloadScriptPublicKeyMaxLength,
dbManager,
pastMedianTimeManager,
ghostdagDataStore,
@@ -239,14 +237,12 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
config.GenesisBlock.Header.Bits())
coinbaseManager := coinbasemanager.New(
dbManager,
config.SubsidyGenesisReward,
config.PreDeflationaryPhaseBaseSubsidy,
config.CoinbasePayloadScriptPublicKeyMaxLength,
config.GenesisHash,
config.DeflationaryPhaseDaaScore,
config.DeflationaryPhaseBaseSubsidy,
dagTraversalManager,
ghostdagDataStore,
acceptanceDataStore,
@@ -544,8 +540,6 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
return nil, false, err
}
// If the virtual moved before shutdown but the pruning point hasn't, we
// move it if needed.
stagingArea := model.NewStagingArea()
err = pruningManager.UpdatePruningPointByVirtual(stagingArea)
if err != nil {
@@ -557,11 +551,6 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
return nil, false, err
}
err = pruningManager.UpdatePruningPointIfRequired()
if err != nil {
return nil, false, err
}
return c, false, nil
}

View File

@@ -263,7 +263,7 @@ func TestBoundedMergeDepth(t *testing.T) {
continue
}
block, _, err := tcSyncer.GetBlock(blocksHash)
block, err := tcSyncer.GetBlock(blocksHash)
if err != nil {
t.Fatalf("GetBlockHeader: %+v", err)
}

View File

@@ -10,12 +10,10 @@ package externalapi
//
// For example, assume a selected parent chain with IDs as depicted below, and the
// stop block is genesis:
//
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
//
// The block locator for block 17 would be the hashes of blocks:
//
// [17 16 14 11 7 2 genesis]
// [17 16 14 11 7 2 genesis]
type BlockLocator []*DomainHash
// Clone returns a clone of BlockLocator

View File

@@ -13,7 +13,7 @@ type Consensus interface {
ValidatePruningPointProof(pruningPointProof *PruningPointProof) error
ApplyPruningPointProof(pruningPointProof *PruningPointProof) error
GetBlock(blockHash *DomainHash) (*DomainBlock, bool, error)
GetBlock(blockHash *DomainHash) (*DomainBlock, error)
GetBlockEvenIfHeaderOnly(blockHash *DomainHash) (*DomainBlock, error)
GetBlockHeader(blockHash *DomainHash) (BlockHeader, error)
GetBlockInfo(blockHash *DomainHash) (*BlockInfo, error)

View File

@@ -2,7 +2,6 @@ package externalapi
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/pkg/errors"
@@ -243,23 +242,6 @@ func (spk *ScriptPublicKey) Equal(other *ScriptPublicKey) bool {
return bytes.Equal(spk.Script, other.Script)
}
// String stringifies a ScriptPublicKey.
func (spk *ScriptPublicKey) String() string {
var versionBytes = make([]byte, 2) // uint16
binary.LittleEndian.PutUint16(versionBytes, spk.Version)
versionString := string(versionBytes)
scriptString := string(spk.Script)
return versionString + scriptString
}
// NewScriptPublicKeyFromString converts the given string to a scriptPublicKey
func NewScriptPublicKeyFromString(ScriptPublicKeyString string) *ScriptPublicKey {
bytes := []byte(ScriptPublicKeyString)
version := binary.LittleEndian.Uint16(bytes[:2])
script := bytes[2:]
return &ScriptPublicKey{Script: script, Version: version}
}
// DomainTransactionOutput represents a Kaspad transaction output
type DomainTransactionOutput struct {
Value uint64

View File

@@ -7,4 +7,5 @@ type MergeDepthManager interface {
CheckBoundedMergeDepth(stagingArea *StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) error
NonBoundedMergeDepthViolatingBlues(stagingArea *StagingArea, blockHash, mergeDepthRoot *externalapi.DomainHash) ([]*externalapi.DomainHash, error)
VirtualMergeDepthRoot(stagingArea *StagingArea) (*externalapi.DomainHash, error)
MergeDepthRoot(stagingArea *StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) (*externalapi.DomainHash, error)
}

View File

@@ -43,9 +43,6 @@ type TestConsensus interface {
AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData,
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
AddBlockOnTips(coinbaseData *externalapi.DomainCoinbaseData,
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
AddUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,

View File

@@ -131,12 +131,11 @@ func (bb *blockBuilder) validateTransactions(stagingArea *model.StagingArea,
for _, transaction := range transactions {
err := bb.validateTransaction(stagingArea, transaction)
if err != nil {
ruleError := ruleerrors.RuleError{}
if !errors.As(err, &ruleError) {
if !errors.As(err, &ruleerrors.RuleError{}) {
return err
}
invalidTransactions = append(invalidTransactions,
ruleerrors.InvalidTransaction{Transaction: transaction, Error: &ruleError})
ruleerrors.InvalidTransaction{Transaction: transaction, Error: err})
}
}

View File

@@ -93,7 +93,7 @@ func TestValidateAndInsertImportedPruningPoint(t *testing.T) {
}
for _, blockHash := range pruningPointAndItsAnticone {
block, _, err := tcSyncer.GetBlock(blockHash)
block, err := tcSyncer.GetBlock(blockHash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
@@ -268,7 +268,7 @@ func TestValidateAndInsertImportedPruningPoint(t *testing.T) {
}
for _, blocksHash := range missingBlockHashes {
block, _, err := tcSyncer.GetBlock(blocksHash)
block, err := tcSyncer.GetBlock(blocksHash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
@@ -294,7 +294,7 @@ func TestValidateAndInsertImportedPruningPoint(t *testing.T) {
}
tipHash := addBlock(tcSyncer, syncerTips, t)
tip, _, err := tcSyncer.GetBlock(tipHash)
tip, err := tcSyncer.GetBlock(tipHash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
@@ -341,7 +341,7 @@ func TestValidateAndInsertImportedPruningPoint(t *testing.T) {
tipHash := consensusConfig.GenesisHash
for i := 0; i < numSharedBlocks; i++ {
tipHash = addBlock(tcSyncer, []*externalapi.DomainHash{tipHash}, t)
block, _, err := tcSyncer.GetBlock(tipHash)
block, err := tcSyncer.GetBlock(tipHash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}

View File

@@ -41,7 +41,7 @@ func TestCheckBlockIsNotPruned(t *testing.T) {
t.Fatalf("AddBlock: %+v", err)
}
beforePruningBlock, _, err := tc.GetBlock(tipHash)
beforePruningBlock, err := tc.GetBlock(tipHash)
if err != nil {
t.Fatalf("beforePruningBlock: %+v", err)
}
@@ -199,7 +199,7 @@ func TestIsFinalizedTransaction(t *testing.T) {
t.Fatalf("Error getting block DAA score : %+v", err)
}
blockParents := block.Header.DirectParents()
parentToSpend, _, err := tc.GetBlock(blockParents[0])
parentToSpend, err := tc.GetBlock(blockParents[0])
if err != nil {
t.Fatalf("Error getting block1: %+v", err)
}

View File

@@ -70,7 +70,7 @@ func TestChainedTransactions(t *testing.T) {
t.Fatalf("AddBlock: %+v", err)
}
block1, _, err := tc.GetBlock(block1Hash)
block1, err := tc.GetBlock(block1Hash)
if err != nil {
t.Fatalf("Error getting block1: %+v", err)
}
@@ -97,7 +97,7 @@ func TestChainedTransactions(t *testing.T) {
t.Fatalf("unexpected error %+v", err)
}
block2, _, err := tc.GetBlock(block2Hash)
block2, err := tc.GetBlock(block2Hash)
if err != nil {
t.Fatalf("Error getting block2: %+v", err)
}
@@ -169,10 +169,10 @@ var unOrderedParentsBlock = externalapi.DomainBlock{
}),
}},
externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{
0x7e, 0xe2, 0x10, 0x4e, 0x21, 0x2f, 0x2a, 0xb1,
0x7d, 0x22, 0xf5, 0xe8, 0xa0, 0x98, 0xef, 0x53,
0x83, 0xae, 0x59, 0x1f, 0x83, 0xf3, 0x78, 0x5d,
0x30, 0xae, 0x3e, 0xb3, 0x06, 0x08, 0x6f, 0x79,
0x31, 0x33, 0x37, 0x72, 0x5c, 0xde, 0x1c, 0xdf,
0xf5, 0x9f, 0xde, 0x16, 0x74, 0xbf, 0x0c, 0x64,
0x37, 0x40, 0x49, 0xdf, 0x02, 0x05, 0xca, 0x6d,
0x52, 0x23, 0x6f, 0xc2, 0x2b, 0xec, 0xad, 0x42,
}),
externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{
0x80, 0xf7, 0x00, 0xe3, 0x16, 0x3d, 0x04, 0x95,
@@ -202,7 +202,20 @@ var unOrderedParentsBlock = externalapi.DomainBlock{
Transactions: []*externalapi.DomainTransaction{
{
Version: 0,
Inputs: nil,
Inputs: []*externalapi.DomainTransactionInput{
{
PreviousOutpoint: externalapi.DomainOutpoint{
TransactionID: *externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{}),
Index: 0xffffffff,
},
SignatureScript: []byte{
0x02, 0x10, 0x27, 0x08, 0xac, 0x29, 0x2f, 0x2f,
0xcf, 0x70, 0xb0, 0x7e, 0x0b, 0x2f, 0x50, 0x32,
0x53, 0x48, 0x2f, 0x62, 0x74, 0x63, 0x64, 0x2f,
},
Sequence: math.MaxUint64,
},
},
Outputs: []*externalapi.DomainTransactionOutput{
{
Value: 0x12a05f200, // 5000000000
@@ -433,10 +446,10 @@ var exampleValidBlock = externalapi.DomainBlock{
}),
}},
externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{
0x46, 0xec, 0xf4, 0x5b, 0xe3, 0xba, 0xca, 0x34,
0x9d, 0xfe, 0x8a, 0x78, 0xde, 0xaf, 0x05, 0x3b,
0x0a, 0xa6, 0xd5, 0x38, 0x97, 0x4d, 0xa5, 0x0f,
0xd6, 0xef, 0xb4, 0xd2, 0x66, 0xbc, 0x8d, 0x21,
0x86, 0x8b, 0x73, 0xcd, 0x20, 0x51, 0x23, 0x60,
0xea, 0x62, 0x99, 0x9b, 0x87, 0xf6, 0xdd, 0x8d,
0xa4, 0x0b, 0xd7, 0xcf, 0xc6, 0x32, 0x38, 0xee,
0xd9, 0x68, 0x72, 0x1f, 0xa2, 0x51, 0xe4, 0x28,
}),
externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{
0x8a, 0xb7, 0xd6, 0x73, 0x1b, 0xe6, 0xc5, 0xd3,
@@ -461,7 +474,21 @@ var exampleValidBlock = externalapi.DomainBlock{
Transactions: []*externalapi.DomainTransaction{
{
Version: 0,
Inputs: nil,
Inputs: []*externalapi.DomainTransactionInput{
{
PreviousOutpoint: externalapi.DomainOutpoint{
TransactionID: *externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x9b, 0x22, 0x59, 0x44, 0x66, 0xf0, 0xbe, 0x50,
0x7c, 0x1c, 0x8a, 0xf6, 0x06, 0x27, 0xe6, 0x33,
0x38, 0x7e, 0xd1, 0xd5, 0x8c, 0x42, 0x59, 0x1a,
0x31, 0xac, 0x9a, 0xa6, 0x2e, 0xd5, 0x2b, 0x0f,
}),
Index: 0xffffffff,
},
SignatureScript: nil,
Sequence: math.MaxUint64,
},
},
Outputs: []*externalapi.DomainTransactionOutput{
{
Value: 0x12a05f200, // 5000000000
@@ -724,10 +751,10 @@ var blockWithWrongTxOrder = externalapi.DomainBlock{
}),
}},
externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{
0xd5, 0xd2, 0x32, 0xe4, 0xbe, 0x9c, 0x33, 0xbd,
0xf1, 0x0a, 0xd2, 0x9d, 0x0c, 0xbd, 0xe5, 0xae,
0xcb, 0x1a, 0xf9, 0x5a, 0x3e, 0xfb, 0xf3, 0xc7,
0x2b, 0x4d, 0x10, 0xa6, 0xbd, 0x5f, 0x07, 0xe7,
0x7b, 0x25, 0x8b, 0xfa, 0xfb, 0x49, 0xe4, 0x94,
0x48, 0x2c, 0xf9, 0x74, 0xdd, 0xad, 0x9d, 0x6f,
0x98, 0x8f, 0xfb, 0x01, 0x9d, 0x49, 0x29, 0xbe,
0x3c, 0xec, 0x90, 0xfe, 0xa5, 0x0c, 0xaf, 0x6b,
}),
externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{
0xa0, 0x69, 0x2d, 0x16, 0xb5, 0xd7, 0xe4, 0xf3,

View File

@@ -146,8 +146,8 @@ func (v *blockValidator) validateDifficulty(stagingArea *model.StagingArea,
// target difficulty as claimed.
//
// The flags modify the behavior of this function as follows:
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
// difficulty is not performed.
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
// difficulty is not performed.
func (v *blockValidator) checkProofOfWork(header externalapi.BlockHeader) error {
// The target difficulty must be larger than zero.
state := pow.NewState(header.ToMutable())

View File

@@ -204,8 +204,8 @@ func (c *coinbaseManager) calcDeflationaryPeriodBlockSubsidy(blockDaaScore uint6
}
/*
This table was pre-calculated by calling `calcDeflationaryPeriodBlockSubsidyFloatCalc` for all months until reaching 0 subsidy.
To regenerate this table, run `TestBuildSubsidyTable` in coinbasemanager_test.go (note the `deflationaryPhaseBaseSubsidy` therein)
This table was pre-calculated by calling `calcDeflationaryPeriodBlockSubsidyFloatCalc` for all months until reaching 0 subsidy.
To regenerate this table, run `TestBuildSubsidyTable` in coinbasemanager_test.go (note the `deflationaryPhaseBaseSubsidy` therein)
*/
var subsidyByDeflationaryMonthTable = []uint64{
44000000000, 41530469757, 39199543598, 36999442271, 34922823143, 32962755691, 31112698372, 29366476791, 27718263097, 26162556530, 24694165062, 23308188075, 22000000000, 20765234878, 19599771799, 18499721135, 17461411571, 16481377845, 15556349186, 14683238395, 13859131548, 13081278265, 12347082531, 11654094037, 11000000000,

View File

@@ -1,57 +0,0 @@
package coinbasemanager_test
import (
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"testing"
)
func TestExtractCoinbaseDataBlueScoreAndSubsidy(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestBlockStatus")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
tests := []struct {
name string
scriptPublicKeyVersion uint16
}{
{
name: "below 255",
scriptPublicKeyVersion: 100,
},
{
name: "above 255",
scriptPublicKeyVersion: 300,
},
}
for _, test := range tests {
coinbaseTx, _, err := tc.CoinbaseManager().ExpectedCoinbaseTransaction(model.NewStagingArea(), model.VirtualBlockHash, &externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,
Version: test.scriptPublicKeyVersion,
},
ExtraData: nil,
})
if err != nil {
t.Fatal(err)
}
_, cbData, _, err := tc.CoinbaseManager().ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx)
if err != nil {
t.Fatal(err)
}
if cbData.ScriptPublicKey.Version != test.scriptPublicKeyVersion {
t.Fatalf("test %s post HF expected %d but got %d", test.name, test.scriptPublicKeyVersion, cbData.ScriptPublicKey.Version)
}
}
})
}

View File

@@ -28,7 +28,7 @@ func (c *coinbaseManager) serializeCoinbasePayload(blueScore uint64,
binary.LittleEndian.PutUint64(payload[:uint64Len], blueScore)
binary.LittleEndian.PutUint64(payload[uint64Len:], subsidy)
binary.LittleEndian.PutUint16(payload[uint64Len+lengthOfSubsidy:], coinbaseData.ScriptPublicKey.Version)
payload[uint64Len+lengthOfSubsidy] = uint8(coinbaseData.ScriptPublicKey.Version)
payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey] = uint8(len(coinbaseData.ScriptPublicKey.Script))
copy(payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey+lengthOfScriptPubKeyLength:], coinbaseData.ScriptPublicKey.Script)
copy(payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey+lengthOfScriptPubKeyLength+scriptLengthOfScriptPubKey:], coinbaseData.ExtraData)
@@ -52,7 +52,7 @@ func ModifyCoinbasePayload(payload []byte, coinbaseData *externalapi.DomainCoinb
payload = newPayload
}
binary.LittleEndian.PutUint16(payload[uint64Len+lengthOfSubsidy:uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey], coinbaseData.ScriptPublicKey.Version)
payload[uint64Len+lengthOfSubsidy] = uint8(coinbaseData.ScriptPublicKey.Version)
payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey] = uint8(len(coinbaseData.ScriptPublicKey.Script))
copy(payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey+lengthOfScriptPubKeyLength:], coinbaseData.ScriptPublicKey.Script)
copy(payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey+lengthOfScriptPubKeyLength+scriptLengthOfScriptPubKey:], coinbaseData.ExtraData)
@@ -73,8 +73,7 @@ func (c *coinbaseManager) ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *ext
blueScore = binary.LittleEndian.Uint64(coinbaseTx.Payload[:uint64Len])
subsidy = binary.LittleEndian.Uint64(coinbaseTx.Payload[uint64Len:])
scriptPubKeyVersion := binary.LittleEndian.Uint16(coinbaseTx.Payload[uint64Len+lengthOfSubsidy : uint64Len+lengthOfSubsidy+uint16Len])
scriptPubKeyVersion := uint16(coinbaseTx.Payload[uint64Len+lengthOfSubsidy])
scriptPubKeyScriptLength := coinbaseTx.Payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey]
if scriptPubKeyScriptLength > c.coinbasePayloadScriptPublicKeyMaxLength {

View File

@@ -40,7 +40,7 @@ func TestUTXOCommitment(t *testing.T) {
if err != nil {
t.Fatalf("Error creating block B: %+v", err)
}
blockB, _, err := consensus.GetBlock(blockBHash)
blockB, err := consensus.GetBlock(blockBHash)
if err != nil {
t.Fatalf("Error getting block B: %+v", err)
}
@@ -73,7 +73,7 @@ func TestUTXOCommitment(t *testing.T) {
}
func checkBlockUTXOCommitment(t *testing.T, consensus testapi.TestConsensus, blockHash *externalapi.DomainHash, blockName string) {
block, _, err := consensus.GetBlock(blockHash)
block, err := consensus.GetBlock(blockHash)
if err != nil {
t.Fatalf("Error getting block %s: %+v", blockName, err)
}

View File

@@ -72,8 +72,7 @@ func New(
maxBlockParents: maxBlockParents,
mergeSetSizeLimit: mergeSetSizeLimit,
genesisHash: genesisHash,
databaseContext: databaseContext,
databaseContext: databaseContext,
ghostdagManager: ghostdagManager,
dagTopologyManager: dagTopologyManager,

View File

@@ -40,7 +40,7 @@ func TestDoubleSpends(t *testing.T) {
if err != nil {
t.Fatalf("Error creating fundingBlock: %+v", err)
}
fundingBlock, _, err := consensus.GetBlock(fundingBlockHash)
fundingBlock, err := consensus.GetBlock(fundingBlockHash)
if err != nil {
t.Fatalf("Error getting fundingBlock: %+v", err)
}
@@ -159,9 +159,8 @@ func TestDoubleSpends(t *testing.T) {
// TestTransactionAcceptance checks that block transactions are accepted correctly when the merge set is sorted topologically.
// DAG diagram:
// genesis <- blockA <- blockB <- blockC <- ..(chain of k-blocks).. lastBlockInChain <- blockD <- blockE <- blockF <- blockG
//
// ^ ^ |
// | redBlock <------------------------ blueChildOfRedBlock <-------------------------------
// ^ ^ |
// | redBlock <------------------------ blueChildOfRedBlock <-------------------------------
func TestTransactionAcceptance(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
stagingArea := model.NewStagingArea()
@@ -196,7 +195,7 @@ func TestTransactionAcceptance(t *testing.T) {
}
}
lastBlockInChain := chainTipHash
blockC, _, err := testConsensus.GetBlock(blockHashC)
blockC, err := testConsensus.GetBlock(blockHashC)
if err != nil {
t.Fatalf("Error getting blockC: %+v", err)
}
@@ -270,27 +269,22 @@ func TestTransactionAcceptance(t *testing.T) {
if err != nil {
t.Fatalf("Error getting acceptance data: %+v", err)
}
blueChildOfRedBlock, _, err := testConsensus.GetBlock(hashBlueChildOfRedBlock)
blueChildOfRedBlock, err := testConsensus.GetBlock(hashBlueChildOfRedBlock)
if err != nil {
t.Fatalf("Error getting blueChildOfRedBlock: %+v", err)
}
blockE, _, err := testConsensus.GetBlock(blockHashF)
blockE, err := testConsensus.GetBlock(blockHashF)
if err != nil {
t.Fatalf("Error getting blockE: %+v", err)
}
redBlock, _, err := testConsensus.GetBlock(redHash)
redBlock, err := testConsensus.GetBlock(redHash)
if err != nil {
t.Fatalf("Error getting redBlock: %+v", err)
}
_, found, err := testConsensus.GetBlock(blockHashG)
_, err = testConsensus.GetBlock(blockHashG)
if err != nil {
t.Fatalf("Error getting blockG: %+v", err)
t.Fatalf("Error getting blockF: %+v", err)
}
if !found {
t.Fatalf("block G is missing")
}
updatedDAAScoreVirtualBlock := consensusConfig.GenesisBlock.Header.DAAScore() + 26
//We expect the second transaction in the "blue block" (blueChildOfRedBlock) to be accepted because the merge set is ordered topologically
//and the red block is ordered topologically before the "blue block" so the input is known in the UTXOSet.

View File

@@ -205,37 +205,37 @@ func TestBlockWindow(t *testing.T) {
{
parents: []string{"H", "F"},
id: "I",
expectedWindow: []string{"F", "C", "H", "D", "B", "G"},
expectedWindow: []string{"F", "C", "D", "H", "G", "B"},
},
{
parents: []string{"I"},
id: "J",
expectedWindow: []string{"I", "F", "C", "H", "D", "B", "G"},
expectedWindow: []string{"I", "F", "C", "D", "H", "G", "B"},
},
{
parents: []string{"J"},
id: "K",
expectedWindow: []string{"J", "I", "F", "C", "H", "D", "B", "G"},
expectedWindow: []string{"J", "I", "F", "C", "D", "H", "G", "B"},
},
{
parents: []string{"K"},
id: "L",
expectedWindow: []string{"K", "J", "I", "F", "C", "H", "D", "B", "G"},
expectedWindow: []string{"K", "J", "I", "F", "C", "D", "H", "G", "B"},
},
{
parents: []string{"L"},
id: "M",
expectedWindow: []string{"L", "K", "J", "I", "F", "C", "H", "D", "B", "G"},
expectedWindow: []string{"L", "K", "J", "I", "F", "C", "D", "H", "G", "B"},
},
{
parents: []string{"M"},
id: "N",
expectedWindow: []string{"M", "L", "K", "J", "I", "F", "C", "H", "D", "B"},
expectedWindow: []string{"M", "L", "K", "J", "I", "F", "C", "D", "H", "G"},
},
{
parents: []string{"N"},
id: "O",
expectedWindow: []string{"N", "M", "L", "K", "J", "I", "F", "C", "H", "D"},
expectedWindow: []string{"N", "M", "L", "K", "J", "I", "F", "C", "D", "H"},
},
},
dagconfig.SimnetParams.Name: {

View File

@@ -132,7 +132,7 @@ func TestDifficulty(t *testing.T) {
case dagconfig.TestnetParams.Name:
expectedBits = uint32(0x1e7f1441)
case dagconfig.DevnetParams.Name:
expectedBits = uint32(0x1f4e54ab)
expectedBits = uint32(0x207f1441)
case dagconfig.MainnetParams.Name:
expectedBits = uint32(0x1d02c50f)
}

View File

@@ -30,14 +30,14 @@ func (bg *blockGHOSTDAGData) toModel() *externalapi.BlockGHOSTDAGData {
//
// 1) |anticone-of-candidate-block ∩ blue-set-of-newBlock| ≤ K
//
// 2. For every blue block in blue-set-of-newBlock:
// |(anticone-of-blue-block ∩ blue-set-newBlock) {candidate-block}| ≤ K.
// We validate this condition by maintaining a map BluesAnticoneSizes for
// each block which holds all the blue anticone sizes that were affected by
// the new added blue blocks.
// So to find out what is |anticone-of-blue ∩ blue-set-of-newBlock| we just iterate in
// the selected parent chain of the new block until we find an existing entry in
// BluesAnticoneSizes.
// 2) For every blue block in blue-set-of-newBlock:
// |(anticone-of-blue-block ∩ blue-set-newBlock) {candidate-block}| ≤ K.
// We validate this condition by maintaining a map BluesAnticoneSizes for
// each block which holds all the blue anticone sizes that were affected by
// the new added blue blocks.
// So to find out what is |anticone-of-blue ∩ blue-set-of-newBlock| we just iterate in
// the selected parent chain of the new block until we find an existing entry in
// BluesAnticoneSizes.
//
// For further details see the article https://eprint.iacr.org/2018/104.pdf
func (gm *ghostdagManager) GHOSTDAG(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) error {

View File

@@ -38,7 +38,7 @@ func TestPruning(t *testing.T) {
"dag-for-test-pruning.json": {
dagconfig.MainnetParams.Name: "503",
dagconfig.TestnetParams.Name: "503",
dagconfig.DevnetParams.Name: "502",
dagconfig.DevnetParams.Name: "503",
dagconfig.SimnetParams.Name: "503",
},
}

View File

@@ -768,114 +768,6 @@ func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPoints(st
return nil, errors.Errorf("previous pruning point doesn't exist")
}
previousPruningHash, err := pm.pruningStore.PruningPointByIndex(pm.databaseContext, stagingArea, pruningPointIndex-1)
if err != nil {
return nil, err
}
currentPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningHash, false)
if err != nil {
return nil, err
}
previousPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningHash, false)
if err != nil {
return nil, err
}
currentPruningCurrentDiffChild := currentPruningHash
previousPruningCurrentDiffChild := previousPruningHash
// We need to use BlueWork because it's the only thing that's monotonic in the whole DAG
// We use the BlueWork to know which point is currently lower on the DAG so we can keep climbing its children,
// that way we keep climbing on the lowest point until they both reach the exact same descendant
currentPruningCurrentDiffChildBlueWork := currentPruningGhostDAG.BlueWork()
previousPruningCurrentDiffChildBlueWork := previousPruningGhostDAG.BlueWork()
var diffHashesFromPrevious []*externalapi.DomainHash
var diffHashesFromCurrent []*externalapi.DomainHash
for {
// if currentPruningCurrentDiffChildBlueWork > previousPruningCurrentDiffChildBlueWork
if currentPruningCurrentDiffChildBlueWork.Cmp(previousPruningCurrentDiffChildBlueWork) == 1 {
diffHashesFromPrevious = append(diffHashesFromPrevious, previousPruningCurrentDiffChild)
previousPruningCurrentDiffChild, err = pm.utxoDiffStore.UTXODiffChild(pm.databaseContext, stagingArea, previousPruningCurrentDiffChild)
if err != nil {
return nil, err
}
diffChildGhostDag, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningCurrentDiffChild, false)
if err != nil {
return nil, err
}
previousPruningCurrentDiffChildBlueWork = diffChildGhostDag.BlueWork()
} else if currentPruningCurrentDiffChild.Equal(previousPruningCurrentDiffChild) {
break
} else {
diffHashesFromCurrent = append(diffHashesFromCurrent, currentPruningCurrentDiffChild)
currentPruningCurrentDiffChild, err = pm.utxoDiffStore.UTXODiffChild(pm.databaseContext, stagingArea, currentPruningCurrentDiffChild)
if err != nil {
return nil, err
}
diffChildGhostDag, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningCurrentDiffChild, false)
if err != nil {
return nil, err
}
currentPruningCurrentDiffChildBlueWork = diffChildGhostDag.BlueWork()
}
}
// The order in which we apply the diffs should be from top to bottom, but we traversed from bottom to top
// so we apply the diffs in reverse order.
oldDiff := utxo.NewMutableUTXODiff()
for i := len(diffHashesFromPrevious) - 1; i >= 0; i-- {
utxoDiff, err := pm.utxoDiffStore.UTXODiff(pm.databaseContext, stagingArea, diffHashesFromPrevious[i])
if err != nil {
return nil, err
}
err = oldDiff.WithDiffInPlace(utxoDiff)
if err != nil {
return nil, err
}
}
newDiff := utxo.NewMutableUTXODiff()
for i := len(diffHashesFromCurrent) - 1; i >= 0; i-- {
utxoDiff, err := pm.utxoDiffStore.UTXODiff(pm.databaseContext, stagingArea, diffHashesFromCurrent[i])
if err != nil {
return nil, err
}
err = newDiff.WithDiffInPlace(utxoDiff)
if err != nil {
return nil, err
}
}
return oldDiff.DiffFrom(newDiff.ToImmutable())
}
// This function takes 2 chain blocks (currentPruningHash, previousPruningHash) and finds
// the UTXO diff between them by iterating over acceptance data of the chain blocks in between.
func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPointsUsingAcceptanceData(stagingArea *model.StagingArea, currentPruningHash *externalapi.DomainHash) (externalapi.UTXODiff, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "pruningManager.calculateDiffBetweenPreviousAndCurrentPruningPoints__UsingAcceptanceData")
defer onEnd()
if currentPruningHash.Equal(pm.genesisHash) {
iter, err := pm.consensusStateManager.RestorePastUTXOSetIterator(stagingArea, currentPruningHash)
if err != nil {
return nil, err
}
set := make(map[externalapi.DomainOutpoint]externalapi.UTXOEntry)
for ok := iter.First(); ok; ok = iter.Next() {
outpoint, entry, err := iter.Get()
if err != nil {
return nil, err
}
set[*outpoint] = entry
}
return utxo.NewUTXODiffFromCollections(utxo.NewUTXOCollection(set), utxo.NewUTXOCollection(make(map[externalapi.DomainOutpoint]externalapi.UTXOEntry)))
}
pruningPointIndex, err := pm.pruningStore.CurrentPruningPointIndex(pm.databaseContext, stagingArea)
if err != nil {
return nil, err
}
if pruningPointIndex == 0 {
return nil, errors.Errorf("previous pruning point doesn't exist")
}
previousPruningHash, err := pm.pruningStore.PruningPointByIndex(pm.databaseContext, stagingArea, pruningPointIndex-1)
if err != nil {
return nil, err
@@ -1001,20 +893,14 @@ func (pm *pruningManager) updatePruningPoint() error {
log.Debugf("Restoring the pruning point UTXO set")
utxoSetDiff, err := pm.calculateDiffBetweenPreviousAndCurrentPruningPoints(stagingArea, pruningPoint)
if err != nil {
log.Infof("Calculating pruning points diff through utxo diff children failed %s. Falling back to calculation "+
"through acceptance data", err)
utxoSetDiff, err = pm.calculateDiffBetweenPreviousAndCurrentPruningPointsUsingAcceptanceData(stagingArea, pruningPoint)
if err != nil {
return err
}
return err
}
log.Debugf("Updating the pruning point UTXO set")
err = pm.pruningStore.UpdatePruningPointUTXOSet(pm.databaseContext, utxoSetDiff)
if err != nil {
return err
}
if pm.shouldSanityCheckPruningUTXOSet && !pruningPoint.Equal(pm.genesisHash) {
if pm.shouldSanityCheckPruningUTXOSet {
err = pm.validateUTXOSetFitsCommitment(stagingArea, pruningPoint)
if err != nil {
return err

View File

@@ -12,13 +12,13 @@ import (
// interval contains B's interval, it replaces it.
//
// Notes:
// - Intervals never intersect unless one contains the other
// (this follows from the tree structure and the indexing rule).
// - Since node.FutureCoveringSet is kept ordered, a binary search can be
// used for insertion/queries.
// - Although reindexing may change a block's interval, the
// is-superset relation will by definition
// be always preserved.
// * Intervals never intersect unless one contains the other
// (this follows from the tree structure and the indexing rule).
// * Since node.FutureCoveringSet is kept ordered, a binary search can be
// used for insertion/queries.
// * Although reindexing may change a block's interval, the
// is-superset relation will by definition
// be always preserved.
func (rt *reachabilityManager) insertToFutureCoveringSet(stagingArea *model.StagingArea, node, futureNode *externalapi.DomainHash) error {
reachabilityData, err := rt.reachabilityDataForInsertion(stagingArea, node)
if err != nil {

View File

@@ -161,9 +161,7 @@ func intervalSplitWithExponentialBias(ri *model.ReachabilityInterval, sizes []ui
// exponentialFractions returns a fraction of each size in sizes
// as follows:
//
// fraction[i] = 2^size[i] / sum_j(2^size[j])
//
// fraction[i] = 2^size[i] / sum_j(2^size[j])
// In the code below the above equation is divided by 2^max(size)
// to avoid exploding numbers. Note that in 1 / 2^(max(size)-size[i])
// we divide 1 by potentially a very large number, which will

View File

@@ -42,13 +42,13 @@ Core (BFS) algorithms used during reindexing
// and populates the provided subTreeSizeMap with the results.
// It is equivalent to the following recursive implementation:
//
// func (rt *reachabilityManager) countSubtrees(node *model.ReachabilityTreeNode) uint64 {
// subtreeSize := uint64(0)
// for _, child := range node.children {
// subtreeSize += child.countSubtrees()
// }
// return subtreeSize + 1
// }
// func (rt *reachabilityManager) countSubtrees(node *model.ReachabilityTreeNode) uint64 {
// subtreeSize := uint64(0)
// for _, child := range node.children {
// subtreeSize += child.countSubtrees()
// }
// return subtreeSize + 1
// }
//
// However, we are expecting (linearly) deep trees, and so a
// recursive stack-based approach is inefficient and will hit

View File

@@ -350,7 +350,6 @@ func (v *transactionValidator) validateTransactionSigOpCounts(tx *externalapi.Do
sigScript := input.SignatureScript
isP2SH := txscript.IsPayToScriptHash(utxoEntry.ScriptPublicKey())
sigOpCount := txscript.GetPreciseSigOpCount(sigScript, utxoEntry.ScriptPublicKey(), isP2SH)
if sigOpCount != int(input.SigOpCount) {
return errors.Wrapf(ruleerrors.ErrWrongSigOpCount,
"input %d specifies SigOpCount %d while actual SigOpCount is %d",

View File

@@ -23,7 +23,7 @@ func (v *transactionValidator) ValidateTransactionInIsolation(tx *externalapi.Do
if err != nil {
return err
}
err = v.checkCoinbaseInIsolation(tx)
err = v.checkCoinbaseLength(tx)
if err != nil {
return err
}
@@ -114,7 +114,7 @@ func (v *transactionValidator) checkDuplicateTransactionInputs(tx *externalapi.D
return nil
}
func (v *transactionValidator) checkCoinbaseInIsolation(tx *externalapi.DomainTransaction) error {
func (v *transactionValidator) checkCoinbaseLength(tx *externalapi.DomainTransaction) error {
if !transactionhelper.IsCoinBase(tx) {
return nil
}
@@ -127,22 +127,6 @@ func (v *transactionValidator) checkCoinbaseInIsolation(tx *externalapi.DomainTr
payloadLen, v.maxCoinbasePayloadLength)
}
if len(tx.Inputs) != 0 {
return errors.Wrap(ruleerrors.ErrCoinbaseWithInputs, "coinbase has inputs")
}
outputsLimit := uint64(v.ghostdagK) + 2
if uint64(len(tx.Outputs)) > outputsLimit {
return errors.Wrapf(ruleerrors.ErrCoinbaseTooManyOutputs, "coinbase has too many outputs: got %d where the limit is %d", len(tx.Outputs), outputsLimit)
}
for i, output := range tx.Outputs {
if len(output.ScriptPublicKey.Script) > int(v.coinbasePayloadScriptPublicKeyMaxLength) {
return errors.Wrapf(ruleerrors.ErrCoinbaseTooLongScriptPublicKey, "coinbase output %d has a too long script public key", i)
}
}
return nil
}

View File

@@ -76,7 +76,7 @@ func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) {
subnetworks.SubnetworkIDNative,
&txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil},
nil,
ruleerrors.ErrCoinbaseWithInputs, 0},
nil, 0},
{"no inputs coinbase",
0,
1,

View File

@@ -2,7 +2,6 @@ package transactionvalidator
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/util/txmass"
)
@@ -12,26 +11,22 @@ const sigCacheSize = 10_000
// transactionValidator exposes a set of validation classes, after which
// it's possible to determine whether either a transaction is valid
type transactionValidator struct {
blockCoinbaseMaturity uint64
databaseContext model.DBReader
pastMedianTimeManager model.PastMedianTimeManager
ghostdagDataStore model.GHOSTDAGDataStore
daaBlocksStore model.DAABlocksStore
enableNonNativeSubnetworks bool
maxCoinbasePayloadLength uint64
ghostdagK externalapi.KType
coinbasePayloadScriptPublicKeyMaxLength uint8
sigCache *txscript.SigCache
sigCacheECDSA *txscript.SigCacheECDSA
txMassCalculator *txmass.Calculator
blockCoinbaseMaturity uint64
databaseContext model.DBReader
pastMedianTimeManager model.PastMedianTimeManager
ghostdagDataStore model.GHOSTDAGDataStore
daaBlocksStore model.DAABlocksStore
enableNonNativeSubnetworks bool
maxCoinbasePayloadLength uint64
sigCache *txscript.SigCache
sigCacheECDSA *txscript.SigCacheECDSA
txMassCalculator *txmass.Calculator
}
// New instantiates a new TransactionValidator
func New(blockCoinbaseMaturity uint64,
enableNonNativeSubnetworks bool,
maxCoinbasePayloadLength uint64,
ghostdagK externalapi.KType,
coinbasePayloadScriptPublicKeyMaxLength uint8,
databaseContext model.DBReader,
pastMedianTimeManager model.PastMedianTimeManager,
ghostdagDataStore model.GHOSTDAGDataStore,
@@ -39,17 +34,15 @@ func New(blockCoinbaseMaturity uint64,
txMassCalculator *txmass.Calculator) model.TransactionValidator {
return &transactionValidator{
blockCoinbaseMaturity: blockCoinbaseMaturity,
enableNonNativeSubnetworks: enableNonNativeSubnetworks,
maxCoinbasePayloadLength: maxCoinbasePayloadLength,
ghostdagK: ghostdagK,
coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength,
databaseContext: databaseContext,
pastMedianTimeManager: pastMedianTimeManager,
ghostdagDataStore: ghostdagDataStore,
daaBlocksStore: daaBlocksStore,
sigCache: txscript.NewSigCache(sigCacheSize),
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
txMassCalculator: txMassCalculator,
blockCoinbaseMaturity: blockCoinbaseMaturity,
enableNonNativeSubnetworks: enableNonNativeSubnetworks,
maxCoinbasePayloadLength: maxCoinbasePayloadLength,
databaseContext: databaseContext,
pastMedianTimeManager: pastMedianTimeManager,
ghostdagDataStore: ghostdagDataStore,
daaBlocksStore: daaBlocksStore,
sigCache: txscript.NewSigCache(sigCacheSize),
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
txMassCalculator: txMassCalculator,
}
}

Some files were not shown because too many files have changed in this diff Show More