diff --git a/app/appmessage/rpc_submit_transaction.go b/app/appmessage/rpc_submit_transaction.go index 45d68a904..e2870e8a6 100644 --- a/app/appmessage/rpc_submit_transaction.go +++ b/app/appmessage/rpc_submit_transaction.go @@ -5,6 +5,7 @@ package appmessage type SubmitTransactionRequestMessage struct { baseMessage Transaction *RPCTransaction + AllowOrphan bool } // Command returns the protocol command string for the message diff --git a/app/component_manager.go b/app/component_manager.go index 7cce2f2d2..3830d9231 100644 --- a/app/component_manager.go +++ b/app/component_manager.go @@ -4,6 +4,8 @@ import ( "fmt" "sync/atomic" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool" + "github.com/kaspanet/kaspad/app/protocol" "github.com/kaspanet/kaspad/app/rpc" "github.com/kaspanet/kaspad/domain" @@ -79,8 +81,11 @@ func NewComponentManager(cfg *config.Config, db infrastructuredatabase.Database, IsArchival: cfg.IsArchivalNode, EnableSanityCheckPruningUTXOSet: cfg.EnableSanityCheckPruningUTXOSet, } + mempoolConfig := mempool.DefaultConfig(&consensusConfig.Params) + mempoolConfig.MaximumOrphanTransactionCount = cfg.MaxOrphanTxs + mempoolConfig.MinimumRelayTransactionFee = cfg.MinRelayTxFee - domain, err := domain.New(&consensusConfig, db) + domain, err := domain.New(&consensusConfig, mempoolConfig, db) if err != nil { return nil, err } diff --git a/app/protocol/flowcontext/blocks.go b/app/protocol/flowcontext/blocks.go index b259cc4a5..e327b3739 100644 --- a/app/protocol/flowcontext/blocks.go +++ b/app/protocol/flowcontext/blocks.go @@ -3,11 +3,10 @@ package flowcontext import ( peerpkg "github.com/kaspanet/kaspad/app/protocol/peer" "github.com/kaspanet/kaspad/app/protocol/protocolerrors" - "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" - "github.com/pkg/errors" - "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/pkg/errors" "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/app/protocol/flows/blockrelay" @@ -71,8 +70,6 @@ func (f *FlowContext) OnPruningPointUTXOSetOverride() error { func (f *FlowContext) broadcastTransactionsAfterBlockAdded( addedBlocks []*externalapi.DomainBlock, transactionsAcceptedToMempool []*externalapi.DomainTransaction) error { - f.updateTransactionsToRebroadcast(addedBlocks) - // Don't relay transactions when in IBD. if f.IsIBDRunning() { return nil @@ -80,7 +77,11 @@ func (f *FlowContext) broadcastTransactionsAfterBlockAdded( var txIDsToRebroadcast []*externalapi.DomainTransactionID if f.shouldRebroadcastTransactions() { - txIDsToRebroadcast = f.txIDsToRebroadcast() + txsToRebroadcast, err := f.Domain().MiningManager().RevalidateHighPriorityTransactions() + if err != nil { + return err + } + txIDsToRebroadcast = consensushashing.TransactionIDs(txsToRebroadcast) } txIDsToBroadcast := make([]*externalapi.DomainTransactionID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast)) diff --git a/app/protocol/flowcontext/flow_context.go b/app/protocol/flowcontext/flow_context.go index 4b54e512b..c6ebc25a3 100644 --- a/app/protocol/flowcontext/flow_context.go +++ b/app/protocol/flowcontext/flow_context.go @@ -1,10 +1,11 @@ package flowcontext import ( - "github.com/kaspanet/kaspad/util/mstime" "sync" "time" + "github.com/kaspanet/kaspad/util/mstime" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain" @@ -46,10 +47,8 @@ type FlowContext struct { onPruningPointUTXOSetOverrideHandler OnPruningPointUTXOSetOverrideHandler onTransactionAddedToMempoolHandler OnTransactionAddedToMempoolHandler - transactionsToRebroadcastLock sync.Mutex - transactionsToRebroadcast map[externalapi.DomainTransactionID]*externalapi.DomainTransaction - lastRebroadcastTime time.Time - sharedRequestedTransactions *transactionrelay.SharedRequestedTransactions + lastRebroadcastTime time.Time + sharedRequestedTransactions *transactionrelay.SharedRequestedTransactions sharedRequestedBlocks *blockrelay.SharedRequestedBlocks @@ -78,7 +77,6 @@ func New(cfg *config.Config, domain domain.Domain, addressManager *addressmanage sharedRequestedTransactions: transactionrelay.NewSharedRequestedTransactions(), sharedRequestedBlocks: blockrelay.NewSharedRequestedBlocks(), peers: make(map[id.ID]*peerpkg.Peer), - transactionsToRebroadcast: make(map[externalapi.DomainTransactionID]*externalapi.DomainTransaction), orphans: make(map[externalapi.DomainHash]*externalapi.DomainBlock), timeStarted: mstime.Now().UnixMilliseconds(), shutdownChan: make(chan struct{}), diff --git a/app/protocol/flowcontext/transactions.go b/app/protocol/flowcontext/transactions.go index 9f87431d7..504861b66 100644 --- a/app/protocol/flowcontext/transactions.go +++ b/app/protocol/flowcontext/transactions.go @@ -10,53 +10,23 @@ import ( ) // AddTransaction adds transaction to the mempool and propagates it. -func (f *FlowContext) AddTransaction(tx *externalapi.DomainTransaction) error { - f.transactionsToRebroadcastLock.Lock() - defer f.transactionsToRebroadcastLock.Unlock() - - err := f.Domain().MiningManager().ValidateAndInsertTransaction(tx, false) +func (f *FlowContext) AddTransaction(tx *externalapi.DomainTransaction, allowOrphan bool) error { + acceptedTransactions, err := f.Domain().MiningManager().ValidateAndInsertTransaction(tx, true, allowOrphan) if err != nil { return err } - transactionID := consensushashing.TransactionID(tx) - f.transactionsToRebroadcast[*transactionID] = tx - inv := appmessage.NewMsgInvTransaction([]*externalapi.DomainTransactionID{transactionID}) + acceptedTransactionIDs := consensushashing.TransactionIDs(acceptedTransactions) + inv := appmessage.NewMsgInvTransaction(acceptedTransactionIDs) + return f.Broadcast(inv) } -func (f *FlowContext) updateTransactionsToRebroadcast(addedBlocks []*externalapi.DomainBlock) { - f.transactionsToRebroadcastLock.Lock() - defer f.transactionsToRebroadcastLock.Unlock() - - for _, block := range addedBlocks { - // Note: if a transaction is included in the DAG but not accepted, - // it won't be rebroadcast anymore, although it is not included in - // the UTXO set - for _, tx := range block.Transactions { - delete(f.transactionsToRebroadcast, *consensushashing.TransactionID(tx)) - } - } -} - func (f *FlowContext) shouldRebroadcastTransactions() bool { const rebroadcastInterval = 30 * time.Second return time.Since(f.lastRebroadcastTime) > rebroadcastInterval } -func (f *FlowContext) txIDsToRebroadcast() []*externalapi.DomainTransactionID { - f.transactionsToRebroadcastLock.Lock() - defer f.transactionsToRebroadcastLock.Unlock() - - txIDs := make([]*externalapi.DomainTransactionID, len(f.transactionsToRebroadcast)) - i := 0 - for _, tx := range f.transactionsToRebroadcast { - txIDs[i] = consensushashing.TransactionID(tx) - i++ - } - return txIDs -} - // SharedRequestedTransactions returns a *transactionrelay.SharedRequestedTransactions for sharing // data about requested transactions between different peers. func (f *FlowContext) SharedRequestedTransactions() *transactionrelay.SharedRequestedTransactions { diff --git a/app/protocol/flows/testing/handle_relay_invs_test.go b/app/protocol/flows/testing/handle_relay_invs_test.go index adbd838d0..590889dc7 100644 --- a/app/protocol/flows/testing/handle_relay_invs_test.go +++ b/app/protocol/flows/testing/handle_relay_invs_test.go @@ -273,6 +273,10 @@ func (f *fakeRelayInvsContext) GetVirtualInfo() (*externalapi.VirtualInfo, error panic(errors.Errorf("called unimplemented function from test '%s'", f.testName)) } +func (f *fakeRelayInvsContext) GetVirtualDAAScore() (uint64, error) { + panic(errors.Errorf("called unimplemented function from test '%s'", f.testName)) +} + func (f *fakeRelayInvsContext) IsValidPruningPoint(blockHash *externalapi.DomainHash) (bool, error) { f.rwLock.RLock() defer f.rwLock.RUnlock() diff --git a/app/protocol/flows/transactionrelay/handle_relayed_transactions.go b/app/protocol/flows/transactionrelay/handle_relayed_transactions.go index 4558978c1..9a3466101 100644 --- a/app/protocol/flows/transactionrelay/handle_relayed_transactions.go +++ b/app/protocol/flows/transactionrelay/handle_relayed_transactions.go @@ -173,17 +173,18 @@ func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransact expectedID, txID) } - err = flow.Domain().MiningManager().ValidateAndInsertTransaction(tx, true) + acceptedTransactions, err := + flow.Domain().MiningManager().ValidateAndInsertTransaction(tx, false, true) if err != nil { ruleErr := &mempool.RuleError{} if !errors.As(err, ruleErr) { return errors.Wrapf(err, "failed to process transaction %s", txID) } - shouldBan := true + shouldBan := false if txRuleErr := (&mempool.TxRuleError{}); errors.As(ruleErr.Err, txRuleErr) { - if txRuleErr.RejectCode != mempool.RejectInvalid { - shouldBan = false + if txRuleErr.RejectCode == mempool.RejectInvalid { + shouldBan = true } } @@ -193,7 +194,7 @@ func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransact return protocolerrors.Errorf(true, "rejected transaction %s: %s", txID, ruleErr) } - err = flow.broadcastAcceptedTransactions([]*externalapi.DomainTransactionID{txID}) + err = flow.broadcastAcceptedTransactions(consensushashing.TransactionIDs(acceptedTransactions)) if err != nil { return err } diff --git a/app/protocol/flows/transactionrelay/handle_relayed_transactions_test.go b/app/protocol/flows/transactionrelay/handle_relayed_transactions_test.go index 1c4e19360..463bb9add 100644 --- a/app/protocol/flows/transactionrelay/handle_relayed_transactions_test.go +++ b/app/protocol/flows/transactionrelay/handle_relayed_transactions_test.go @@ -2,16 +2,18 @@ package transactionrelay_test import ( "errors" + "strings" + "testing" + "github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay" "github.com/kaspanet/kaspad/app/protocol/protocolerrors" "github.com/kaspanet/kaspad/domain" "github.com/kaspanet/kaspad/domain/consensus" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool" "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/util/panics" - "strings" - "testing" "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/infrastructure/config" @@ -63,7 +65,7 @@ func TestHandleRelayedTransactionsNotFound(t *testing.T) { if err != nil { t.Fatalf("Failed to create a NetAdapter: %v", err) } - domainInstance, err := domain.New(consensusConfig, tc.Database()) + domainInstance, err := domain.New(consensusConfig, mempool.DefaultConfig(&consensusConfig.Params), tc.Database()) if err != nil { t.Fatalf("Failed to set up a domain instance: %v", err) } @@ -156,7 +158,7 @@ func TestOnClosedIncomingRoute(t *testing.T) { if err != nil { t.Fatalf("Failed to creat a NetAdapter : %v", err) } - domainInstance, err := domain.New(consensusConfig, tc.Database()) + domainInstance, err := domain.New(consensusConfig, mempool.DefaultConfig(&consensusConfig.Params), tc.Database()) if err != nil { t.Fatalf("Failed to set up a domain instance: %v", err) } diff --git a/app/protocol/flows/transactionrelay/handle_requested_transactions_test.go b/app/protocol/flows/transactionrelay/handle_requested_transactions_test.go index b1d02b74f..11c49b4c5 100644 --- a/app/protocol/flows/transactionrelay/handle_requested_transactions_test.go +++ b/app/protocol/flows/transactionrelay/handle_requested_transactions_test.go @@ -1,19 +1,21 @@ package transactionrelay_test import ( + "testing" + "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay" "github.com/kaspanet/kaspad/domain" "github.com/kaspanet/kaspad/domain/consensus" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool" "github.com/kaspanet/kaspad/infrastructure/config" "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/infrastructure/network/netadapter" "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" "github.com/kaspanet/kaspad/util/panics" "github.com/pkg/errors" - "testing" ) // TestHandleRequestedTransactionsNotFound tests the flow of HandleRequestedTransactions @@ -34,7 +36,7 @@ func TestHandleRequestedTransactionsNotFound(t *testing.T) { if err != nil { t.Fatalf("Failed to create a NetAdapter: %v", err) } - domainInstance, err := domain.New(consensusConfig, tc.Database()) + domainInstance, err := domain.New(consensusConfig, mempool.DefaultConfig(&consensusConfig.Params), tc.Database()) if err != nil { t.Fatalf("Failed to set up a domain Instance: %v", err) } diff --git a/app/protocol/manager.go b/app/protocol/manager.go index 759e4db18..2a92bcf8a 100644 --- a/app/protocol/manager.go +++ b/app/protocol/manager.go @@ -2,10 +2,11 @@ package protocol import ( "fmt" - "github.com/pkg/errors" "sync" "sync/atomic" + "github.com/pkg/errors" + "github.com/kaspanet/kaspad/domain" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -61,8 +62,8 @@ func (m *Manager) IBDPeer() *peerpkg.Peer { } // AddTransaction adds transaction to the mempool and propagates it. -func (m *Manager) AddTransaction(tx *externalapi.DomainTransaction) error { - return m.context.AddTransaction(tx) +func (m *Manager) AddTransaction(tx *externalapi.DomainTransaction, allowOrphan bool) error { + return m.context.AddTransaction(tx, allowOrphan) } // AddBlock adds the given block to the DAG and propagates it. diff --git a/app/rpc/manager.go b/app/rpc/manager.go index 51f5ff08f..40bb25d3e 100644 --- a/app/rpc/manager.go +++ b/app/rpc/manager.go @@ -162,12 +162,12 @@ func (m *Manager) notifyVirtualDaaScoreChanged() error { onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyVirtualDaaScoreChanged") defer onEnd() - virtualInfo, err := m.context.Domain.Consensus().GetVirtualInfo() + virtualDAAScore, err := m.context.Domain.Consensus().GetVirtualDAAScore() if err != nil { return err } - notification := appmessage.NewVirtualDaaScoreChangedNotificationMessage(virtualInfo.DAAScore) + notification := appmessage.NewVirtualDaaScoreChangedNotificationMessage(virtualDAAScore) return m.context.NotificationManager.NotifyVirtualDaaScoreChanged(notification) } diff --git a/app/rpc/rpchandlers/submit_transaction.go b/app/rpc/rpchandlers/submit_transaction.go index e7174c60e..e962181b8 100644 --- a/app/rpc/rpchandlers/submit_transaction.go +++ b/app/rpc/rpchandlers/submit_transaction.go @@ -21,7 +21,7 @@ func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, requ } transactionID := consensushashing.TransactionID(domainTransaction) - err = context.ProtocolManager.AddTransaction(domainTransaction) + err = context.ProtocolManager.AddTransaction(domainTransaction, submitTransactionRequest.AllowOrphan) if err != nil { if !errors.As(err, &mempool.RuleError{}) { return nil, err diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 425a9b808..25acfc509 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -401,6 +401,15 @@ func (s *consensus) GetVirtualInfo() (*externalapi.VirtualInfo, error) { }, nil } +func (s *consensus) GetVirtualDAAScore() (uint64, error) { + s.lock.Lock() + defer s.lock.Unlock() + + stagingArea := model.NewStagingArea() + + return s.daaBlocksStore.DAAScore(s.databaseContext, stagingArea, model.VirtualBlockHash) +} + func (s *consensus) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) { s.lock.Lock() defer s.lock.Unlock() diff --git a/domain/consensus/model/externalapi/consensus.go b/domain/consensus/model/externalapi/consensus.go index c565a0f90..c79a8a1ef 100644 --- a/domain/consensus/model/externalapi/consensus.go +++ b/domain/consensus/model/externalapi/consensus.go @@ -28,6 +28,7 @@ type Consensus interface { GetSyncInfo() (*SyncInfo, error) Tips() ([]*DomainHash, error) GetVirtualInfo() (*VirtualInfo, error) + GetVirtualDAAScore() (uint64, error) IsValidPruningPoint(blockHash *DomainHash) (bool, error) GetVirtualSelectedParentChainFromBlock(blockHash *DomainHash) (*SelectedChainPath, error) IsInSelectedParentChainOf(blockHashA *DomainHash, blockHashB *DomainHash) (bool, error) diff --git a/domain/consensus/utils/consensushashing/transaction.go b/domain/consensus/utils/consensushashing/transaction.go index d59f30da0..a364a7ae6 100644 --- a/domain/consensus/utils/consensushashing/transaction.go +++ b/domain/consensus/utils/consensushashing/transaction.go @@ -65,6 +65,15 @@ func TransactionID(tx *externalapi.DomainTransaction) *externalapi.DomainTransac return tx.ID } +// TransactionIDs converts the provided slice of DomainTransactions to a corresponding slice of TransactionIDs +func TransactionIDs(txs []*externalapi.DomainTransaction) []*externalapi.DomainTransactionID { + txIDs := make([]*externalapi.DomainTransactionID, len(txs)) + for i, tx := range txs { + txIDs[i] = TransactionID(tx) + } + return txIDs +} + func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodingFlags txEncoding) error { err := binaryserializer.PutUint16(w, tx.Version) if err != nil { diff --git a/domain/domain.go b/domain/domain.go index 87d7fd3e3..ddb914803 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1,16 +1,18 @@ package domain import ( + "sync" + "sync/atomic" + "unsafe" + "github.com/kaspanet/kaspad/domain/consensus" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/miningmanager" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool" "github.com/kaspanet/kaspad/domain/prefixmanager" "github.com/kaspanet/kaspad/domain/prefixmanager/prefix" infrastructuredatabase "github.com/kaspanet/kaspad/infrastructure/db/database" "github.com/pkg/errors" - "sync" - "sync/atomic" - "unsafe" ) // Domain provides a reference to the domain's external aps @@ -157,7 +159,7 @@ func (d *domain) DeleteStagingConsensus() error { } // New instantiates a new instance of a Domain object -func New(consensusConfig *consensus.Config, db infrastructuredatabase.Database) (Domain, error) { +func New(consensusConfig *consensus.Config, mempoolConfig *mempool.Config, db infrastructuredatabase.Database) (Domain, error) { err := prefixmanager.DeleteInactivePrefix(db) if err != nil { return nil, err @@ -183,7 +185,7 @@ func New(consensusConfig *consensus.Config, db infrastructuredatabase.Database) } miningManagerFactory := miningmanager.NewFactory() - miningManager := miningManagerFactory.NewMiningManager(consensusInstance, &consensusConfig.Params) + miningManager := miningManagerFactory.NewMiningManager(consensusInstance, &consensusConfig.Params, mempoolConfig) return &domain{ consensus: &consensusInstance, diff --git a/domain/domain_test.go b/domain/domain_test.go index 9e0982b22..80842179e 100644 --- a/domain/domain_test.go +++ b/domain/domain_test.go @@ -2,16 +2,18 @@ package domain_test import ( "fmt" + "io/ioutil" + "os" + "strings" + "testing" + "github.com/kaspanet/kaspad/domain" "github.com/kaspanet/kaspad/domain/consensus" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool" "github.com/kaspanet/kaspad/infrastructure/db/database/ldb" - "io/ioutil" - "os" - "strings" - "testing" ) func TestCreateStagingConsensus(t *testing.T) { @@ -27,7 +29,7 @@ func TestCreateStagingConsensus(t *testing.T) { t.Fatalf("NewLevelDB: %+v", err) } - domainInstance, err := domain.New(consensusConfig, db) + domainInstance, err := domain.New(consensusConfig, mempool.DefaultConfig(&consensusConfig.Params), db) if err != nil { t.Fatalf("New: %+v", err) } @@ -101,7 +103,7 @@ func TestCreateStagingConsensus(t *testing.T) { t.Fatalf("ValidateAndInsertBlock: %+v", err) } - domainInstance2, err := domain.New(consensusConfig, db) + domainInstance2, err := domain.New(consensusConfig, mempool.DefaultConfig(&consensusConfig.Params), db) if err != nil { t.Fatalf("New: %+v", err) } diff --git a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go index cff952d86..da443faa2 100644 --- a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go +++ b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go @@ -139,7 +139,7 @@ func (btb *blockTemplateBuilder) GetBlockTemplate(coinbaseData *consensusexterna for _, tx := range invalidTxsErr.InvalidTransactions { invalidTxs = append(invalidTxs, tx.Transaction) } - err = btb.mempool.RemoveTransactions(invalidTxs) + err = btb.mempool.RemoveTransactions(invalidTxs, true) if err != nil { // mempool.RemoveTransactions might return errors in situations that are perfectly fine in this context. // TODO: Once the mempool invariants are clear, this should be converted back `return nil, err`: diff --git a/domain/miningmanager/factory.go b/domain/miningmanager/factory.go index a578c9bba..204d5be6a 100644 --- a/domain/miningmanager/factory.go +++ b/domain/miningmanager/factory.go @@ -9,14 +9,16 @@ import ( // Factory instantiates new mining managers type Factory interface { - NewMiningManager(consensus externalapi.Consensus, params *dagconfig.Params) MiningManager + NewMiningManager(consensus externalapi.Consensus, params *dagconfig.Params, mempoolConfig *mempoolpkg.Config) MiningManager } type factory struct{} // NewMiningManager instantiate a new mining manager -func (f *factory) NewMiningManager(consensus externalapi.Consensus, params *dagconfig.Params) MiningManager { - mempool := mempoolpkg.New(consensus, params) +func (f *factory) NewMiningManager(consensus externalapi.Consensus, params *dagconfig.Params, + mempoolConfig *mempoolpkg.Config) MiningManager { + + mempool := mempoolpkg.New(mempoolConfig, consensus) blockTemplateBuilder := blocktemplatebuilder.New(consensus, mempool, params.MaxMassAcceptedByBlock) return &miningManager{ diff --git a/domain/miningmanager/mempool/README.md b/domain/miningmanager/mempool/README.md deleted file mode 100644 index aed25cbd5..000000000 --- a/domain/miningmanager/mempool/README.md +++ /dev/null @@ -1,72 +0,0 @@ -mempool -======= - -[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](https://choosealicense.com/licenses/isc/) -[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/kaspanet/kaspad/mempool) - -Package mempool provides a policy-enforced pool of unmined kaspa transactions. - -A key responsbility of the kaspa network is mining user-generated transactions -into blocks. In order to facilitate this, the mining process relies on having a -readily-available source of transactions to include in a block that is being -solved. - -At a high level, this package satisfies that requirement by providing an -in-memory pool of fully validated transactions that can also optionally be -further filtered based upon a configurable policy. - -One of the policy configuration options controls whether or not "standard" -transactions are accepted. In essence, a "standard" transaction is one that -satisfies a fairly strict set of requirements that are largely intended to help -provide fair use of the system to all users. It is important to note that what -is considered a "standard" transaction changes over time. For some insight, at -the time of this writing, an example of _some_ of the criteria that are required -for a transaction to be considered standard are that it is of the most-recently -supported version, finalized, does not exceed a specific size, and only consists -of specific script forms. - -Since this package does not deal with other kaspa specifics such as network -communication and transaction relay, it returns a list of transactions that were -accepted which gives the caller a high level of flexibility in how they want to -proceed. Typically, this will involve things such as relaying the transactions -to other peers on the network and notifying the mining process that new -transactions are available. - -This package has intentionally been designed so it can be used as a standalone -package for any projects needing the ability create an in-memory pool of bitcoin -transactions that are not only valid by consensus rules, but also adhere to a -configurable policy. - -## Feature Overview - -The following is a quick overview of the major features. It is not intended to -be an exhaustive list. - -- Maintain a pool of fully validated transactions - - Reject non-fully-spent duplicate transactions - - Reject coinbase transactions - - Reject double spends (both from the DAG and other transactions in pool) - - Reject invalid transactions according to the network consensus rules - - Full script execution and validation with signature cache support - - Individual transaction query support -- Orphan transaction support (transactions that spend from unknown outputs) - - Configurable limits (see transaction acceptance policy) - - Automatic addition of orphan transactions that are no longer orphans as new - transactions are added to the pool - - Individual orphan transaction query support -- Configurable transaction acceptance policy - - Option to accept or reject standard transactions - - Option to accept or reject transactions based on priority calculations - - Rate limiting of low-fee and free transactions - - Non-zero fee threshold - - Max signature operations per transaction - - Max orphan transaction size - - Max number of orphan transactions allowed -- Additional metadata tracking for each transaction - - Timestamp when the transaction was added to the pool - - Most recent block height when the transaction was added to the pool - - The fee the transaction pays - - The starting priority for the transaction -- Manual control of transaction removal - - Recursive removal of all dependent transactions - diff --git a/domain/miningmanager/mempool/check_transaction_standard.go b/domain/miningmanager/mempool/check_transaction_standard.go new file mode 100644 index 000000000..9026be701 --- /dev/null +++ b/domain/miningmanager/mempool/check_transaction_standard.go @@ -0,0 +1,211 @@ +package mempool + +import ( + "fmt" + + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" + "github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize" + "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" + "github.com/kaspanet/kaspad/util" +) + +const ( + // maxStandardP2SHSigOps is the maximum number of signature operations + // that are considered standard in a pay-to-script-hash script. + maxStandardP2SHSigOps = 15 + + // maximumStandardSignatureScriptSize is the maximum size allowed for a + // transaction input signature script to be considered standard. This + // value allows for a 15-of-15 CHECKMULTISIG pay-to-script-hash with + // compressed keys. + // + // The form of the overall script is: OP_0 <15 signatures> OP_PUSHDATA2 + // <2 bytes len> [OP_15 <15 pubkeys> OP_15 OP_CHECKMULTISIG] + // + // For the p2sh script portion, each of the 15 compressed pubkeys are + // 33 bytes (plus one for the OP_DATA_33 opcode), and the thus it totals + // to (15*34)+3 = 513 bytes. Next, each of the 15 signatures is a max + // of 73 bytes (plus one for the OP_DATA_73 opcode). Also, there is one + // extra byte for the initial extra OP_0 push and 3 bytes for the + // OP_PUSHDATA2 needed to specify the 513 bytes for the script push. + // That brings the total to 1+(15*74)+3+513 = 1627. This value also + // adds a few extra bytes to provide a little buffer. + // (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650 + maximumStandardSignatureScriptSize = 1650 + + // maximumStandardTransactionSize is the maximum size allowed for transactions that + // are considered standard and will therefore be relayed and considered + // for mining. + maximumStandardTransactionSize = 100000 +) + +func (mp *mempool) checkTransactionStandardInIsolation(transaction *externalapi.DomainTransaction) error { + // The transaction must be a currently supported version. + // + // This check is currently mirrored in consensus. + // However, in a later version of Kaspa the consensus-valid transaction version range might diverge from the + // standard transaction version range, and thus the validation should happen in both levels. + if transaction.Version > mp.config.MaximumStandardTransactionVersion || + transaction.Version < mp.config.MinimumStandardTransactionVersion { + str := fmt.Sprintf("transaction version %d is not in the valid range of %d-%d", transaction.Version, + mp.config.MinimumStandardTransactionVersion, mp.config.MaximumStandardTransactionVersion) + return transactionRuleError(RejectNonstandard, str) + } + + // Since extremely large transactions with a lot of inputs can cost + // almost as much to process as the sender fees, limit the maximum + // size of a transaction. This also helps mitigate CPU exhaustion + // attacks. + serializedLength := estimatedsize.TransactionEstimatedSerializedSize(transaction) + if serializedLength > maximumStandardTransactionSize { + str := fmt.Sprintf("transaction size of %d is larger than max allowed size of %d", + serializedLength, maximumStandardTransactionSize) + return transactionRuleError(RejectNonstandard, str) + } + + for i, input := range transaction.Inputs { + // Each transaction input signature script must not exceed the + // maximum size allowed for a standard transaction. See + // the comment on maximumStandardSignatureScriptSize for more details. + signatureScriptLen := len(input.SignatureScript) + if signatureScriptLen > maximumStandardSignatureScriptSize { + str := fmt.Sprintf("transaction input %d: signature script size of %d bytes is larger than the "+ + "maximum allowed size of %d bytes", i, signatureScriptLen, maximumStandardSignatureScriptSize) + return transactionRuleError(RejectNonstandard, str) + } + } + + // None of the output public key scripts can be a non-standard script or be "dust". + for i, output := range transaction.Outputs { + if output.ScriptPublicKey.Version > constants.MaxScriptPublicKeyVersion { + return transactionRuleError(RejectNonstandard, "The version of the scriptPublicKey is higher than the known version.") + } + scriptClass := txscript.GetScriptClass(output.ScriptPublicKey.Script) + if scriptClass == txscript.NonStandardTy { + str := fmt.Sprintf("transaction output %d: non-standard script form", i) + return transactionRuleError(RejectNonstandard, str) + } + + if mp.IsTransactionOutputDust(output) { + str := fmt.Sprintf("transaction output %d: payment "+ + "of %d is dust", i, output.Value) + return transactionRuleError(RejectDust, str) + } + } + + return nil +} + +// IsTransactionOutputDust returns whether or not the passed transaction output amount +// is considered dust or not based on the configured minimum transaction relay fee. +// Dust is defined in terms of the minimum transaction relay fee. In +// particular, if the cost to the network to spend coins is more than 1/3 of the +// minimum transaction relay fee, it is considered dust. +// +// It is exported for use by transaction generators and wallets +func (mp *mempool) IsTransactionOutputDust(output *externalapi.DomainTransactionOutput) bool { + // Unspendable outputs are considered dust. + if txscript.IsUnspendable(output.ScriptPublicKey.Script) { + return true + } + + // The total serialized size consists of the output and the associated + // input script to redeem it. Since there is no input script + // to redeem it yet, use the minimum size of a typical input script. + // + // Pay-to-pubkey bytes breakdown: + // + // Output to pubkey (43 bytes): + // 8 value, 1 script len, 34 script [1 OP_DATA_32, + // 32 pubkey, 1 OP_CHECKSIG] + // + // Input (105 bytes): + // 36 prev outpoint, 1 script len, 64 script [1 OP_DATA_64, + // 64 sig], 4 sequence + // + // The most common scripts are pay-to-pubkey, and as per the above + // breakdown, the minimum size of a p2pk input script is 148 bytes. So + // that figure is used. + totalSerializedSize := estimatedsize.TransactionOutputEstimatedSerializedSize(output) + 148 + + // The output is considered dust if the cost to the network to spend the + // coins is more than 1/3 of the minimum free transaction relay fee. + // mp.config.MinimumRelayTransactionFee is in sompi/KB, so multiply + // by 1000 to convert to bytes. + // + // Using the typical values for a pay-to-pubkey transaction from + // the breakdown above and the default minimum free transaction relay + // fee of 1000, this equates to values less than 546 sompi being + // considered dust. + // + // The following is equivalent to (value/totalSerializedSize) * (1/3) * 1000 + // without needing to do floating point math. + return output.Value*1000/(3*totalSerializedSize) < uint64(mp.config.MinimumRelayTransactionFee) +} + +// checkTransactionStandardInContext performs a series of checks on a transaction's +// inputs to ensure they are "standard". A standard transaction input within the +// context of this function is one whose referenced public key script is of a +// standard form and, for pay-to-script-hash, does not have more than +// maxStandardP2SHSigOps signature operations. +// In addition, makes sure that the transaction's fee is above the minimum for acceptance +// into the mempool and relay +func (mp *mempool) checkTransactionStandardInContext(transaction *externalapi.DomainTransaction) error { + for i, input := range transaction.Inputs { + // It is safe to elide existence and index checks here since + // they have already been checked prior to calling this + // function. + utxoEntry := input.UTXOEntry + originScriptPubKey := utxoEntry.ScriptPublicKey() + switch txscript.GetScriptClass(originScriptPubKey.Script) { + case txscript.ScriptHashTy: + numSigOps := txscript.GetPreciseSigOpCount( + input.SignatureScript, originScriptPubKey, true) + if numSigOps > maxStandardP2SHSigOps { + str := fmt.Sprintf("transaction input #%d has %d signature operations which is more "+ + "than the allowed max amount of %d", i, numSigOps, maxStandardP2SHSigOps) + return transactionRuleError(RejectNonstandard, str) + } + + case txscript.NonStandardTy: + str := fmt.Sprintf("transaction input #%d has a non-standard script form", i) + return transactionRuleError(RejectNonstandard, str) + } + } + + serializedSize := estimatedsize.TransactionEstimatedSerializedSize(transaction) + minimumFee := mp.minimumRequiredTransactionRelayFee(serializedSize) + if transaction.Fee < minimumFee { + str := fmt.Sprintf("transaction %s has %d fees which is under the required amount of %d", + consensushashing.TransactionID(transaction), transaction.Fee, minimumFee) + return transactionRuleError(RejectInsufficientFee, str) + } + + return nil +} + +// minimumRequiredTransactionRelayFee returns the minimum transaction fee required for a +// transaction with the passed serialized size to be accepted into the memory +// pool and relayed. +func (mp *mempool) minimumRequiredTransactionRelayFee(serializedSize uint64) uint64 { + // Calculate the minimum fee for a transaction to be allowed into the + // mempool and relayed by scaling the base fee. MinimumRelayTransactionFee is in + // sompi/kB so multiply by serializedSize (which is in bytes) and + // divide by 1000 to get minimum sompis. + minimumFee := (serializedSize * uint64(mp.config.MinimumRelayTransactionFee)) / 1000 + + if minimumFee == 0 && mp.config.MinimumRelayTransactionFee > 0 { + minimumFee = uint64(mp.config.MinimumRelayTransactionFee) + } + + // Set the minimum fee to the maximum possible value if the calculated + // fee is not in the valid range for monetary amounts. + if minimumFee > util.MaxSompi { + minimumFee = util.MaxSompi + } + + return minimumFee +} diff --git a/domain/miningmanager/mempool/check_transaction_standard_test.go b/domain/miningmanager/mempool/check_transaction_standard_test.go new file mode 100644 index 000000000..64e959056 --- /dev/null +++ b/domain/miningmanager/mempool/check_transaction_standard_test.go @@ -0,0 +1,349 @@ +// Copyright (c) 2013-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package mempool + +import ( + "bytes" + "math" + "testing" + + "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" + + "github.com/kaspanet/kaspad/domain/consensus" + + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" + + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" + "github.com/kaspanet/kaspad/util" + "github.com/pkg/errors" +) + +func TestCalcMinRequiredTxRelayFee(t *testing.T) { + tests := []struct { + name string // test description. + size uint64 // Transaction size in bytes. + minimumRelayTransactionFee util.Amount // minimum relay transaction fee. + want uint64 // Expected fee. + }{ + { + // Ensure combination of size and fee that are less than 1000 + // produce a non-zero fee. + "250 bytes with relay fee of 3", + 250, + 3, + 3, + }, + { + "100 bytes with default minimum relay fee", + 100, + defaultMinimumRelayTransactionFee, + 100, + }, + { + "max standard tx size with default minimum relay fee", + maximumStandardTransactionSize, + defaultMinimumRelayTransactionFee, + 100000, + }, + { + "max standard tx size with max sompi relay fee", + maximumStandardTransactionSize, + util.MaxSompi, + util.MaxSompi, + }, + { + "1500 bytes with 5000 relay fee", + 1500, + 5000, + 7500, + }, + { + "1500 bytes with 3000 relay fee", + 1500, + 3000, + 4500, + }, + { + "782 bytes with 5000 relay fee", + 782, + 5000, + 3910, + }, + { + "782 bytes with 3000 relay fee", + 782, + 3000, + 2346, + }, + { + "782 bytes with 2550 relay fee", + 782, + 2550, + 1994, + }, + } + + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + factory := consensus.NewFactory() + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCalcMinRequiredTxRelayFee") + if err != nil { + t.Fatalf("Error setting up consensus: %+v", err) + } + defer teardown(false) + + for _, test := range tests { + mempoolConfig := DefaultConfig(tc.DAGParams()) + mempoolConfig.MinimumRelayTransactionFee = test.minimumRelayTransactionFee + mempool := New(mempoolConfig, tc).(*mempool) + + got := mempool.minimumRequiredTransactionRelayFee(test.size) + if got != test.want { + t.Errorf("TestCalcMinRequiredTxRelayFee test '%s' "+ + "failed: got %v want %v", test.name, got, + test.want) + } + } + }) +} + +func TestIsTransactionOutputDust(t *testing.T) { + scriptPublicKey := &externalapi.ScriptPublicKey{ + []byte{0x76, 0xa9, 0x21, 0x03, 0x2f, 0x7e, 0x43, + 0x0a, 0xa4, 0xc9, 0xd1, 0x59, 0x43, 0x7e, 0x84, 0xb9, + 0x75, 0xdc, 0x76, 0xd9, 0x00, 0x3b, 0xf0, 0x92, 0x2c, + 0xf3, 0xaa, 0x45, 0x28, 0x46, 0x4b, 0xab, 0x78, 0x0d, + 0xba, 0x5e}, 0} + + tests := []struct { + name string // test description + txOut externalapi.DomainTransactionOutput + minimumRelayTransactionFee util.Amount // minimum relay transaction fee. + isDust bool + }{ + { + // Any value is allowed with a zero relay fee. + "zero value with zero relay fee", + externalapi.DomainTransactionOutput{Value: 0, ScriptPublicKey: scriptPublicKey}, + 0, + false, + }, + { + // Zero value is dust with any relay fee" + "zero value with very small tx fee", + externalapi.DomainTransactionOutput{Value: 0, ScriptPublicKey: scriptPublicKey}, + 1, + true, + }, + { + "36 byte public key script with value 605", + externalapi.DomainTransactionOutput{Value: 605, ScriptPublicKey: scriptPublicKey}, + 1000, + true, + }, + { + "36 byte public key script with value 606", + externalapi.DomainTransactionOutput{Value: 606, ScriptPublicKey: scriptPublicKey}, + 1000, + false, + }, + { + // Maximum allowed value is never dust. + "max sompi amount is never dust", + externalapi.DomainTransactionOutput{Value: util.MaxSompi, ScriptPublicKey: scriptPublicKey}, + util.MaxSompi, + false, + }, + { + // Maximum uint64 value causes overflow. + "maximum uint64 value", + externalapi.DomainTransactionOutput{Value: math.MaxUint64, ScriptPublicKey: scriptPublicKey}, + math.MaxUint64, + true, + }, + { + // Unspendable ScriptPublicKey due to an invalid public key + // script. + "unspendable ScriptPublicKey", + externalapi.DomainTransactionOutput{Value: 5000, ScriptPublicKey: &externalapi.ScriptPublicKey{[]byte{0x01}, 0}}, + 0, // no relay fee + true, + }, + } + + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + factory := consensus.NewFactory() + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestIsTransactionOutputDust") + if err != nil { + t.Fatalf("Error setting up consensus: %+v", err) + } + defer teardown(false) + + for _, test := range tests { + mempoolConfig := DefaultConfig(tc.DAGParams()) + mempoolConfig.MinimumRelayTransactionFee = test.minimumRelayTransactionFee + mempool := New(mempoolConfig, tc).(*mempool) + + res := mempool.IsTransactionOutputDust(&test.txOut) + if res != test.isDust { + t.Errorf("Dust test '%s' failed: want %v got %v", + test.name, test.isDust, res) + } + } + }) +} + +func TestCheckTransactionStandardInIsolation(t *testing.T) { + // Create some dummy, but otherwise standard, data for transactions. + prevOutTxID := &externalapi.DomainTransactionID{} + dummyPrevOut := externalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1} + dummySigScript := bytes.Repeat([]byte{0x00}, 65) + dummyTxIn := externalapi.DomainTransactionInput{ + PreviousOutpoint: dummyPrevOut, + SignatureScript: dummySigScript, + Sequence: constants.MaxTxInSequenceNum, + } + addrHash := [32]byte{0x01} + addr, err := util.NewAddressPublicKey(addrHash[:], util.Bech32PrefixKaspaTest) + if err != nil { + t.Fatalf("NewAddressPublicKey: unexpected error: %v", err) + } + dummyScriptPublicKey, err := txscript.PayToAddrScript(addr) + if err != nil { + t.Fatalf("PayToAddrScript: unexpected error: %v", err) + } + dummyTxOut := externalapi.DomainTransactionOutput{ + Value: 100000000, // 1 KAS + ScriptPublicKey: dummyScriptPublicKey, + } + + tests := []struct { + name string + tx *externalapi.DomainTransaction + height uint64 + isStandard bool + code RejectCode + }{ + { + name: "Typical pay-to-pubkey transaction", + tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{&dummyTxOut}}, + height: 300000, + isStandard: true, + }, + { + name: "Transaction version too high", + tx: &externalapi.DomainTransaction{Version: constants.MaxTransactionVersion + 1, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{&dummyTxOut}}, + height: 300000, + isStandard: false, + code: RejectNonstandard, + }, + + { + name: "Transaction size is too large", + tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{ + Value: 0, + ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, maximumStandardTransactionSize+1), 0}, + }}}, + height: 300000, + isStandard: false, + code: RejectNonstandard, + }, + { + name: "Signature script size is too large", + tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{{ + PreviousOutpoint: dummyPrevOut, + SignatureScript: bytes.Repeat([]byte{0x00}, maximumStandardSignatureScriptSize+1), + Sequence: constants.MaxTxInSequenceNum, + }}, Outputs: []*externalapi.DomainTransactionOutput{&dummyTxOut}}, + height: 300000, + isStandard: false, + code: RejectNonstandard, + }, + { + name: "Valid but non standard public key script", + tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{ + Value: 100000000, + ScriptPublicKey: &externalapi.ScriptPublicKey{[]byte{txscript.OpTrue}, 0}, + }}}, + height: 300000, + isStandard: false, + code: RejectNonstandard, + }, + { //Todo : check on ScriptPublicKey type. + name: "Dust output", + tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{ + Value: 0, + ScriptPublicKey: dummyScriptPublicKey, + }}}, + height: 300000, + isStandard: false, + code: RejectDust, + }, + { + name: "Nulldata transaction", + tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{ + Value: 0, + ScriptPublicKey: &externalapi.ScriptPublicKey{[]byte{txscript.OpReturn}, 0}, + }}}, + height: 300000, + isStandard: false, + code: RejectNonstandard, + }, + } + + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + factory := consensus.NewFactory() + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckTransactionStandardInIsolation") + if err != nil { + t.Fatalf("Error setting up consensus: %+v", err) + } + defer teardown(false) + + for _, test := range tests { + mempoolConfig := DefaultConfig(tc.DAGParams()) + mempool := New(mempoolConfig, tc).(*mempool) + + // Ensure standardness is as expected. + err := mempool.checkTransactionStandardInIsolation(test.tx) + if err == nil && test.isStandard { + // Test passes since function returned standard for a + // transaction which is intended to be standard. + continue + } + if err == nil && !test.isStandard { + t.Errorf("checkTransactionStandardInIsolation (%s): standard when "+ + "it should not be", test.name) + continue + } + if err != nil && test.isStandard { + t.Errorf("checkTransactionStandardInIsolation (%s): nonstandard "+ + "when it should not be: %v", test.name, err) + continue + } + + // Ensure error type is a TxRuleError inside of a RuleError. + var ruleErr RuleError + if !errors.As(err, &ruleErr) { + t.Errorf("checkTransactionStandardInIsolation (%s): unexpected "+ + "error type - got %T", test.name, err) + continue + } + txRuleErr, ok := ruleErr.Err.(TxRuleError) + if !ok { + t.Errorf("checkTransactionStandardInIsolation (%s): unexpected "+ + "error type - got %T", test.name, ruleErr.Err) + continue + } + + // Ensure the reject code is the expected one. + if txRuleErr.RejectCode != test.code { + t.Errorf("checkTransactionStandardInIsolation (%s): unexpected "+ + "error code - got %v, want %v", test.name, + txRuleErr.RejectCode, test.code) + continue + } + } + }) +} diff --git a/domain/miningmanager/mempool/config.go b/domain/miningmanager/mempool/config.go new file mode 100644 index 000000000..6cc3ab7ed --- /dev/null +++ b/domain/miningmanager/mempool/config.go @@ -0,0 +1,70 @@ +package mempool + +import ( + "time" + + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" + + "github.com/kaspanet/kaspad/util" + + "github.com/kaspanet/kaspad/domain/dagconfig" +) + +const ( + defaultMaximumTransactionCount = 1_000_000 + + defaultTransactionExpireIntervalSeconds uint64 = 60 + defaultTransactionExpireScanIntervalSeconds uint64 = 10 + defaultOrphanExpireIntervalSeconds uint64 = 60 + defaultOrphanExpireScanIntervalSeconds uint64 = 10 + + defaultMaximumOrphanTransactionSize = 100000 + // defaultMaximumOrphanTransactionCount should remain small as long as we have recursion in + // removeOrphans when removeRedeemers = true + defaultMaximumOrphanTransactionCount = 50 + + defaultMinimumRelayTransactionFee = util.Amount(1000) + + // Standard transaction version range might be different from what consensus accepts, therefore + // we define separate values in mempool. + // However, currently there's exactly one transaction version, so mempool accepts the same version + // as consensus. + defaultMinimumStandardTransactionVersion = constants.MaxTransactionVersion + defaultMaximumStandardTransactionVersion = constants.MaxTransactionVersion +) + +// Config represents a mempool configuration +type Config struct { + MaximumTransactionCount uint64 + TransactionExpireIntervalDAAScore uint64 + TransactionExpireScanIntervalDAAScore uint64 + OrphanExpireIntervalDAAScore uint64 + OrphanExpireScanIntervalDAAScore uint64 + MaximumOrphanTransactionSize uint64 + MaximumOrphanTransactionCount uint64 + AcceptNonStandard bool + MaximumMassAcceptedByBlock uint64 + MinimumRelayTransactionFee util.Amount + MinimumStandardTransactionVersion uint16 + MaximumStandardTransactionVersion uint16 +} + +// DefaultConfig returns the default mempool configuration +func DefaultConfig(dagParams *dagconfig.Params) *Config { + targetBlocksPerSecond := uint64(time.Second / dagParams.TargetTimePerBlock) + + return &Config{ + MaximumTransactionCount: defaultMaximumTransactionCount, + TransactionExpireIntervalDAAScore: defaultTransactionExpireIntervalSeconds / targetBlocksPerSecond, + TransactionExpireScanIntervalDAAScore: defaultTransactionExpireScanIntervalSeconds / targetBlocksPerSecond, + OrphanExpireIntervalDAAScore: defaultOrphanExpireIntervalSeconds / targetBlocksPerSecond, + OrphanExpireScanIntervalDAAScore: defaultOrphanExpireScanIntervalSeconds / targetBlocksPerSecond, + MaximumOrphanTransactionSize: defaultMaximumOrphanTransactionSize, + MaximumOrphanTransactionCount: defaultMaximumOrphanTransactionCount, + AcceptNonStandard: dagParams.RelayNonStdTxs, + MaximumMassAcceptedByBlock: dagParams.MaxMassAcceptedByBlock, + MinimumRelayTransactionFee: defaultMinimumRelayTransactionFee, + MinimumStandardTransactionVersion: defaultMinimumStandardTransactionVersion, + MaximumStandardTransactionVersion: defaultMaximumStandardTransactionVersion, + } +} diff --git a/domain/miningmanager/mempool/doc.go b/domain/miningmanager/mempool/doc.go deleted file mode 100644 index a5b38ab16..000000000 --- a/domain/miningmanager/mempool/doc.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Package mempool provides a policy-enforced pool of unmined kaspa transactions. - -A key responsbility of the kaspa network is mining user-generated transactions -into blocks. In order to facilitate this, the mining process relies on having a -readily-available source of transactions to include in a block that is being -solved. - -At a high level, this package satisfies that requirement by providing an -in-memory pool of fully validated transactions that can also optionally be -further filtered based upon a configurable policy. - -One of the policy configuration options controls whether or not "standard" -transactions are accepted. In essence, a "standard" transaction is one that -satisfies a fairly strict set of requirements that are largely intended to help -provide fair use of the system to all users. It is important to note that what -is considered a "standard" transaction changes over time. For some insight, at -the time of this writing, an example of SOME of the criteria that are required -for a transaction to be considered standard are that it is of the most-recently -supported version, finalized, does not exceed a specific size, and only consists -of specific script forms. - -Since this package does not deal with other kaspa specifics such as network -communication and transaction relay, it returns a list of transactions that were -accepted which gives the caller a high level of flexibility in how they want to -proceed. Typically, this will involve things such as relaying the transactions -to other peers on the network and notifying the mining process that new -transactions are available. - -Feature Overview - -The following is a quick overview of the major features. It is not intended to -be an exhaustive list. - - - Maintain a pool of fully validated transactions - - Reject non-fully-spent duplicate transactions - - Reject coinbase transactions - - Reject double spends (both from the DAG and other transactions in pool) - - Reject invalid transactions according to the network consensus rules - - Full script execution and validation with signature cache support - - Individual transaction query support - - Orphan transaction support (transactions that spend from unknown outputs) - - Configurable limits (see transaction acceptance policy) - - Automatic addition of orphan transactions that are no longer orphans as new - transactions are added to the pool - - Individual orphan transaction query support - - Configurable transaction acceptance policy - - Option to accept or reject standard transactions - - Option to accept or reject transactions based on priority calculations - - Max signature operations per transaction - - Max number of orphan transactions allowed - - Additional metadata tracking for each transaction - - Timestamp when the transaction was added to the pool - - The fee the transaction pays - - The starting priority for the transaction - - Manual control of transaction removal - - Recursive removal of all dependent transactions - -Errors - -Errors returned by this package are either the raw errors provided by underlying -calls or of type mempool.RuleError. Since there are two classes of rules -(mempool acceptance rules and blockDAG (consensus) acceptance rules), the -mempool.RuleError type contains a single Err field which will, in turn, either -be a mempool.TxRuleError or a ruleerrors.RuleError. The first indicates a -violation of mempool acceptance rules while the latter indicates a violation of -consensus acceptance rules. This allows the caller to easily differentiate -between unexpected errors, such as database errors, versus errors due to rule -violations through type assertions. In addition, callers can programmatically -determine the specific rule violation by type asserting the Err field to one of -the aforementioned types and examining their underlying ErrorCode field. -*/ -package mempool diff --git a/domain/miningmanager/mempool/error.go b/domain/miningmanager/mempool/error.go index bc51bb3a0..d698217d7 100644 --- a/domain/miningmanager/mempool/error.go +++ b/domain/miningmanager/mempool/error.go @@ -50,6 +50,7 @@ const ( RejectFinality RejectCode = 0x43 RejectDifficulty RejectCode = 0x44 RejectImmatureSpend RejectCode = 0x45 + RejectBadOrphan RejectCode = 0x64 ) // Map of reject codes back strings for pretty printing. @@ -58,13 +59,14 @@ var rejectCodeStrings = map[RejectCode]string{ RejectInvalid: "REJECT_INVALID", RejectObsolete: "REJECT_OBSOLETE", RejectDuplicate: "REJECT_DUPLICATE", - RejectNonstandard: "REJECT_NONSTANDARD", + RejectNonstandard: "REJECT_NON_STANDARD", RejectDust: "REJECT_DUST", - RejectInsufficientFee: "REJECT_INSUFFICIENTFEE", + RejectInsufficientFee: "REJECT_INSUFFICIENT_FEE", RejectFinality: "REJECT_FINALITY", RejectDifficulty: "REJECT_DIFFICULTY", - RejectNotRequested: "REJECT_NOTREQUESTED", - RejectImmatureSpend: "REJECT_IMMATURESPEND", + RejectNotRequested: "REJECT_NOT_REQUESTED", + RejectImmatureSpend: "REJECT_IMMATURE_SPEND", + RejectBadOrphan: "REJECT_BAD_ORPHAN", } // String returns the RejectCode in human-readable form. @@ -91,12 +93,10 @@ func (e TxRuleError) Error() string { return e.Description } -// txRuleError creates an underlying TxRuleError with the given a set of +// transactionRuleError creates an underlying TxRuleError with the given a set of // arguments and returns a RuleError that encapsulates it. -func txRuleError(c RejectCode, desc string) RuleError { - return RuleError{ - Err: TxRuleError{RejectCode: c, Description: desc}, - } +func transactionRuleError(c RejectCode, desc string) RuleError { + return newRuleError(TxRuleError{RejectCode: c, Description: desc}) } func newRuleError(err error) RuleError { diff --git a/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go b/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go new file mode 100644 index 000000000..5e6b707e1 --- /dev/null +++ b/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go @@ -0,0 +1,47 @@ +package mempool + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" + "github.com/pkg/errors" +) + +func (mp *mempool) fillInputsAndGetMissingParents(transaction *externalapi.DomainTransaction) ( + parents model.OutpointToTransactionMap, missingOutpoints []*externalapi.DomainOutpoint, err error) { + + parentsInPool := mp.transactionsPool.getParentTransactionsInPool(transaction) + + fillInputs(transaction, parentsInPool) + + err = mp.consensus.ValidateTransactionAndPopulateWithConsensusData(transaction) + if err != nil { + errMissingOutpoints := ruleerrors.ErrMissingTxOut{} + if errors.As(err, &errMissingOutpoints) { + return parentsInPool, errMissingOutpoints.MissingOutpoints, nil + } + if errors.Is(err, ruleerrors.ErrImmatureSpend) { + return nil, nil, transactionRuleError( + RejectImmatureSpend, "one of the transaction inputs spends an immature UTXO") + } + if errors.As(err, &ruleerrors.RuleError{}) { + return nil, nil, newRuleError(err) + } + return nil, nil, err + } + + return parentsInPool, nil, nil +} + +func fillInputs(transaction *externalapi.DomainTransaction, parentsInPool model.OutpointToTransactionMap) { + for _, input := range transaction.Inputs { + parent, ok := parentsInPool[input.PreviousOutpoint] + if !ok { + continue + } + relevantOutput := parent.Transaction().Outputs[input.PreviousOutpoint.Index] + input.UTXOEntry = utxo.NewUTXOEntry(relevantOutput.Value, relevantOutput.ScriptPublicKey, + false, model.UnacceptedDAAScore) + } +} diff --git a/domain/miningmanager/mempool/handle_new_block_transactions.go b/domain/miningmanager/mempool/handle_new_block_transactions.go new file mode 100644 index 000000000..344043138 --- /dev/null +++ b/domain/miningmanager/mempool/handle_new_block_transactions.go @@ -0,0 +1,62 @@ +package mempool + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" +) + +func (mp *mempool) handleNewBlockTransactions(blockTransactions []*externalapi.DomainTransaction) ( + []*externalapi.DomainTransaction, error) { + + // Skip the coinbase transaction + blockTransactions = blockTransactions[transactionhelper.CoinbaseTransactionIndex+1:] + + acceptedOrphans := []*externalapi.DomainTransaction{} + for _, transaction := range blockTransactions { + transactionID := consensushashing.TransactionID(transaction) + err := mp.removeTransaction(transactionID, false) + if err != nil { + return nil, err + } + + err = mp.removeDoubleSpends(transaction) + if err != nil { + return nil, err + } + + err = mp.orphansPool.removeOrphan(transactionID, false) + if err != nil { + return nil, err + } + + acceptedOrphansFromThisTransaction, err := mp.orphansPool.processOrphansAfterAcceptedTransaction(transaction) + if err != nil { + return nil, err + } + + acceptedOrphans = append(acceptedOrphans, acceptedOrphansFromThisTransaction...) + } + err := mp.orphansPool.expireOrphanTransactions() + if err != nil { + return nil, err + } + err = mp.transactionsPool.expireOldTransactions() + if err != nil { + return nil, err + } + + return acceptedOrphans, nil +} + +func (mp *mempool) removeDoubleSpends(transaction *externalapi.DomainTransaction) error { + for _, input := range transaction.Inputs { + if redeemer, ok := mp.mempoolUTXOSet.transactionByPreviousOutpoint[input.PreviousOutpoint]; ok { + err := mp.removeTransaction(redeemer.TransactionID(), true) + if err != nil { + return err + } + } + } + return nil +} diff --git a/domain/miningmanager/mempool/mempool.go b/domain/miningmanager/mempool/mempool.go index a73dbf321..1bd4e9036 100644 --- a/domain/miningmanager/mempool/mempool.go +++ b/domain/miningmanager/mempool/mempool.go @@ -1,1044 +1,100 @@ -// Copyright (c) 2013-2016 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - package mempool import ( - "container/list" - "fmt" - "github.com/kaspanet/kaspad/domain/dagconfig" - "sort" "sync" - "time" - "github.com/kaspanet/kaspad/infrastructure/logger" - - "github.com/kaspanet/kaspad/domain/consensus/utils/constants" - - "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" - - consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" - "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" - "github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" miningmanagermodel "github.com/kaspanet/kaspad/domain/miningmanager/model" - "github.com/kaspanet/kaspad/util" - "github.com/kaspanet/kaspad/util/mstime" - "github.com/pkg/errors" ) -const ( - // orphanTTL is the maximum amount of time an orphan is allowed to - // stay in the orphan pool before it expires and is evicted during the - // next scan. - orphanTTL = time.Minute * 15 - - // orphanExpireScanInterval is the minimum amount of time in between - // scans of the orphan pool to evict expired transactions. - orphanExpireScanInterval = time.Minute * 5 -) - -// policy houses the policy (configuration parameters) which is used to -// control the mempool. -type policy struct { - // MaxTxVersion is the transaction version that the mempool should - // accept. All transactions above this version are rejected as - // non-standard. - MaxTxVersion uint16 - - // AcceptNonStd defines whether to accept non-standard transactions. If - // true, non-standard transactions will be accepted into the mempool. - // Otherwise, all non-standard transactions will be rejected. - AcceptNonStd bool - - // MaxOrphanTxs is the maximum number of orphan transactions - // that can be queued. - MaxOrphanTxs int - - // MaxOrphanTxSize is the maximum size allowed for orphan transactions. - // This helps prevent memory exhaustion attacks from sending a lot of - // of big orphans. - MaxOrphanTxSize int - - // MinRelayTxFee defines the minimum transaction fee in KAS/kB to be - // considered a non-zero fee. - MinRelayTxFee util.Amount -} - -// mempool is used as a source of transactions that need to be mined into blocks -// and relayed to other peers. It is safe for concurrent access from multiple -// peers. type mempool struct { - pool map[consensusexternalapi.DomainTransactionID]*txDescriptor + mtx sync.RWMutex - chainedTransactions map[consensusexternalapi.DomainTransactionID]*txDescriptor - chainedTransactionByPreviousOutpoint map[consensusexternalapi.DomainOutpoint]*txDescriptor + config *Config + consensus externalapi.Consensus - orphans map[consensusexternalapi.DomainTransactionID]*orphanTx - orphansByPrev map[consensusexternalapi.DomainOutpoint]map[consensusexternalapi.DomainTransactionID]*consensusexternalapi.DomainTransaction - - mempoolUTXOSet *mempoolUTXOSet - consensus consensusexternalapi.Consensus - - orderedTransactionsByFeeRate []*consensusexternalapi.DomainTransaction - - // nextExpireScan is the time after which the orphan pool will be - // scanned in order to evict orphans. This is NOT a hard deadline as - // the scan will only run when an orphan is added to the pool as opposed - // to on an unconditional timer. - nextExpireScan mstime.Time - - mtx sync.RWMutex - policy policy - dagParams *dagconfig.Params + mempoolUTXOSet *mempoolUTXOSet + transactionsPool *transactionsPool + orphansPool *orphansPool } -// New returns a new memory pool for validating and storing standalone -// transactions until they are mined into a block. -func New(consensus consensusexternalapi.Consensus, dagParams *dagconfig.Params) miningmanagermodel.Mempool { - policy := policy{ - MaxTxVersion: constants.MaxTransactionVersion, - AcceptNonStd: dagParams.RelayNonStdTxs, - MaxOrphanTxs: 5, - MaxOrphanTxSize: 100000, - MinRelayTxFee: 1000, // 1 sompi per byte - } - return &mempool{ - mtx: sync.RWMutex{}, - policy: policy, - pool: make(map[consensusexternalapi.DomainTransactionID]*txDescriptor), - chainedTransactions: make(map[consensusexternalapi.DomainTransactionID]*txDescriptor), - chainedTransactionByPreviousOutpoint: make(map[consensusexternalapi.DomainOutpoint]*txDescriptor), - orphans: make(map[consensusexternalapi.DomainTransactionID]*orphanTx), - orphansByPrev: make(map[consensusexternalapi.DomainOutpoint]map[consensusexternalapi.DomainTransactionID]*consensusexternalapi.DomainTransaction), - mempoolUTXOSet: newMempoolUTXOSet(), - consensus: consensus, - nextExpireScan: mstime.Now().Add(orphanExpireScanInterval), - dagParams: dagParams, +// New constructs a new mempool +func New(config *Config, consensus externalapi.Consensus) miningmanagermodel.Mempool { + mp := &mempool{ + config: config, + consensus: consensus, } + + mp.mempoolUTXOSet = newMempoolUTXOSet(mp) + mp.transactionsPool = newTransactionsPool(mp) + mp.orphansPool = newOrphansPool(mp) + + return mp } -func (mp *mempool) GetTransaction( - transactionID *consensusexternalapi.DomainTransactionID) (*consensusexternalapi.DomainTransaction, bool) { +func (mp *mempool) ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) ( + acceptedTransactions []*externalapi.DomainTransaction, err error) { + + mp.mtx.Lock() + defer mp.mtx.Unlock() + + return mp.validateAndInsertTransaction(transaction, isHighPriority, allowOrphan) +} + +func (mp *mempool) GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { mp.mtx.RLock() defer mp.mtx.RUnlock() - txDesc, exists := mp.fetchTxDesc(transactionID) - if !exists { - return nil, false - } - - return txDesc.DomainTransaction, true + return mp.transactionsPool.getTransaction(transactionID) } -func (mp *mempool) AllTransactions() []*consensusexternalapi.DomainTransaction { +func (mp *mempool) AllTransactions() []*externalapi.DomainTransaction { mp.mtx.RLock() defer mp.mtx.RUnlock() - transactions := make([]*consensusexternalapi.DomainTransaction, 0, len(mp.pool)+len(mp.chainedTransactions)) - for _, txDesc := range mp.pool { - transactions = append(transactions, txDesc.DomainTransaction) - } - - for _, txDesc := range mp.chainedTransactions { - transactions = append(transactions, txDesc.DomainTransaction) - } - - return transactions + return mp.transactionsPool.getAllTransactions() } func (mp *mempool) TransactionCount() int { mp.mtx.RLock() defer mp.mtx.RUnlock() - return len(mp.pool) + len(mp.chainedTransactions) + return mp.transactionsPool.transactionCount() } -// txDescriptor is a descriptor containing a transaction in the mempool along with -// additional metadata. -type txDescriptor struct { - *consensusexternalapi.DomainTransaction +func (mp *mempool) HandleNewBlockTransactions(transactions []*externalapi.DomainTransaction) ( + acceptedOrphans []*externalapi.DomainTransaction, err error) { - // depCount is not 0 for a chained transaction. A chained transaction is - // one that is accepted to pool, but cannot be mined in next block because it - // depends on outputs of accepted, but still not mined transaction - depCount int -} - -// orphanTx is normal transaction that references an ancestor transaction -// that is not yet available. It also contains additional information related -// to it such as an expiration time to help prevent caching the orphan forever. -type orphanTx struct { - tx *consensusexternalapi.DomainTransaction - expiration mstime.Time -} - -// removeOrphan removes the passed orphan transaction from the orphan pool and -// previous orphan index. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) removeOrphan(tx *consensusexternalapi.DomainTransaction, removeRedeemers bool) { - // Nothing to do if passed tx is not an orphan. - txID := consensushashing.TransactionID(tx) - otx, exists := mp.orphans[*txID] - if !exists { - return - } - - // Remove the reference from the previous orphan index. - for _, txIn := range otx.tx.Inputs { - orphans, exists := mp.orphansByPrev[txIn.PreviousOutpoint] - if exists { - delete(orphans, *txID) - - // Remove the map entry altogether if there are no - // longer any orphans which depend on it. - if len(orphans) == 0 { - delete(mp.orphansByPrev, txIn.PreviousOutpoint) - } - } - } - - // Remove any orphans that redeem outputs from this one if requested. - if removeRedeemers { - prevOut := consensusexternalapi.DomainOutpoint{TransactionID: *txID} - for txOutIdx := range tx.Outputs { - prevOut.Index = uint32(txOutIdx) - for _, orphan := range mp.orphansByPrev[prevOut] { - mp.removeOrphan(orphan, true) - } - } - } - - // Remove the transaction from the orphan pool. - delete(mp.orphans, *txID) -} - -// limitNumOrphans limits the number of orphan transactions by evicting a random -// orphan if adding a new one would cause it to overflow the max allowed. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) limitNumOrphans() error { - // Scan through the orphan pool and remove any expired orphans when it's - // time. This is done for efficiency so the scan only happens - // periodically instead of on every orphan added to the pool. - if now := mstime.Now(); now.After(mp.nextExpireScan) { - origNumOrphans := len(mp.orphans) - for _, otx := range mp.orphans { - if now.After(otx.expiration) { - // Remove redeemers too because the missing - // parents are very unlikely to ever materialize - // since the orphan has already been around more - // than long enough for them to be delivered. - mp.removeOrphan(otx.tx, true) - } - } - - // Set next expiration scan to occur after the scan interval. - mp.nextExpireScan = now.Add(orphanExpireScanInterval) - - numOrphans := len(mp.orphans) - if numExpired := origNumOrphans - numOrphans; numExpired > 0 { - log.Debugf("Expired %d orphans (remaining: %d)", numExpired, numOrphans) - } - } - - // Nothing to do if adding another orphan will not cause the pool to - // exceed the limit. - if len(mp.orphans)+1 <= mp.policy.MaxOrphanTxs { - return nil - } - - // Remove a random entry from the map. For most compilers, Go's - // range statement iterates starting at a random item although - // that is not 100% guaranteed by the spec. The iteration order - // is not important here because an adversary would have to be - // able to pull off preimage attacks on the hashing function in - // order to target eviction of specific entries anyways. - for _, otx := range mp.orphans { - // Don't remove redeemers in the case of a random eviction since - // it is quite possible it might be needed again shortly. - mp.removeOrphan(otx.tx, false) - break - } - - return nil -} - -// addOrphan adds an orphan transaction to the orphan pool. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) addOrphan(tx *consensusexternalapi.DomainTransaction) { - // Nothing to do if no orphans are allowed. - if mp.policy.MaxOrphanTxs <= 0 { - return - } - - // Limit the number orphan transactions to prevent memory exhaustion. - // This will periodically remove any expired orphans and evict a random - // orphan if space is still needed. - mp.limitNumOrphans() - txID := consensushashing.TransactionID(tx) - mp.orphans[*txID] = &orphanTx{ - tx: tx, - expiration: mstime.Now().Add(orphanTTL), - } - for _, txIn := range tx.Inputs { - if _, exists := mp.orphansByPrev[txIn.PreviousOutpoint]; !exists { - mp.orphansByPrev[txIn.PreviousOutpoint] = - make(map[consensusexternalapi.DomainTransactionID]*consensusexternalapi.DomainTransaction) - } - mp.orphansByPrev[txIn.PreviousOutpoint][*txID] = tx - } - - log.Debugf("Stored orphan transaction %s (total: %d)", consensushashing.TransactionID(tx), - len(mp.orphans)) -} - -// maybeAddOrphan potentially adds an orphan to the orphan pool. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) maybeAddOrphan(tx *consensusexternalapi.DomainTransaction) error { - // Ignore orphan transactions that are too large. This helps avoid - // a memory exhaustion attack based on sending a lot of really large - // orphans. In the case there is a valid transaction larger than this, - // it will ultimtely be rebroadcast after the parent transactions - // have been mined or otherwise received. - // - // Note that the number of orphan transactions in the orphan pool is - // also limited, so this equates to a maximum memory used of - // mp.policy.MaxOrphanTxSize * mp.policy.MaxOrphanTxs (which is ~5MB - // using the default values at the time this comment was written). - serializedLen := estimatedsize.TransactionEstimatedSerializedSize(tx) - if serializedLen > uint64(mp.policy.MaxOrphanTxSize) { - str := fmt.Sprintf("orphan transaction size of %d bytes is "+ - "larger than max allowed size of %d bytes", - serializedLen, mp.policy.MaxOrphanTxSize) - return txRuleError(RejectNonstandard, str) - } - - // Add the orphan if the none of the above disqualified it. - mp.addOrphan(tx) - - return nil -} - -// removeOrphanDoubleSpends removes all orphans which spend outputs spent by the -// passed transaction from the orphan pool. Removing those orphans then leads -// to removing all orphans which rely on them, recursively. This is necessary -// when a transaction is added to the main pool because it may spend outputs -// that orphans also spend. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) removeOrphanDoubleSpends(tx *consensusexternalapi.DomainTransaction) { - for _, txIn := range tx.Inputs { - for _, orphan := range mp.orphansByPrev[txIn.PreviousOutpoint] { - mp.removeOrphan(orphan, true) - } - } -} - -// isTransactionInPool returns whether or not the passed transaction already -// exists in the main pool. -// -// This function MUST be called with the mempool lock held (for reads). -func (mp *mempool) isTransactionInPool(txID *consensusexternalapi.DomainTransactionID) bool { - if _, exists := mp.pool[*txID]; exists { - return true - } - return mp.isInDependPool(txID) -} - -// isInDependPool returns whether or not the passed transaction already -// exists in the depend pool. -// -// This function MUST be called with the mempool lock held (for reads). -func (mp *mempool) isInDependPool(hash *consensusexternalapi.DomainTransactionID) bool { - if _, exists := mp.chainedTransactions[*hash]; exists { - return true - } - - return false -} - -// isOrphanInPool returns whether or not the passed transaction already exists -// in the orphan pool. -// -// This function MUST be called with the mempool lock held (for reads). -func (mp *mempool) isOrphanInPool(txID *consensusexternalapi.DomainTransactionID) bool { - if _, exists := mp.orphans[*txID]; exists { - return true - } - - return false -} - -// haveTransaction returns whether or not the passed transaction already exists -// in the main pool or in the orphan pool. -// -// This function MUST be called with the mempool lock held (for reads). -func (mp *mempool) haveTransaction(txID *consensusexternalapi.DomainTransactionID) bool { - return mp.isTransactionInPool(txID) || mp.isOrphanInPool(txID) -} - -// removeTransactionsFromPool removes given transactions from the mempool, and move their chained mempool -// transactions (if any) to the main pool. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) removeTransactionsFromPool(txs []*consensusexternalapi.DomainTransaction) error { - for _, tx := range txs[transactionhelper.CoinbaseTransactionIndex+1:] { - txID := consensushashing.TransactionID(tx) - - // We use the mempool transaction, because it has populated fee and mass - mempoolTx, exists := mp.fetchTxDesc(txID) - if !exists { - continue - } - - err := mp.cleanTransactionFromSets(mempoolTx.DomainTransaction) - if err != nil { - return err - } - - mp.updateBlockTransactionChainedTransactions(mempoolTx.DomainTransaction) - } - return nil -} - -type transactionAndOutpoint struct { - transaction *consensusexternalapi.DomainTransaction - outpoint *consensusexternalapi.DomainOutpoint -} - -// removeTransactionAndItsChainedTransactions removes a transaction and all of its chained transaction from the mempool. -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) removeTransactionAndItsChainedTransactions(transaction *consensusexternalapi.DomainTransaction) error { - transactionAndOutpointQueue := make([]*transactionAndOutpoint, 0) - transactionAndOutpointQueue = appendTransactionToTransactionAndOutpointQueue(transactionAndOutpointQueue, transaction) - // Remove any transactions which rely on this one. - for len(transactionAndOutpointQueue) > 0 { - txAndOutpoint := transactionAndOutpointQueue[0] - transactionAndOutpointQueue = transactionAndOutpointQueue[1:] - if txRedeemer, exists := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(*txAndOutpoint.outpoint); exists { - transactionAndOutpointQueue = appendTransactionToTransactionAndOutpointQueue(transactionAndOutpointQueue, txRedeemer) - } - - transactionID := txAndOutpoint.outpoint.TransactionID - if _, exists := mp.chainedTransactions[transactionID]; exists { - mp.removeChainTransaction(txAndOutpoint.transaction) - } - err := mp.cleanTransactionFromSets(txAndOutpoint.transaction) - if err != nil { - return err - } - } - return nil -} - -func appendTransactionToTransactionAndOutpointQueue(queue []*transactionAndOutpoint, - transaction *consensusexternalapi.DomainTransaction) []*transactionAndOutpoint { - - transactionID := consensushashing.TransactionID(transaction) - queueWithAddedTransactionAndOutpoint := queue - for i := uint32(0); i < uint32(len(transaction.Outputs)); i++ { - previousOutpoint := consensusexternalapi.DomainOutpoint{TransactionID: *transactionID, Index: i} - queueWithAddedTransactionAndOutpoint = append(queueWithAddedTransactionAndOutpoint, - &transactionAndOutpoint{transaction: transaction, outpoint: &previousOutpoint}) - } - return queueWithAddedTransactionAndOutpoint -} - -// cleanTransactionFromSets removes the transaction from all mempool related transaction sets. -// It assumes that any chained transaction is already cleaned from the mempool. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) cleanTransactionFromSets(tx *consensusexternalapi.DomainTransaction) error { - err := mp.mempoolUTXOSet.removeTx(tx) - if err != nil { - return err - } - - txID := consensushashing.TransactionID(tx) - delete(mp.pool, *txID) - delete(mp.chainedTransactions, *txID) - - return mp.removeTransactionFromOrderedTransactionsByFeeRate(tx) -} - -// updateBlockTransactionChainedTransactions processes the dependencies of a -// transaction that was included in a block and was just now removed from the mempool. -// -// This function MUST be called with the mempool lock held (for writes). - -func (mp *mempool) updateBlockTransactionChainedTransactions(tx *consensusexternalapi.DomainTransaction) { - prevOut := consensusexternalapi.DomainOutpoint{TransactionID: *consensushashing.TransactionID(tx)} - for txOutIdx := range tx.Outputs { - // Skip to the next available output if there are none. - prevOut.Index = uint32(txOutIdx) - txDesc, exists := mp.chainedTransactionByPreviousOutpoint[prevOut] - if !exists { - continue - } - - txDesc.depCount-- - // If the transaction is not chained anymore, move it into the main pool - if txDesc.depCount == 0 { - // Transaction may be already removed by recursive calls, if removeRedeemers is true. - // So avoid moving it into main pool - txDescID := consensushashing.TransactionID(txDesc.DomainTransaction) - if _, ok := mp.chainedTransactions[*txDescID]; ok { - delete(mp.chainedTransactions, *txDescID) - mp.pool[*txDescID] = txDesc - } - } - delete(mp.chainedTransactionByPreviousOutpoint, prevOut) - } -} - -// removeChainTransaction removes a chain transaction and all of its relation as a result of double spend. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) removeChainTransaction(tx *consensusexternalapi.DomainTransaction) { - delete(mp.chainedTransactions, *consensushashing.TransactionID(tx)) - for _, txIn := range tx.Inputs { - delete(mp.chainedTransactionByPreviousOutpoint, txIn.PreviousOutpoint) - } -} - -// removeDoubleSpends removes all transactions which spend outputs spent by the -// passed transaction from the memory pool. Removing those transactions then -// leads to removing all transactions which rely on them, recursively. This is -// necessary when a block is connected to the DAG because the block may -// contain transactions which were previously unknown to the memory pool. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) removeDoubleSpends(tx *consensusexternalapi.DomainTransaction) error { - txID := consensushashing.TransactionID(tx) - for _, txIn := range tx.Inputs { - if txRedeemer, ok := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(txIn.PreviousOutpoint); ok { - if !consensushashing.TransactionID(txRedeemer).Equal(txID) { - err := mp.removeTransactionAndItsChainedTransactions(txRedeemer) - if err != nil { - return err - } - } - } - } - return nil -} - -// addTransaction adds the passed transaction to the memory pool. It should -// not be called directly as it doesn't perform any validation. This is a -// helper for maybeAcceptTransaction. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) addTransaction(tx *consensusexternalapi.DomainTransaction, parentsInPool []consensusexternalapi.DomainOutpoint) (*txDescriptor, error) { - // Add the transaction to the pool and mark the referenced outpoints - // as spent by the pool. - txDescriptor := &txDescriptor{ - DomainTransaction: tx, - depCount: len(parentsInPool), - } - txID := *consensushashing.TransactionID(tx) - - if len(parentsInPool) == 0 { - mp.pool[txID] = txDescriptor - } else { - mp.chainedTransactions[txID] = txDescriptor - for _, previousOutpoint := range parentsInPool { - mp.chainedTransactionByPreviousOutpoint[previousOutpoint] = txDescriptor - } - } - - err := mp.mempoolUTXOSet.addTx(tx) - if err != nil { - return nil, err - } - - err = mp.addTransactionToOrderedTransactionsByFeeRate(tx) - if err != nil { - return nil, err - } - - return txDescriptor, nil -} - -func (mp *mempool) findTxIndexInOrderedTransactionsByFeeRate(tx *consensusexternalapi.DomainTransaction) (int, error) { - if tx.Fee == 0 || tx.Mass == 0 { - return 0, errors.Errorf("findTxIndexInOrderedTransactionsByFeeRate expects a transaction with " + - "populated fee and mass") - } - txID := consensushashing.TransactionID(tx) - txFeeRate := float64(tx.Fee) / float64(tx.Mass) - - return sort.Search(len(mp.orderedTransactionsByFeeRate), func(i int) bool { - elementFeeRate := float64(mp.orderedTransactionsByFeeRate[i].Fee) / float64(mp.orderedTransactionsByFeeRate[i].Mass) - if elementFeeRate > txFeeRate { - return true - } - - if elementFeeRate == txFeeRate && txID.LessOrEqual(consensushashing.TransactionID(mp.orderedTransactionsByFeeRate[i])) { - return true - } - - return false - }), nil -} - -func (mp *mempool) addTransactionToOrderedTransactionsByFeeRate(tx *consensusexternalapi.DomainTransaction) error { - index, err := mp.findTxIndexInOrderedTransactionsByFeeRate(tx) - if err != nil { - return err - } - - mp.orderedTransactionsByFeeRate = append(mp.orderedTransactionsByFeeRate[:index], - append([]*consensusexternalapi.DomainTransaction{tx}, mp.orderedTransactionsByFeeRate[index:]...)...) - - return nil -} - -func (mp *mempool) removeTransactionFromOrderedTransactionsByFeeRate(tx *consensusexternalapi.DomainTransaction) error { - index, err := mp.findTxIndexInOrderedTransactionsByFeeRate(tx) - if err != nil { - return err - } - - txID := consensushashing.TransactionID(tx) - if !consensushashing.TransactionID(mp.orderedTransactionsByFeeRate[index]).Equal(txID) { - return errors.Errorf("Couldn't find %s in mp.orderedTransactionsByFeeRate", txID) - } - - mp.orderedTransactionsByFeeRate = append(mp.orderedTransactionsByFeeRate[:index], mp.orderedTransactionsByFeeRate[index+1:]...) - return nil -} - -func (mp *mempool) enforceTransactionLimit() error { - const limit = 1_000_000 - if len(mp.pool)+len(mp.chainedTransactions) > limit { - // mp.orderedTransactionsByFeeRate[0] is the least profitable transaction - txToRemove := mp.orderedTransactionsByFeeRate[0] - log.Debugf("Mempool size is over the limit of %d transactions. Removing %s", - limit, - consensushashing.TransactionID(txToRemove), - ) - return mp.removeTransactionAndItsChainedTransactions(txToRemove) - } - return nil -} - -// checkPoolDoubleSpend checks whether or not the passed transaction is -// attempting to spend coins already spent by other transactions in the pool. -// Note it does not check for double spends against transactions already in the -// DAG. -// -// This function MUST be called with the mempool lock held (for reads). -func (mp *mempool) checkPoolDoubleSpend(tx *consensusexternalapi.DomainTransaction) error { - for _, txIn := range tx.Inputs { - if txR, exists := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(txIn.PreviousOutpoint); exists { - str := fmt.Sprintf("output %s already spent by "+ - "transaction %s in the memory pool", - txIn.PreviousOutpoint, consensushashing.TransactionID(txR)) - return txRuleError(RejectDuplicate, str) - } - } - - return nil -} - -// This function MUST be called with the mempool lock held (for reads). -// This only fetches from the main transaction pool and does not include -// orphans. -// returns false in the second return parameter if transaction was not found -func (mp *mempool) fetchTxDesc(txID *consensusexternalapi.DomainTransactionID) (*txDescriptor, bool) { - txDesc, exists := mp.pool[*txID] - if !exists { - txDesc, exists = mp.chainedTransactions[*txID] - } - return txDesc, exists -} - -// maybeAcceptTransaction is the main workhorse for handling insertion of new -// free-standing transactions into a memory pool. It includes functionality -// such as rejecting duplicate transactions, ensuring transactions follow all -// rules, detecting orphan transactions, and insertion into the memory pool. -// -// If the transaction is an orphan (missing parent transactions), the -// transaction is NOT added to the orphan pool, but each unknown referenced -// parent is returned. Use ProcessTransaction instead if new orphans should -// be added to the orphan pool. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) maybeAcceptTransaction(tx *consensusexternalapi.DomainTransaction, rejectDupOrphans bool) ( - []*consensusexternalapi.DomainOutpoint, *txDescriptor, error) { - - txID := consensushashing.TransactionID(tx) - - // Don't accept the transaction if it already exists in the pool. This - // applies to orphan transactions as well when the reject duplicate - // orphans flag is set. This check is intended to be a quick check to - // weed out duplicates. - if mp.isTransactionInPool(txID) || (rejectDupOrphans && - mp.isOrphanInPool(txID)) { - - str := fmt.Sprintf("already have transaction %s", txID) - return nil, nil, txRuleError(RejectDuplicate, str) - } - - // Don't allow non-standard transactions if the network parameters - // forbid their acceptance. - if !mp.policy.AcceptNonStd { - err := checkTransactionStandard(tx, &mp.policy) - if err != nil { - // Attempt to extract a reject code from the error so - // it can be retained. When not possible, fall back to - // a non standard error. - rejectCode, found := extractRejectCode(err) - if !found { - rejectCode = RejectNonstandard - } - str := fmt.Sprintf("transaction %s is not standard: %s", - txID, err) - return nil, nil, txRuleError(rejectCode, str) - } - } - - // The transaction may not use any of the same outputs as other - // transactions already in the pool as that would ultimately result in a - // double spend. This check is intended to be quick and therefore only - // detects double spends within the transaction pool itself. The - // transaction could still be double spending coins from the DAG - // at this point. There is a more in-depth check that happens later - // after fetching the referenced transaction inputs from the DAG - // which examines the actual spend data and prevents double spends. - err := mp.checkPoolDoubleSpend(tx) - if err != nil { - return nil, nil, err - } - - // Don't allow the transaction if it exists in the DAG and is - // not already fully spent. - if mp.mempoolUTXOSet.checkExists(tx) { - return nil, nil, txRuleError(RejectDuplicate, "transaction already exists") - } - - // Transaction is an orphan if any of the referenced transaction outputs - // don't exist or are already spent. Adding orphans to the orphan pool - // is not handled by this function, and the caller should use - // maybeAddOrphan if this behavior is desired. - parentsInPool := mp.mempoolUTXOSet.populateUTXOEntries(tx) - - // This will populate the missing UTXOEntries. - err = mp.consensus.ValidateTransactionAndPopulateWithConsensusData(tx) - missingOutpoints := ruleerrors.ErrMissingTxOut{} - if err != nil { - if errors.As(err, &missingOutpoints) { - return missingOutpoints.MissingOutpoints, nil, nil - } - if errors.Is(err, ruleerrors.ErrImmatureSpend) { - return nil, nil, txRuleError(RejectImmatureSpend, "one of the transaction inputs spends an immature UTXO") - } - if errors.As(err, &ruleerrors.RuleError{}) { - return nil, nil, newRuleError(err) - } - return nil, nil, err - } - - if tx.Mass > mp.dagParams.MaxMassAcceptedByBlock { - return nil, nil, newRuleError(errors.Errorf("The transaction mass is %d which is "+ - "higher than the maxmimum of %d", tx.Mass, mp.dagParams.MaxMassAcceptedByBlock)) - } - - // Don't allow transactions with non-standard inputs if the network - // parameters forbid their acceptance. - if !mp.policy.AcceptNonStd { - err := checkInputsStandard(tx) - if err != nil { - // Attempt to extract a reject code from the error so - // it can be retained. When not possible, fall back to - // a non standard error. - rejectCode, found := extractRejectCode(err) - if !found { - rejectCode = RejectNonstandard - } - str := fmt.Sprintf("transaction %s has a non-standard "+ - "input: %s", txID, err) - return nil, nil, txRuleError(rejectCode, str) - } - } - - // Don't allow transactions with fees too low to get into a mined block - serializedSize := int64(estimatedsize.TransactionEstimatedSerializedSize(tx)) - minFee := uint64(calcMinRequiredTxRelayFee(serializedSize, - mp.policy.MinRelayTxFee)) - if tx.Fee < minFee { - str := fmt.Sprintf("transaction %s has %d fees which is under "+ - "the required amount of %d", txID, tx.Fee, - minFee) - return nil, nil, txRuleError(RejectInsufficientFee, str) - } - // Add to transaction pool. - txDesc, err := mp.addTransaction(tx, parentsInPool) - if err != nil { - return nil, nil, err - } - - log.Debugf("Accepted transaction %s (pool size: %d)", txID, - len(mp.pool)) - - err = mp.enforceTransactionLimit() - if err != nil { - return nil, nil, err - } - - return nil, txDesc, nil -} - -// processOrphans determines if there are any orphans which depend on the passed -// transaction hash (it is possible that they are no longer orphans) and -// potentially accepts them to the memory pool. It repeats the process for the -// newly accepted transactions (to detect further orphans which may no longer be -// orphans) until there are no more. -// -// It returns a slice of transactions added to the mempool. A nil slice means -// no transactions were moved from the orphan pool to the mempool. -// -// This function MUST be called with the mempool lock held (for writes). -func (mp *mempool) processOrphans(acceptedTx *consensusexternalapi.DomainTransaction) ([]*txDescriptor, error) { - var acceptedTxns []*txDescriptor - - // Start with processing at least the passed transaction. - processList := list.New() - processList.PushBack(acceptedTx) - for processList.Len() > 0 { - // Pop the transaction to process from the front of the list. - firstElement := processList.Remove(processList.Front()) - processItem := firstElement.(*consensusexternalapi.DomainTransaction) - - prevOut := consensusexternalapi.DomainOutpoint{TransactionID: *consensushashing.TransactionID(processItem)} - for txOutIdx := range processItem.Outputs { - // Look up all orphans that redeem the output that is - // now available. This will typically only be one, but - // it could be multiple if the orphan pool contains - // double spends. While it may seem odd that the orphan - // pool would allow this since there can only possibly - // ultimately be a single redeemer, it's important to - // track it this way to prevent malicious actors from - // being able to purposely constructing orphans that - // would otherwise make outputs unspendable. - // - // Skip to the next available output if there are none. - prevOut.Index = uint32(txOutIdx) - orphans, exists := mp.orphansByPrev[prevOut] - if !exists { - continue - } - - // Potentially accept an orphan into the tx pool. - for _, tx := range orphans { - missing, txD, err := mp.maybeAcceptTransaction( - tx, false) - if err != nil { - if !errors.As(err, &RuleError{}) { - return nil, err - } - - log.Warnf("Invalid orphan transaction: %s", err) - // The orphan is now invalid, so there - // is no way any other orphans which - // redeem any of its outputs can be - // accepted. Remove them. - mp.removeOrphan(tx, true) - break - } - - // Transaction is still an orphan. Try the next - // orphan which redeems this output. - if len(missing) > 0 { - continue - } - - // Transaction was accepted into the main pool. - // - // Add it to the list of accepted transactions - // that are no longer orphans, remove it from - // the orphan pool, and add it to the list of - // transactions to process so any orphans that - // depend on it are handled too. - acceptedTxns = append(acceptedTxns, txD) - mp.removeOrphan(tx, false) - processList.PushBack(tx) - - // Only one transaction for this outpoint can be - // accepted, so the rest are now double spends - // and are removed later. - break - } - } - } - - // Recursively remove any orphans that also redeem any outputs redeemed - // by the accepted transactions since those are now definitive double - // spends. - mp.removeOrphanDoubleSpends(acceptedTx) - for _, txDescriptor := range acceptedTxns { - mp.removeOrphanDoubleSpends(txDescriptor.DomainTransaction) - } - - return acceptedTxns, nil -} - -// ProcessTransaction is the main workhorse for handling insertion of new -// free-standing transactions into the memory pool. It includes functionality -// such as rejecting duplicate transactions, ensuring transactions follow all -// rules, orphan transaction handling, and insertion into the memory pool. -// -// It returns a slice of transactions added to the mempool. When the -// error is nil, the list will include the passed transaction itself along -// with any additional orphan transaactions that were added as a result of -// the passed one being accepted. -// -// This function is safe for concurrent access. -func (mp *mempool) ValidateAndInsertTransaction(tx *consensusexternalapi.DomainTransaction, allowOrphan bool) error { - log.Tracef("Processing transaction %s", consensushashing.TransactionID(tx)) - - // Protect concurrent access. mp.mtx.Lock() defer mp.mtx.Unlock() - // Potentially accept the transaction to the memory pool. - missingParents, txD, err := mp.maybeAcceptTransaction(tx, true) - if err != nil { - return err - } - - if len(missingParents) == 0 { - // Accept any orphan transactions that depend on this - // transaction (they may no longer be orphans if all inputs - // are now available) and repeat for those accepted - // transactions until there are no more. - newTxs, err := mp.processOrphans(tx) - if err != nil { - return err - } - - acceptedTxs := make([]*txDescriptor, len(newTxs)+1) - - // Add the parent transaction first so remote nodes - // do not add orphans. - acceptedTxs[0] = txD - copy(acceptedTxs[1:], newTxs) - - return nil - } - - // The transaction is an orphan (has inputs missing). Reject - // it if the flag to allow orphans is not set. - if !allowOrphan { - // Only use the first missing parent transaction in - // the error message. - // - // NOTE: RejectDuplicate is really not an accurate - // reject code here, but it matches the reference - // implementation and there isn't a better choice due - // to the limited number of reject codes. Missing - // inputs is assumed to mean they are already spent - // which is not really always the case. - str := fmt.Sprintf("orphan transaction %s references "+ - "outputs of unknown or fully-spent "+ - "transaction %s", consensushashing.TransactionID(tx), missingParents[0]) - return txRuleError(RejectDuplicate, str) - } - - // Potentially add the orphan transaction to the orphan pool. - return mp.maybeAddOrphan(tx) + return mp.handleNewBlockTransactions(transactions) } -// Count returns the number of transactions in the main pool. It does not -// include the orphan pool. -// -// This function is safe for concurrent access. -func (mp *mempool) Count() int { - mp.mtx.RLock() - defer mp.mtx.RUnlock() - count := len(mp.pool) - - return count -} - -// ChainedCount returns the number of chained transactions in the mempool. It does not -// include the orphan pool. -// -// This function is safe for concurrent access. -func (mp *mempool) ChainedCount() int { - mp.mtx.RLock() - defer mp.mtx.RUnlock() - return len(mp.chainedTransactions) -} - -// BlockCandidateTransactions returns a slice of all the candidate transactions for the next block -// This is safe for concurrent use -func (mp *mempool) BlockCandidateTransactions() []*consensusexternalapi.DomainTransaction { +func (mp *mempool) BlockCandidateTransactions() []*externalapi.DomainTransaction { mp.mtx.RLock() defer mp.mtx.RUnlock() - onEnd := logger.LogAndMeasureExecutionTime(log, "BlockCandidateTransactions") - defer onEnd() - - descs := make([]*consensusexternalapi.DomainTransaction, len(mp.pool)) - i := 0 - for _, desc := range mp.pool { - descs[i] = desc.DomainTransaction.Clone() // Clone the transaction to prevent data races. A shallow-copy might do as well - i++ - } - - return descs + return mp.transactionsPool.allReadyTransactions() } -// HandleNewBlockTransactions removes all the transactions in the new block -// from the mempool and the orphan pool, and it also removes -// from the mempool transactions that double spend a -// transaction that is already in the DAG -func (mp *mempool) HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction) ([]*consensusexternalapi.DomainTransaction, error) { - // Protect concurrent access. +func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) { mp.mtx.Lock() defer mp.mtx.Unlock() - // Remove all of the transactions (except the coinbase) in the - // connected block from the transaction pool. Secondly, remove any - // transactions which are now double spends as a result of these - // new transactions. Finally, remove any transaction that is - // no longer an orphan. Transactions which depend on a confirmed - // transaction are NOT removed recursively because they are still - // valid. - err := mp.removeTransactionsFromPool(txs) - if err != nil { - return nil, errors.Wrapf(err, "Failed removing txs from pool") - } - acceptedTxs := make([]*consensusexternalapi.DomainTransaction, 0) - for _, tx := range txs[transactionhelper.CoinbaseTransactionIndex+1:] { - err := mp.removeDoubleSpends(tx) - if err != nil { - return nil, errors.Wrapf(err, "Failed removing tx from mempool: %s", consensushashing.TransactionID(tx)) - } - mp.removeOrphan(tx, false) - acceptedOrphans, err := mp.processOrphans(tx) - if err != nil { - return nil, err - } - - for _, acceptedOrphan := range acceptedOrphans { - acceptedTxs = append(acceptedTxs, acceptedOrphan.DomainTransaction) - } - } - - return acceptedTxs, nil + return mp.revalidateHighPriorityTransactions() } -func (mp *mempool) RemoveTransactions(txs []*consensusexternalapi.DomainTransaction) error { - // Protect concurrent access. +func (mp *mempool) RemoveTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error { mp.mtx.Lock() defer mp.mtx.Unlock() - return mp.removeTransactionsFromPool(txs) + return mp.removeTransactions(transactions, removeRedeemers) +} + +func (mp *mempool) RemoveTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error { + mp.mtx.Lock() + defer mp.mtx.Unlock() + + return mp.removeTransaction(transactionID, removeRedeemers) } diff --git a/domain/miningmanager/mempool/mempool_utxo_set.go b/domain/miningmanager/mempool/mempool_utxo_set.go new file mode 100644 index 000000000..efb1dd9b8 --- /dev/null +++ b/domain/miningmanager/mempool/mempool_utxo_set.go @@ -0,0 +1,80 @@ +package mempool + +import ( + "fmt" + + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" +) + +type mempoolUTXOSet struct { + mempool *mempool + poolUnspentOutputs model.OutpointToUTXOEntryMap + transactionByPreviousOutpoint model.OutpointToTransactionMap +} + +func newMempoolUTXOSet(mp *mempool) *mempoolUTXOSet { + return &mempoolUTXOSet{ + mempool: mp, + poolUnspentOutputs: model.OutpointToUTXOEntryMap{}, + transactionByPreviousOutpoint: model.OutpointToTransactionMap{}, + } +} + +func (mpus *mempoolUTXOSet) addTransaction(transaction *model.MempoolTransaction) { + outpoint := &externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID()} + + for i, input := range transaction.Transaction().Inputs { + outpoint.Index = uint32(i) + + // Delete the output this input spends, in case it was created by mempool. + // If the outpoint doesn't exist in mpus.poolUnspentOutputs - this means + // it was created in the DAG (a.k.a. in consensus). + delete(mpus.poolUnspentOutputs, *outpoint) + + mpus.transactionByPreviousOutpoint[input.PreviousOutpoint] = transaction + } + + for i, output := range transaction.Transaction().Outputs { + outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID(), Index: uint32(i)} + + mpus.poolUnspentOutputs[outpoint] = + utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, model.UnacceptedDAAScore) + } +} + +func (mpus *mempoolUTXOSet) removeTransaction(transaction *model.MempoolTransaction) { + for _, input := range transaction.Transaction().Inputs { + // If the transaction creating the output spent by this input is in the mempool - restore it's UTXO + if _, ok := mpus.mempool.transactionsPool.getTransaction(&input.PreviousOutpoint.TransactionID); ok { + mpus.poolUnspentOutputs[input.PreviousOutpoint] = input.UTXOEntry + } + delete(mpus.transactionByPreviousOutpoint, input.PreviousOutpoint) + } + + outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID()} + for i := range transaction.Transaction().Outputs { + outpoint.Index = uint32(i) + + delete(mpus.poolUnspentOutputs, outpoint) + } +} + +func (mpus *mempoolUTXOSet) checkDoubleSpends(transaction *externalapi.DomainTransaction) error { + outpoint := externalapi.DomainOutpoint{TransactionID: *consensushashing.TransactionID(transaction)} + + for i, input := range transaction.Inputs { + outpoint.Index = uint32(i) + + if existingTransaction, exists := mpus.transactionByPreviousOutpoint[input.PreviousOutpoint]; exists { + str := fmt.Sprintf("output %s already spent by transaction %s in the memory pool", + input.PreviousOutpoint, existingTransaction.TransactionID()) + return transactionRuleError(RejectDuplicate, str) + } + } + + return nil +} diff --git a/domain/miningmanager/mempool/mempool_utxoset.go b/domain/miningmanager/mempool/mempool_utxoset.go deleted file mode 100644 index 60d90015a..000000000 --- a/domain/miningmanager/mempool/mempool_utxoset.go +++ /dev/null @@ -1,103 +0,0 @@ -package mempool - -import ( - "math" - - "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" - - consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" - "github.com/pkg/errors" -) - -const unacceptedDAAScore = math.MaxUint64 - -func newMempoolUTXOSet() *mempoolUTXOSet { - return &mempoolUTXOSet{ - transactionByPreviousOutpoint: make(map[consensusexternalapi.DomainOutpoint]*consensusexternalapi.DomainTransaction), - poolUnspentOutputs: make(map[consensusexternalapi.DomainOutpoint]consensusexternalapi.UTXOEntry), - } -} - -type mempoolUTXOSet struct { - transactionByPreviousOutpoint map[consensusexternalapi.DomainOutpoint]*consensusexternalapi.DomainTransaction - poolUnspentOutputs map[consensusexternalapi.DomainOutpoint]consensusexternalapi.UTXOEntry -} - -// Populate UTXO Entries in the transaction, to allow chained txs. -func (mpus *mempoolUTXOSet) populateUTXOEntries(tx *consensusexternalapi.DomainTransaction) (parentsInPool []consensusexternalapi.DomainOutpoint) { - for _, txIn := range tx.Inputs { - if utxoEntry, exists := mpus.poolUnspentOutputs[txIn.PreviousOutpoint]; exists { - txIn.UTXOEntry = utxoEntry - parentsInPool = append(parentsInPool, txIn.PreviousOutpoint) - } - } - return parentsInPool -} - -func (mpus *mempoolUTXOSet) checkExists(tx *consensusexternalapi.DomainTransaction) bool { - // Check if it was already spent. - for _, txIn := range tx.Inputs { - if _, exists := mpus.transactionByPreviousOutpoint[txIn.PreviousOutpoint]; exists { - return true - } - } - - // Check if it creates an already existing UTXO - outpoint := consensusexternalapi.DomainOutpoint{TransactionID: *consensushashing.TransactionID(tx)} - for i := range tx.Outputs { - outpoint.Index = uint32(i) - if _, exists := mpus.poolUnspentOutputs[outpoint]; exists { - return true - } - } - return false -} - -// addTx adds a transaction to the mempool UTXO set. It assumes that it doesn't double spend another transaction -// in the mempool, and that its outputs doesn't exist in the mempool UTXO set, and returns error otherwise. -func (mpus *mempoolUTXOSet) addTx(tx *consensusexternalapi.DomainTransaction) error { - for _, txIn := range tx.Inputs { - if existingTx, exists := mpus.transactionByPreviousOutpoint[txIn.PreviousOutpoint]; exists { - return errors.Errorf("outpoint %s is already used by %s", txIn.PreviousOutpoint, consensushashing.TransactionID(existingTx)) - } - mpus.transactionByPreviousOutpoint[txIn.PreviousOutpoint] = tx - } - - for i, txOut := range tx.Outputs { - outpoint := consensusexternalapi.DomainOutpoint{TransactionID: *consensushashing.TransactionID(tx), Index: uint32(i)} - if _, exists := mpus.poolUnspentOutputs[outpoint]; exists { - return errors.Errorf("outpoint %s already exists", outpoint) - } - mpus.poolUnspentOutputs[outpoint] = - utxo.NewUTXOEntry(txOut.Value, txOut.ScriptPublicKey, false, unacceptedDAAScore) - } - return nil -} - -// removeTx removes a transaction to the mempool UTXO set. -// Note: it doesn't re-add its previous outputs to the mempool UTXO set. -func (mpus *mempoolUTXOSet) removeTx(tx *consensusexternalapi.DomainTransaction) error { - for _, txIn := range tx.Inputs { - if _, exists := mpus.transactionByPreviousOutpoint[txIn.PreviousOutpoint]; !exists { - return errors.Errorf("outpoint %s doesn't exist", txIn.PreviousOutpoint) - } - delete(mpus.transactionByPreviousOutpoint, txIn.PreviousOutpoint) - } - - outpoint := consensusexternalapi.DomainOutpoint{TransactionID: *consensushashing.TransactionID(tx)} - for i := range tx.Outputs { - outpoint.Index = uint32(i) - if _, exists := mpus.poolUnspentOutputs[outpoint]; !exists { - return errors.Errorf("outpoint %s doesn't exist", outpoint) - } - delete(mpus.poolUnspentOutputs, outpoint) - } - - return nil -} - -func (mpus *mempoolUTXOSet) poolTransactionBySpendingOutpoint(outpoint consensusexternalapi.DomainOutpoint) (*consensusexternalapi.DomainTransaction, bool) { - tx, exists := mpus.transactionByPreviousOutpoint[outpoint] - return tx, exists -} diff --git a/domain/miningmanager/mempool/model/constants.go b/domain/miningmanager/mempool/model/constants.go new file mode 100644 index 000000000..d2d743bdc --- /dev/null +++ b/domain/miningmanager/mempool/model/constants.go @@ -0,0 +1,6 @@ +package model + +import "math" + +// UnacceptedDAAScore is used to for UTXOEntries that were created by transactions in the mempool. +const UnacceptedDAAScore = math.MaxUint64 diff --git a/domain/miningmanager/mempool/model/map_types.go b/domain/miningmanager/mempool/model/map_types.go new file mode 100644 index 000000000..c5ca09968 --- /dev/null +++ b/domain/miningmanager/mempool/model/map_types.go @@ -0,0 +1,14 @@ +package model + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" +) + +// IDToTransactionMap maps transactionID to a MempoolTransaction +type IDToTransactionMap map[externalapi.DomainTransactionID]*MempoolTransaction + +// OutpointToUTXOEntryMap maps an outpoint to a UTXOEntry +type OutpointToUTXOEntryMap map[externalapi.DomainOutpoint]externalapi.UTXOEntry + +// OutpointToTransactionMap maps an outpoint to a MempoolTransaction +type OutpointToTransactionMap map[externalapi.DomainOutpoint]*MempoolTransaction diff --git a/domain/miningmanager/mempool/model/mempool_transaction.go b/domain/miningmanager/mempool/model/mempool_transaction.go new file mode 100644 index 000000000..9240486b6 --- /dev/null +++ b/domain/miningmanager/mempool/model/mempool_transaction.go @@ -0,0 +1,54 @@ +package model + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" +) + +// MempoolTransaction represents a transaction inside the main TransactionPool +type MempoolTransaction struct { + transaction *externalapi.DomainTransaction + parentTransactionsInPool OutpointToTransactionMap + isHighPriority bool + addedAtDAAScore uint64 +} + +// NewMempoolTransaction constructs a new MempoolTransaction +func NewMempoolTransaction( + transaction *externalapi.DomainTransaction, + parentTransactionsInPool OutpointToTransactionMap, + isHighPriority bool, + addedAtDAAScore uint64, +) *MempoolTransaction { + return &MempoolTransaction{ + transaction: transaction, + parentTransactionsInPool: parentTransactionsInPool, + isHighPriority: isHighPriority, + addedAtDAAScore: addedAtDAAScore, + } +} + +// TransactionID returns the ID of this MempoolTransaction +func (mt *MempoolTransaction) TransactionID() *externalapi.DomainTransactionID { + return consensushashing.TransactionID(mt.transaction) +} + +// Transaction return the DomainTransaction associated with this MempoolTransaction: +func (mt *MempoolTransaction) Transaction() *externalapi.DomainTransaction { + return mt.transaction +} + +// ParentTransactionsInPool a list of parent transactions that exist in the mempool, indexed by outpoint +func (mt *MempoolTransaction) ParentTransactionsInPool() OutpointToTransactionMap { + return mt.parentTransactionsInPool +} + +// IsHighPriority returns whether this MempoolTransaction is a high-priority one +func (mt *MempoolTransaction) IsHighPriority() bool { + return mt.isHighPriority +} + +// AddedAtDAAScore returns the virtual DAA score at which this MempoolTransaction was added to the mempool +func (mt *MempoolTransaction) AddedAtDAAScore() uint64 { + return mt.addedAtDAAScore +} diff --git a/domain/miningmanager/mempool/model/ordered_transactions_by_fee_rate.go b/domain/miningmanager/mempool/model/ordered_transactions_by_fee_rate.go new file mode 100644 index 000000000..5b23f1534 --- /dev/null +++ b/domain/miningmanager/mempool/model/ordered_transactions_by_fee_rate.go @@ -0,0 +1,80 @@ +package model + +import ( + "sort" + + "github.com/pkg/errors" +) + +// TransactionsOrderedByFeeRate represents a set of MempoolTransactions ordered by their fee / mass rate +type TransactionsOrderedByFeeRate struct { + slice []*MempoolTransaction +} + +// GetByIndex returns the transaction in the given index +func (tobf *TransactionsOrderedByFeeRate) GetByIndex(index int) *MempoolTransaction { + return tobf.slice[index] +} + +// Push inserts a transaction into the set, placing it in the correct place to preserve order +func (tobf *TransactionsOrderedByFeeRate) Push(transaction *MempoolTransaction) error { + index, err := tobf.findTransactionIndex(transaction) + if err != nil { + return err + } + + tobf.slice = append(tobf.slice[:index], + append([]*MempoolTransaction{transaction}, tobf.slice[index:]...)...) + + return nil +} + +// Remove removes the given transaction from the set. +// Returns an error if transaction does not exist in the set, or if the given transaction does not have mass +// and fee filled in. +func (tobf *TransactionsOrderedByFeeRate) Remove(transaction *MempoolTransaction) error { + index, err := tobf.findTransactionIndex(transaction) + if err != nil { + return err + } + + txID := transaction.TransactionID() + if !tobf.slice[index].TransactionID().Equal(txID) { + return errors.Errorf("Couldn't find %s in mp.orderedTransactionsByFeeRate", txID) + } + + return tobf.RemoveAtIndex(index) +} + +// RemoveAtIndex removes the transaction at the given index. +// Returns an error in case of out-of-bounds index. +func (tobf *TransactionsOrderedByFeeRate) RemoveAtIndex(index int) error { + if index < 0 || index > len(tobf.slice)-1 { + return errors.Errorf("Index %d is out of bound of this TransactionsOrderedByFeeRate", index) + } + tobf.slice = append(tobf.slice[:index], tobf.slice[index+1:]...) + return nil +} + +func (tobf *TransactionsOrderedByFeeRate) findTransactionIndex(transaction *MempoolTransaction) (int, error) { + if transaction.Transaction().Fee == 0 || transaction.Transaction().Mass == 0 { + return 0, errors.Errorf("findTxIndexInOrderedTransactionsByFeeRate expects a transaction with " + + "populated fee and mass") + } + txID := transaction.TransactionID() + txFeeRate := float64(transaction.Transaction().Fee) / float64(transaction.Transaction().Mass) + + return sort.Search(len(tobf.slice), func(i int) bool { + iElement := tobf.slice[i] + elementFeeRate := float64(iElement.Transaction().Fee) / float64(iElement.Transaction().Mass) + if elementFeeRate > txFeeRate { + return true + } + + if elementFeeRate == txFeeRate && txID.LessOrEqual(iElement.TransactionID()) { + return true + } + + return false + }), nil +} diff --git a/domain/miningmanager/mempool/model/orphan_transaction.go b/domain/miningmanager/mempool/model/orphan_transaction.go new file mode 100644 index 000000000..c3af19c18 --- /dev/null +++ b/domain/miningmanager/mempool/model/orphan_transaction.go @@ -0,0 +1,46 @@ +package model + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" +) + +// OrphanTransaction represents a transaction in the OrphanPool +type OrphanTransaction struct { + transaction *externalapi.DomainTransaction + isHighPriority bool + addedAtDAAScore uint64 +} + +// NewOrphanTransaction constructs a new OrphanTransaction +func NewOrphanTransaction( + transaction *externalapi.DomainTransaction, + isHighPriority bool, + addedAtDAAScore uint64, +) *OrphanTransaction { + return &OrphanTransaction{ + transaction: transaction, + isHighPriority: isHighPriority, + addedAtDAAScore: addedAtDAAScore, + } +} + +// TransactionID returns the ID of this OrphanTransaction +func (ot *OrphanTransaction) TransactionID() *externalapi.DomainTransactionID { + return consensushashing.TransactionID(ot.transaction) +} + +// Transaction return the DomainTransaction associated with this OrphanTransaction: +func (ot *OrphanTransaction) Transaction() *externalapi.DomainTransaction { + return ot.transaction +} + +// IsHighPriority returns whether this OrphanTransaction is a high-priority one +func (ot *OrphanTransaction) IsHighPriority() bool { + return ot.isHighPriority +} + +// AddedAtDAAScore returns the virtual DAA score at which this OrphanTransaction was added to the mempool +func (ot *OrphanTransaction) AddedAtDAAScore() uint64 { + return ot.addedAtDAAScore +} diff --git a/domain/miningmanager/mempool/model/transaction.go b/domain/miningmanager/mempool/model/transaction.go new file mode 100644 index 000000000..8333251fa --- /dev/null +++ b/domain/miningmanager/mempool/model/transaction.go @@ -0,0 +1,9 @@ +package model + +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + +// Transaction represents a generic transaction either in the mempool's main TransactionPool or OrphanPool +type Transaction interface { + TransactionID() *externalapi.DomainTransactionID + Transaction() *externalapi.DomainTransaction +} diff --git a/domain/miningmanager/mempool/orphan_pool.go b/domain/miningmanager/mempool/orphan_pool.go new file mode 100644 index 000000000..26ef57dfb --- /dev/null +++ b/domain/miningmanager/mempool/orphan_pool.go @@ -0,0 +1,332 @@ +package mempool + +import ( + "fmt" + + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + + "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" + + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" + "github.com/pkg/errors" +) + +type idToOrphanMap map[externalapi.DomainTransactionID]*model.OrphanTransaction +type previousOutpointToOrphanMap map[externalapi.DomainOutpoint]*model.OrphanTransaction + +type orphansPool struct { + mempool *mempool + allOrphans idToOrphanMap + orphansByPreviousOutpoint previousOutpointToOrphanMap + lastExpireScan uint64 +} + +func newOrphansPool(mp *mempool) *orphansPool { + return &orphansPool{ + mempool: mp, + allOrphans: idToOrphanMap{}, + orphansByPreviousOutpoint: previousOutpointToOrphanMap{}, + lastExpireScan: 0, + } +} + +func (op *orphansPool) maybeAddOrphan(transaction *externalapi.DomainTransaction, isHighPriority bool) error { + if op.mempool.config.MaximumOrphanTransactionCount == 0 { + return nil + } + + err := op.checkOrphanDuplicate(transaction) + if err != nil { + return err + } + + err = op.checkOrphanSize(transaction) + if err != nil { + return err + } + err = op.checkOrphanDoubleSpend(transaction) + if err != nil { + return err + } + + err = op.addOrphan(transaction, isHighPriority) + if err != nil { + return err + } + + err = op.limitOrphanPoolSize() + if err != nil { + return err + } + + return nil +} + +func (op *orphansPool) limitOrphanPoolSize() error { + for uint64(len(op.allOrphans)) > op.mempool.config.MaximumOrphanTransactionCount { + orphanToRemove := op.randomNonHighPriorityOrphan() + if orphanToRemove == nil { // this means all orphans are HighPriority + log.Warnf( + "Number of high-priority transactions in orphanPool (%d) is higher than maximum allowed (%d)", + len(op.allOrphans), + op.mempool.config.MaximumOrphanTransactionCount) + break + } + + // Don't remove redeemers in the case of a random eviction since the evicted transaction is + // not invalid, therefore it's redeemers are as good as any orphan that just arrived. + err := op.removeOrphan(orphanToRemove.TransactionID(), false) + if err != nil { + return err + } + } + return nil +} + +func (op *orphansPool) checkOrphanSize(transaction *externalapi.DomainTransaction) error { + serializedLength := estimatedsize.TransactionEstimatedSerializedSize(transaction) + if serializedLength > op.mempool.config.MaximumOrphanTransactionSize { + str := fmt.Sprintf("orphan transaction size of %d bytes is "+ + "larger than max allowed size of %d bytes", + serializedLength, op.mempool.config.MaximumOrphanTransactionSize) + return transactionRuleError(RejectBadOrphan, str) + } + return nil +} + +func (op *orphansPool) checkOrphanDuplicate(transaction *externalapi.DomainTransaction) error { + if _, ok := op.allOrphans[*consensushashing.TransactionID(transaction)]; ok { + str := fmt.Sprintf("Orphan transacion %s is already in the orphan pool", + consensushashing.TransactionID(transaction)) + return transactionRuleError(RejectDuplicate, str) + } + return nil +} + +func (op *orphansPool) checkOrphanDoubleSpend(transaction *externalapi.DomainTransaction) error { + for _, input := range transaction.Inputs { + if doubleSpendOrphan, ok := op.orphansByPreviousOutpoint[input.PreviousOutpoint]; ok { + str := fmt.Sprintf("Orphan transacion %s is double spending an input from already existing orphan %s", + consensushashing.TransactionID(transaction), doubleSpendOrphan.TransactionID()) + return transactionRuleError(RejectDuplicate, str) + } + } + + return nil +} + +func (op *orphansPool) addOrphan(transaction *externalapi.DomainTransaction, isHighPriority bool) error { + virtualDAAScore, err := op.mempool.consensus.GetVirtualDAAScore() + if err != nil { + return err + } + orphanTransaction := model.NewOrphanTransaction(transaction, isHighPriority, virtualDAAScore) + + op.allOrphans[*orphanTransaction.TransactionID()] = orphanTransaction + for _, input := range transaction.Inputs { + if input.UTXOEntry == nil { + op.orphansByPreviousOutpoint[input.PreviousOutpoint] = orphanTransaction + } + } + + return nil +} + +func (op *orphansPool) processOrphansAfterAcceptedTransaction(acceptedTransaction *externalapi.DomainTransaction) ( + acceptedOrphans []*externalapi.DomainTransaction, err error) { + + acceptedOrphans = []*externalapi.DomainTransaction{} + queue := []*externalapi.DomainTransaction{acceptedTransaction} + + for len(queue) > 0 { + var current *externalapi.DomainTransaction + current, queue = queue[0], queue[1:] + + currentTransactionID := consensushashing.TransactionID(current) + outpoint := externalapi.DomainOutpoint{TransactionID: *currentTransactionID} + for i, output := range current.Outputs { + outpoint.Index = uint32(i) + orphan, ok := op.orphansByPreviousOutpoint[outpoint] + if !ok { + continue + } + for _, input := range orphan.Transaction().Inputs { + if input.PreviousOutpoint.Equal(&outpoint) { + input.UTXOEntry = utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, + model.UnacceptedDAAScore) + break + } + } + if countUnfilledInputs(orphan) == 0 { + err := op.unorphanTransaction(orphan) + if err != nil { + if errors.As(err, &RuleError{}) { + log.Infof("Failed to unorphan transaction %s due to rule error: %s", + currentTransactionID, err) + continue + } + return nil, err + } + acceptedOrphans = append(acceptedOrphans, orphan.Transaction()) + } + } + } + + return acceptedOrphans, nil +} + +func countUnfilledInputs(orphan *model.OrphanTransaction) int { + unfilledInputs := 0 + for _, input := range orphan.Transaction().Inputs { + if input.UTXOEntry == nil { + unfilledInputs++ + } + } + return unfilledInputs +} + +func (op *orphansPool) unorphanTransaction(transaction *model.OrphanTransaction) error { + err := op.removeOrphan(transaction.TransactionID(), false) + if err != nil { + return err + } + + err = op.mempool.consensus.ValidateTransactionAndPopulateWithConsensusData(transaction.Transaction()) + if err != nil { + if errors.Is(err, ruleerrors.ErrImmatureSpend) { + return transactionRuleError(RejectImmatureSpend, "one of the transaction inputs spends an immature UTXO") + } + if errors.As(err, &ruleerrors.RuleError{}) { + return newRuleError(err) + } + return err + } + + err = op.mempool.validateTransactionInContext(transaction.Transaction()) + if err != nil { + return err + } + + virtualDAAScore, err := op.mempool.consensus.GetVirtualDAAScore() + if err != nil { + return err + } + mempoolTransaction := model.NewMempoolTransaction( + transaction.Transaction(), + op.mempool.transactionsPool.getParentTransactionsInPool(transaction.Transaction()), + false, + virtualDAAScore, + ) + err = op.mempool.transactionsPool.addMempoolTransaction(mempoolTransaction) + if err != nil { + return err + } + + return nil +} + +func (op *orphansPool) removeOrphan(orphanTransactionID *externalapi.DomainTransactionID, removeRedeemers bool) error { + orphanTransaction, ok := op.allOrphans[*orphanTransactionID] + if !ok { + return nil + } + + delete(op.allOrphans, *orphanTransactionID) + + for i, input := range orphanTransaction.Transaction().Inputs { + if _, ok := op.orphansByPreviousOutpoint[input.PreviousOutpoint]; !ok { + return errors.Errorf("Input No. %d of %s (%s) doesn't exist in orphansByPreviousOutpoint", + i, orphanTransactionID, input.PreviousOutpoint) + } + delete(op.orphansByPreviousOutpoint, input.PreviousOutpoint) + } + + if removeRedeemers { + err := op.removeRedeemersOf(orphanTransaction) + if err != nil { + return err + } + } + + return nil +} + +func (op *orphansPool) removeRedeemersOf(transaction model.Transaction) error { + outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID()} + for i := range transaction.Transaction().Outputs { + outpoint.Index = uint32(i) + if orphan, ok := op.orphansByPreviousOutpoint[outpoint]; ok { + // Recursive call is bound by size of orphan pool (which is very small) + err := op.removeOrphan(orphan.TransactionID(), true) + if err != nil { + return err + } + } + } + return nil +} + +func (op *orphansPool) expireOrphanTransactions() error { + virtualDAAScore, err := op.mempool.consensus.GetVirtualDAAScore() + if err != nil { + return err + } + + if virtualDAAScore-op.lastExpireScan < op.mempool.config.OrphanExpireScanIntervalDAAScore { + return nil + } + + for _, orphanTransaction := range op.allOrphans { + // Never expire high priority transactions + if orphanTransaction.IsHighPriority() { + continue + } + + // Remove all transactions whose addedAtDAAScore is older then TransactionExpireIntervalDAAScore + if virtualDAAScore-orphanTransaction.AddedAtDAAScore() > op.mempool.config.OrphanExpireIntervalDAAScore { + err = op.removeOrphan(orphanTransaction.TransactionID(), true) + if err != nil { + return err + } + } + } + + op.lastExpireScan = virtualDAAScore + return nil +} + +func (op *orphansPool) updateOrphansAfterTransactionRemoved( + removedTransaction *model.MempoolTransaction, removeRedeemers bool) error { + + if removeRedeemers { + return op.removeRedeemersOf(removedTransaction) + } + + outpoint := externalapi.DomainOutpoint{TransactionID: *removedTransaction.TransactionID()} + for i := range removedTransaction.Transaction().Outputs { + outpoint.Index = uint32(i) + if orphan, ok := op.orphansByPreviousOutpoint[outpoint]; ok { + for _, input := range orphan.Transaction().Inputs { + if input.PreviousOutpoint.TransactionID.Equal(removedTransaction.TransactionID()) { + input.UTXOEntry = nil + } + } + } + } + + return nil +} + +func (op *orphansPool) randomNonHighPriorityOrphan() *model.OrphanTransaction { + for _, orphan := range op.allOrphans { + if !orphan.IsHighPriority() { + return orphan + } + } + + return nil +} diff --git a/domain/miningmanager/mempool/policy.go b/domain/miningmanager/mempool/policy.go deleted file mode 100644 index c2bb310d3..000000000 --- a/domain/miningmanager/mempool/policy.go +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) 2013-2016 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package mempool - -import ( - "fmt" - "github.com/kaspanet/kaspad/domain/consensus/utils/constants" - - consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize" - "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" - "github.com/kaspanet/kaspad/util" -) - -const ( - // maxStandardP2SHSigOps is the maximum number of signature operations - // that are considered standard in a pay-to-script-hash script. - maxStandardP2SHSigOps = 15 - - // maxStandardSigScriptSize is the maximum size allowed for a - // transaction input signature script to be considered standard. This - // value allows for a 15-of-15 CHECKMULTISIG pay-to-script-hash with - // compressed keys. - // - // The form of the overall script is: OP_0 <15 signatures> OP_PUSHDATA2 - // <2 bytes len> [OP_15 <15 pubkeys> OP_15 OP_CHECKMULTISIG] - // - // For the p2sh script portion, each of the 15 compressed pubkeys are - // 33 bytes (plus one for the OP_DATA_33 opcode), and the thus it totals - // to (15*34)+3 = 513 bytes. Next, each of the 15 signatures is a max - // of 73 bytes (plus one for the OP_DATA_73 opcode). Also, there is one - // extra byte for the initial extra OP_0 push and 3 bytes for the - // OP_PUSHDATA2 needed to specify the 513 bytes for the script push. - // That brings the total to 1+(15*74)+3+513 = 1627. This value also - // adds a few extra bytes to provide a little buffer. - // (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650 - maxStandardSigScriptSize = 1650 - - // MaxStandardTxSize is the maximum size allowed for transactions that - // are considered standard and will therefore be relayed and considered - // for mining. - MaxStandardTxSize = 100000 - - // DefaultMinRelayTxFee is the minimum fee in sompi that is required - // for a transaction to be treated as free for relay and mining - // purposes. It is also used to help determine if a transaction is - // considered dust and as a base for calculating minimum required fees - // for larger transactions. This value is in sompi/1000 bytes. - DefaultMinRelayTxFee = util.Amount(1000) -) - -// calcMinRequiredTxRelayFee returns the minimum transaction fee required for a -// transaction with the passed serialized size to be accepted into the memory -// pool and relayed. -func calcMinRequiredTxRelayFee(serializedSize int64, minRelayTxFee util.Amount) int64 { - // Calculate the minimum fee for a transaction to be allowed into the - // mempool and relayed by scaling the base fee. minTxRelayFee is in - // sompi/kB so multiply by serializedSize (which is in bytes) and - // divide by 1000 to get minimum sompis. - minFee := (serializedSize * int64(minRelayTxFee)) / 1000 - - if minFee == 0 && minRelayTxFee > 0 { - minFee = int64(minRelayTxFee) - } - - // Set the minimum fee to the maximum possible value if the calculated - // fee is not in the valid range for monetary amounts. - if minFee < 0 || minFee > util.MaxSompi { - minFee = util.MaxSompi - } - - return minFee -} - -// checkInputsStandard performs a series of checks on a transaction's inputs -// to ensure they are "standard". A standard transaction input within the -// context of this function is one whose referenced public key script is of a -// standard form and, for pay-to-script-hash, does not have more than -// maxStandardP2SHSigOps signature operations. -func checkInputsStandard(tx *consensusexternalapi.DomainTransaction) error { - // NOTE: The reference implementation also does a coinbase check here, - // but coinbases have already been rejected prior to calling this - // function so no need to recheck. - - for i, txIn := range tx.Inputs { - // It is safe to elide existence and index checks here since - // they have already been checked prior to calling this - // function. - entry := txIn.UTXOEntry - originScriptPubKey := entry.ScriptPublicKey() - switch txscript.GetScriptClass(originScriptPubKey.Script) { - case txscript.ScriptHashTy: - numSigOps := txscript.GetPreciseSigOpCount( - txIn.SignatureScript, originScriptPubKey, true) - if numSigOps > maxStandardP2SHSigOps { - str := fmt.Sprintf("transaction input #%d has "+ - "%d signature operations which is more "+ - "than the allowed max amount of %d", - i, numSigOps, maxStandardP2SHSigOps) - return txRuleError(RejectNonstandard, str) - } - - case txscript.NonStandardTy: - str := fmt.Sprintf("transaction input #%d has a "+ - "non-standard script form", i) - return txRuleError(RejectNonstandard, str) - } - } - - return nil -} - -// isDust returns whether or not the passed transaction output amount is -// considered dust or not based on the passed minimum transaction relay fee. -// Dust is defined in terms of the minimum transaction relay fee. In -// particular, if the cost to the network to spend coins is more than 1/3 of the -// minimum transaction relay fee, it is considered dust. -func isDust(txOut *consensusexternalapi.DomainTransactionOutput, minRelayTxFee util.Amount) bool { - // Unspendable outputs are considered dust. - if txscript.IsUnspendable(txOut.ScriptPublicKey.Script) { - return true - } - - // The total serialized size consists of the output and the associated - // input script to redeem it. Since there is no input script - // to redeem it yet, use the minimum size of a typical input script. - // - // Pay-to-pubkey bytes breakdown: - // - // Output to pubkey (43 bytes): - // 8 value, 1 script len, 34 script [1 OP_DATA_32, - // 32 pubkey, 1 OP_CHECKSIG] - // - // Input (105 bytes): - // 36 prev outpoint, 1 script len, 64 script [1 OP_DATA_64, - // 64 sig], 4 sequence - // - // The most common scripts are pay-to-pubkey, and as per the above - // breakdown, the minimum size of a p2pk input script is 148 bytes. So - // that figure is used. - totalSize := estimatedsize.TransactionOutputEstimatedSerializedSize(txOut) + 148 - - // The output is considered dust if the cost to the network to spend the - // coins is more than 1/3 of the minimum free transaction relay fee. - // minFreeTxRelayFee is in sompi/KB, so multiply by 1000 to - // convert to bytes. - // - // Using the typical values for a pay-to-pubkey transaction from - // the breakdown above and the default minimum free transaction relay - // fee of 1000, this equates to values less than 546 sompi being - // considered dust. - // - // The following is equivalent to (value/totalSize) * (1/3) * 1000 - // without needing to do floating point math. - return txOut.Value*1000/(3*totalSize) < uint64(minRelayTxFee) -} - -// checkTransactionStandard performs a series of checks on a transaction to -// ensure it is a "standard" transaction. A standard transaction is one that -// conforms to several additional limiting cases over what is considered a -// "sane" transaction such as having a version in the supported range, being -// finalized, conforming to more stringent size constraints, having scripts -// of recognized forms, and not containing "dust" outputs (those that are -// so small it costs more to process them than they are worth). -func checkTransactionStandard(tx *consensusexternalapi.DomainTransaction, policy *policy) error { - - // The transaction must be a currently supported version. - if tx.Version > policy.MaxTxVersion { - str := fmt.Sprintf("transaction version %d is not in the "+ - "valid range of %d-%d", tx.Version, 0, - policy.MaxTxVersion) - return txRuleError(RejectNonstandard, str) - } - - // Since extremely large transactions with a lot of inputs can cost - // almost as much to process as the sender fees, limit the maximum - // size of a transaction. This also helps mitigate CPU exhaustion - // attacks. - serializedLen := estimatedsize.TransactionEstimatedSerializedSize(tx) - if serializedLen > MaxStandardTxSize { - str := fmt.Sprintf("transaction size of %d is larger than max "+ - "allowed size of %d", serializedLen, MaxStandardTxSize) - return txRuleError(RejectNonstandard, str) - } - - for i, txIn := range tx.Inputs { - // Each transaction input signature script must not exceed the - // maximum size allowed for a standard transaction. See - // the comment on maxStandardSigScriptSize for more details. - sigScriptLen := len(txIn.SignatureScript) - if sigScriptLen > maxStandardSigScriptSize { - str := fmt.Sprintf("transaction input %d: signature "+ - "script size of %d bytes is large than max "+ - "allowed size of %d bytes", i, sigScriptLen, - maxStandardSigScriptSize) - return txRuleError(RejectNonstandard, str) - } - } - - // None of the output public key scripts can be a non-standard script or - // be "dust". - for i, txOut := range tx.Outputs { - if txOut.ScriptPublicKey.Version > constants.MaxScriptPublicKeyVersion { - return txRuleError(RejectNonstandard, "The version of the scriptPublicKey is higher than the known version.") - } - scriptClass := txscript.GetScriptClass(txOut.ScriptPublicKey.Script) - if scriptClass == txscript.NonStandardTy { - str := fmt.Sprintf("transaction output %d: non-standard script form", i) - return txRuleError(RejectNonstandard, str) - } - - if isDust(txOut, policy.MinRelayTxFee) { - str := fmt.Sprintf("transaction output %d: payment "+ - "of %d is dust", i, txOut.Value) - return txRuleError(RejectDust, str) - } - } - - return nil -} diff --git a/domain/miningmanager/mempool/policy_test.go b/domain/miningmanager/mempool/policy_test.go deleted file mode 100644 index 156e43604..000000000 --- a/domain/miningmanager/mempool/policy_test.go +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright (c) 2013-2016 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package mempool - -import ( - "bytes" - "testing" - - "github.com/kaspanet/kaspad/domain/consensus/utils/constants" - - consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" - "github.com/kaspanet/kaspad/util" - "github.com/pkg/errors" -) - -// TestCalcMinRequiredTxRelayFee tests the calcMinRequiredTxRelayFee API. -func TestCalcMinRequiredTxRelayFee(t *testing.T) { - tests := []struct { - name string // test description. - size int64 // Transaction size in bytes. - relayFee util.Amount // minimum relay transaction fee. - want int64 // Expected fee. - }{ - { - // Ensure combination of size and fee that are less than 1000 - // produce a non-zero fee. - "250 bytes with relay fee of 3", - 250, - 3, - 3, - }, - { - "100 bytes with default minimum relay fee", - 100, - DefaultMinRelayTxFee, - 100, - }, - { - "max standard tx size with default minimum relay fee", - MaxStandardTxSize, - DefaultMinRelayTxFee, - 100000, - }, - { - "max standard tx size with max sompi relay fee", - MaxStandardTxSize, - util.MaxSompi, - util.MaxSompi, - }, - { - "1500 bytes with 5000 relay fee", - 1500, - 5000, - 7500, - }, - { - "1500 bytes with 3000 relay fee", - 1500, - 3000, - 4500, - }, - { - "782 bytes with 5000 relay fee", - 782, - 5000, - 3910, - }, - { - "782 bytes with 3000 relay fee", - 782, - 3000, - 2346, - }, - { - "782 bytes with 2550 relay fee", - 782, - 2550, - 1994, - }, - } - - for _, test := range tests { - got := calcMinRequiredTxRelayFee(test.size, test.relayFee) - if got != test.want { - t.Errorf("TestCalcMinRequiredTxRelayFee test '%s' "+ - "failed: got %v want %v", test.name, got, - test.want) - continue - } - } -} - -// TestDust tests the isDust API. -func TestDust(t *testing.T) { - scriptPublicKey := &consensusexternalapi.ScriptPublicKey{ - []byte{0x76, 0xa9, 0x21, 0x03, 0x2f, 0x7e, 0x43, - 0x0a, 0xa4, 0xc9, 0xd1, 0x59, 0x43, 0x7e, 0x84, 0xb9, - 0x75, 0xdc, 0x76, 0xd9, 0x00, 0x3b, 0xf0, 0x92, 0x2c, - 0xf3, 0xaa, 0x45, 0x28, 0x46, 0x4b, 0xab, 0x78, 0x0d, - 0xba, 0x5e}, 0} - - tests := []struct { - name string // test description - txOut consensusexternalapi.DomainTransactionOutput - relayFee util.Amount // minimum relay transaction fee. - isDust bool - }{ - { - // Any value is allowed with a zero relay fee. - "zero value with zero relay fee", - consensusexternalapi.DomainTransactionOutput{Value: 0, ScriptPublicKey: scriptPublicKey}, - 0, - false, - }, - { - // Zero value is dust with any relay fee" - "zero value with very small tx fee", - consensusexternalapi.DomainTransactionOutput{Value: 0, ScriptPublicKey: scriptPublicKey}, - 1, - true, - }, - { - "36 byte public key script with value 605", - consensusexternalapi.DomainTransactionOutput{Value: 605, ScriptPublicKey: scriptPublicKey}, - 1000, - true, - }, - { - "36 byte public key script with value 606", - consensusexternalapi.DomainTransactionOutput{Value: 606, ScriptPublicKey: scriptPublicKey}, - 1000, - false, - }, - { - // Maximum allowed value is never dust. - "max sompi amount is never dust", - consensusexternalapi.DomainTransactionOutput{Value: util.MaxSompi, ScriptPublicKey: scriptPublicKey}, - util.MaxSompi, - false, - }, - { - // Maximum int64 value causes overflow. - "maximum int64 value", - consensusexternalapi.DomainTransactionOutput{Value: 1<<63 - 1, ScriptPublicKey: scriptPublicKey}, - 1<<63 - 1, - true, - }, - { - // Unspendable ScriptPublicKey due to an invalid public key - // script. - "unspendable ScriptPublicKey", - consensusexternalapi.DomainTransactionOutput{Value: 5000, ScriptPublicKey: &consensusexternalapi.ScriptPublicKey{[]byte{0x01}, 0}}, - 0, // no relay fee - true, - }, - } - for _, test := range tests { - res := isDust(&test.txOut, test.relayFee) - if res != test.isDust { - t.Fatalf("Dust test '%s' failed: want %v got %v", - test.name, test.isDust, res) - continue - } - } -} - -// TestCheckTransactionStandard tests the checkTransactionStandard API. -func TestCheckTransactionStandard(t *testing.T) { - // Create some dummy, but otherwise standard, data for transactions. - prevOutTxID := &consensusexternalapi.DomainTransactionID{} - dummyPrevOut := consensusexternalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1} - dummySigScript := bytes.Repeat([]byte{0x00}, 65) - dummyTxIn := consensusexternalapi.DomainTransactionInput{ - PreviousOutpoint: dummyPrevOut, - SignatureScript: dummySigScript, - Sequence: constants.MaxTxInSequenceNum, - } - addrHash := [32]byte{0x01} - addr, err := util.NewAddressPublicKey(addrHash[:], util.Bech32PrefixKaspaTest) - if err != nil { - t.Fatalf("NewAddressPublicKey: unexpected error: %v", err) - } - dummyScriptPublicKey, err := txscript.PayToAddrScript(addr) - if err != nil { - t.Fatalf("PayToAddrScript: unexpected error: %v", err) - } - dummyTxOut := consensusexternalapi.DomainTransactionOutput{ - Value: 100000000, // 1 KAS - ScriptPublicKey: dummyScriptPublicKey, - } - - tests := []struct { - name string - tx consensusexternalapi.DomainTransaction - height uint64 - isStandard bool - code RejectCode - }{ - { - name: "Typical pay-to-pubkey transaction", - tx: consensusexternalapi.DomainTransaction{Version: 0, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}}, - height: 300000, - isStandard: true, - }, - { - name: "Transaction version too high", - tx: consensusexternalapi.DomainTransaction{Version: constants.MaxTransactionVersion + 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}}, - height: 300000, - isStandard: false, - code: RejectNonstandard, - }, - - { - name: "Transaction size is too large", - tx: consensusexternalapi.DomainTransaction{Version: 0, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{{ - Value: 0, - ScriptPublicKey: &consensusexternalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, MaxStandardTxSize+1), 0}, - }}}, - height: 300000, - isStandard: false, - code: RejectNonstandard, - }, - { - name: "Signature script size is too large", - tx: consensusexternalapi.DomainTransaction{Version: 0, Inputs: []*consensusexternalapi.DomainTransactionInput{{ - PreviousOutpoint: dummyPrevOut, - SignatureScript: bytes.Repeat([]byte{0x00}, - maxStandardSigScriptSize+1), - Sequence: constants.MaxTxInSequenceNum, - }}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}}, - height: 300000, - isStandard: false, - code: RejectNonstandard, - }, - { - name: "Valid but non standard public key script", - tx: consensusexternalapi.DomainTransaction{Version: 0, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{{ - Value: 100000000, - ScriptPublicKey: &consensusexternalapi.ScriptPublicKey{[]byte{txscript.OpTrue}, 0}, - }}}, - height: 300000, - isStandard: false, - code: RejectNonstandard, - }, - { //Todo : check on ScriptPublicKey type. - name: "Dust output", - tx: consensusexternalapi.DomainTransaction{Version: 0, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{{ - Value: 0, - ScriptPublicKey: dummyScriptPublicKey, - }}}, - height: 300000, - isStandard: false, - code: RejectDust, - }, - { - name: "Nulldata transaction", - tx: consensusexternalapi.DomainTransaction{Version: 0, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{{ - Value: 0, - ScriptPublicKey: &consensusexternalapi.ScriptPublicKey{[]byte{txscript.OpReturn}, 0}, - }}}, - height: 300000, - isStandard: false, - code: RejectNonstandard, - }, - } - - for _, test := range tests { - // Ensure standardness is as expected. - err := checkTransactionStandard(&test.tx, &policy{MinRelayTxFee: DefaultMinRelayTxFee, MaxTxVersion: 0}) - if err == nil && test.isStandard { - // Test passes since function returned standard for a - // transaction which is intended to be standard. - continue - } - if err == nil && !test.isStandard { - t.Errorf("checkTransactionStandard (%s): standard when "+ - "it should not be", test.name) - continue - } - if err != nil && test.isStandard { - t.Errorf("checkTransactionStandard (%s): nonstandard "+ - "when it should not be: %v", test.name, err) - continue - } - - // Ensure error type is a TxRuleError inside of a RuleError. - var ruleErr RuleError - if !errors.As(err, &ruleErr) { - t.Errorf("checkTransactionStandard (%s): unexpected "+ - "error type - got %T", test.name, err) - continue - } - txRuleErr, ok := ruleErr.Err.(TxRuleError) - if !ok { - t.Errorf("checkTransactionStandard (%s): unexpected "+ - "error type - got %T", test.name, ruleErr.Err) - continue - } - - // Ensure the reject code is the expected one. - if txRuleErr.RejectCode != test.code { - t.Errorf("checkTransactionStandard (%s): unexpected "+ - "error code - got %v, want %v", test.name, - txRuleErr.RejectCode, test.code) - continue - } - } -} diff --git a/domain/miningmanager/mempool/remove_transaction.go b/domain/miningmanager/mempool/remove_transaction.go new file mode 100644 index 000000000..38e7efb9f --- /dev/null +++ b/domain/miningmanager/mempool/remove_transaction.go @@ -0,0 +1,66 @@ +package mempool + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" +) + +func (mp *mempool) removeTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error { + for _, transaction := range transactions { + err := mp.removeTransaction(consensushashing.TransactionID(transaction), removeRedeemers) + if err != nil { + return err + } + } + return nil +} + +func (mp *mempool) removeTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error { + if _, ok := mp.orphansPool.allOrphans[*transactionID]; ok { + return mp.orphansPool.removeOrphan(transactionID, true) + } + + mempoolTransaction, ok := mp.transactionsPool.allTransactions[*transactionID] + if !ok { + return nil + } + + transactionsToRemove := []*model.MempoolTransaction{mempoolTransaction} + if removeRedeemers { + redeemers := mp.transactionsPool.getRedeemers(mempoolTransaction) + transactionsToRemove = append(transactionsToRemove, redeemers...) + } + + for _, transactionToRemove := range transactionsToRemove { + err := mp.removeTransactionFromSets(transactionToRemove, removeRedeemers) + if err != nil { + return err + } + } + + if removeRedeemers { + err := mp.orphansPool.removeRedeemersOf(mempoolTransaction) + if err != nil { + return err + } + } + + return nil +} + +func (mp *mempool) removeTransactionFromSets(mempoolTransaction *model.MempoolTransaction, removeRedeemers bool) error { + mp.mempoolUTXOSet.removeTransaction(mempoolTransaction) + + err := mp.transactionsPool.removeTransaction(mempoolTransaction) + if err != nil { + return err + } + + err = mp.orphansPool.updateOrphansAfterTransactionRemoved(mempoolTransaction, removeRedeemers) + if err != nil { + return err + } + + return nil +} diff --git a/domain/miningmanager/mempool/revalidate_high_priority_transactions.go b/domain/miningmanager/mempool/revalidate_high_priority_transactions.go new file mode 100644 index 000000000..d4f3a286e --- /dev/null +++ b/domain/miningmanager/mempool/revalidate_high_priority_transactions.go @@ -0,0 +1,48 @@ +package mempool + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" +) + +func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTransaction, error) { + validTransactions := []*externalapi.DomainTransaction{} + + for _, transaction := range mp.transactionsPool.highPriorityTransactions { + isValid, err := mp.revalidateTransaction(transaction) + if err != nil { + return nil, err + } + if !isValid { + continue + } + + validTransactions = append(validTransactions, transaction.Transaction()) + } + + return validTransactions, nil +} + +func (mp *mempool) revalidateTransaction(transaction *model.MempoolTransaction) (isValid bool, err error) { + clearInputs(transaction) + + _, missingParents, err := mp.fillInputsAndGetMissingParents(transaction.Transaction()) + if err != nil { + return false, err + } + if len(missingParents) > 0 { + err := mp.removeTransaction(transaction.TransactionID(), true) + if err != nil { + return false, err + } + return false, nil + } + + return true, nil +} + +func clearInputs(transaction *model.MempoolTransaction) { + for _, input := range transaction.Transaction().Inputs { + input.UTXOEntry = nil + } +} diff --git a/domain/miningmanager/mempool/transactions_pool.go b/domain/miningmanager/mempool/transactions_pool.go new file mode 100644 index 000000000..bcd43f935 --- /dev/null +++ b/domain/miningmanager/mempool/transactions_pool.go @@ -0,0 +1,203 @@ +package mempool + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" +) + +type transactionsPool struct { + mempool *mempool + allTransactions model.IDToTransactionMap + highPriorityTransactions model.IDToTransactionMap + chainedTransactionsByPreviousOutpoint model.OutpointToTransactionMap + transactionsOrderedByFeeRate model.TransactionsOrderedByFeeRate + lastExpireScan uint64 +} + +func newTransactionsPool(mp *mempool) *transactionsPool { + return &transactionsPool{ + mempool: mp, + allTransactions: model.IDToTransactionMap{}, + highPriorityTransactions: model.IDToTransactionMap{}, + chainedTransactionsByPreviousOutpoint: model.OutpointToTransactionMap{}, + transactionsOrderedByFeeRate: model.TransactionsOrderedByFeeRate{}, + lastExpireScan: 0, + } +} + +func (tp *transactionsPool) addTransaction(transaction *externalapi.DomainTransaction, + parentTransactionsInPool model.OutpointToTransactionMap, isHighPriority bool) (*model.MempoolTransaction, error) { + + virtualDAAScore, err := tp.mempool.consensus.GetVirtualDAAScore() + if err != nil { + return nil, err + } + + mempoolTransaction := model.NewMempoolTransaction( + transaction, parentTransactionsInPool, isHighPriority, virtualDAAScore) + + err = tp.addMempoolTransaction(mempoolTransaction) + if err != nil { + return nil, err + } + + return mempoolTransaction, nil +} + +func (tp *transactionsPool) addMempoolTransaction(transaction *model.MempoolTransaction) error { + tp.allTransactions[*transaction.TransactionID()] = transaction + + for outpoint, parentTransactionInPool := range transaction.ParentTransactionsInPool() { + tp.chainedTransactionsByPreviousOutpoint[outpoint] = parentTransactionInPool + } + + tp.mempool.mempoolUTXOSet.addTransaction(transaction) + + err := tp.transactionsOrderedByFeeRate.Push(transaction) + if err != nil { + return err + } + + if transaction.IsHighPriority() { + tp.highPriorityTransactions[*transaction.TransactionID()] = transaction + } + + return nil +} + +func (tp *transactionsPool) removeTransaction(transaction *model.MempoolTransaction) error { + delete(tp.allTransactions, *transaction.TransactionID()) + + err := tp.transactionsOrderedByFeeRate.Remove(transaction) + if err != nil { + return err + } + + delete(tp.highPriorityTransactions, *transaction.TransactionID()) + + for outpoint := range transaction.ParentTransactionsInPool() { + delete(tp.chainedTransactionsByPreviousOutpoint, outpoint) + } + + return nil +} + +func (tp *transactionsPool) expireOldTransactions() error { + virtualDAAScore, err := tp.mempool.consensus.GetVirtualDAAScore() + if err != nil { + return err + } + + if virtualDAAScore-tp.lastExpireScan < tp.mempool.config.TransactionExpireScanIntervalDAAScore { + return nil + } + + for _, mempoolTransaction := range tp.allTransactions { + // Never expire high priority transactions + if mempoolTransaction.IsHighPriority() { + continue + } + + // Remove all transactions whose addedAtDAAScore is older then TransactionExpireIntervalDAAScore + if virtualDAAScore-mempoolTransaction.AddedAtDAAScore() > tp.mempool.config.TransactionExpireIntervalDAAScore { + err = tp.mempool.removeTransaction(mempoolTransaction.TransactionID(), true) + if err != nil { + return err + } + } + } + + tp.lastExpireScan = virtualDAAScore + return nil +} + +func (tp *transactionsPool) allReadyTransactions() []*externalapi.DomainTransaction { + result := []*externalapi.DomainTransaction{} + + for _, mempoolTransaction := range tp.allTransactions { + if len(mempoolTransaction.ParentTransactionsInPool()) == 0 { + result = append(result, mempoolTransaction.Transaction()) + } + } + + return result +} + +func (tp *transactionsPool) getParentTransactionsInPool( + transaction *externalapi.DomainTransaction) model.OutpointToTransactionMap { + + parentsTransactionsInPool := model.OutpointToTransactionMap{} + + for _, input := range transaction.Inputs { + if transaction, ok := tp.allTransactions[input.PreviousOutpoint.TransactionID]; ok { + parentsTransactionsInPool[input.PreviousOutpoint] = transaction + } + } + + return parentsTransactionsInPool +} + +func (tp *transactionsPool) getRedeemers(transaction *model.MempoolTransaction) []*model.MempoolTransaction { + queue := []*model.MempoolTransaction{transaction} + redeemers := []*model.MempoolTransaction{} + for len(queue) > 0 { + var current *model.MempoolTransaction + current, queue = queue[0], queue[1:] + + outpoint := externalapi.DomainOutpoint{TransactionID: *current.TransactionID()} + for i := range current.Transaction().Outputs { + outpoint.Index = uint32(i) + if redeemerTransaction, ok := tp.chainedTransactionsByPreviousOutpoint[outpoint]; ok { + queue = append(queue, redeemerTransaction) + redeemers = append(redeemers, redeemerTransaction) + } + } + } + return redeemers +} + +func (tp *transactionsPool) limitTransactionCount() error { + currentIndex := 0 + + for uint64(len(tp.allTransactions)) > tp.mempool.config.MaximumTransactionCount { + var transactionToRemove *model.MempoolTransaction + for { + transactionToRemove = tp.transactionsOrderedByFeeRate.GetByIndex(currentIndex) + if !transactionToRemove.IsHighPriority() { + break + } + currentIndex++ + if currentIndex >= len(tp.allTransactions) { + log.Warnf( + "Number of high-priority transactions in mempool (%d) is higher than maximum allowed (%d)", + len(tp.allTransactions), tp.mempool.config.MaximumTransactionCount) + return nil + } + } + + err := tp.mempool.removeTransaction(transactionToRemove.TransactionID(), true) + if err != nil { + return err + } + } + return nil +} + +func (tp *transactionsPool) getTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { + if mempoolTransaction, ok := tp.allTransactions[*transactionID]; ok { + return mempoolTransaction.Transaction(), true + } + return nil, false +} + +func (tp *transactionsPool) getAllTransactions() []*externalapi.DomainTransaction { + allTransactions := make([]*externalapi.DomainTransaction, 0, len(tp.allTransactions)) + for _, mempoolTransaction := range tp.allTransactions { + allTransactions = append(allTransactions, mempoolTransaction.Transaction()) + } + return allTransactions +} + +func (tp *transactionsPool) transactionCount() int { + return len(tp.allTransactions) +} diff --git a/domain/miningmanager/mempool/validate_and_insert_transaction.go b/domain/miningmanager/mempool/validate_and_insert_transaction.go new file mode 100644 index 000000000..294994e95 --- /dev/null +++ b/domain/miningmanager/mempool/validate_and_insert_transaction.go @@ -0,0 +1,56 @@ +package mempool + +import ( + "fmt" + + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" +) + +func (mp *mempool) validateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, + allowOrphan bool) (acceptedTransactions []*externalapi.DomainTransaction, err error) { + + err = mp.validateTransactionPreUTXOEntry(transaction) + if err != nil { + return nil, err + } + + parentsInPool, missingOutpoints, err := mp.fillInputsAndGetMissingParents(transaction) + if err != nil { + return nil, err + } + + if len(missingOutpoints) > 0 { + if !allowOrphan { + str := fmt.Sprintf("Transaction %s is an orphan, where allowOrphan = false", + consensushashing.TransactionID(transaction)) + return nil, transactionRuleError(RejectBadOrphan, str) + } + + return nil, mp.orphansPool.maybeAddOrphan(transaction, isHighPriority) + } + + err = mp.validateTransactionInContext(transaction) + if err != nil { + return nil, err + } + + mempoolTransaction, err := mp.transactionsPool.addTransaction(transaction, parentsInPool, isHighPriority) + if err != nil { + return nil, err + } + + acceptedOrphans, err := mp.orphansPool.processOrphansAfterAcceptedTransaction(mempoolTransaction.Transaction()) + if err != nil { + return nil, err + } + + acceptedTransactions = append([]*externalapi.DomainTransaction{transaction}, acceptedOrphans...) + + err = mp.transactionsPool.limitTransactionCount() + if err != nil { + return nil, err + } + + return acceptedTransactions, nil +} diff --git a/domain/miningmanager/mempool/validate_transaction.go b/domain/miningmanager/mempool/validate_transaction.go new file mode 100644 index 000000000..a4910d1ff --- /dev/null +++ b/domain/miningmanager/mempool/validate_transaction.go @@ -0,0 +1,71 @@ +package mempool + +import ( + "fmt" + + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" +) + +func (mp *mempool) validateTransactionPreUTXOEntry(transaction *externalapi.DomainTransaction) error { + err := mp.validateTransactionInIsolation(transaction) + if err != nil { + return err + } + + if err := mp.mempoolUTXOSet.checkDoubleSpends(transaction); err != nil { + return err + } + return nil +} + +func (mp *mempool) validateTransactionInIsolation(transaction *externalapi.DomainTransaction) error { + transactionID := consensushashing.TransactionID(transaction) + if _, ok := mp.transactionsPool.allTransactions[*transactionID]; ok { + return transactionRuleError(RejectDuplicate, + fmt.Sprintf("transaction %s is already in the mempool", transactionID)) + } + + if !mp.config.AcceptNonStandard { + if err := mp.checkTransactionStandardInIsolation(transaction); err != nil { + // Attempt to extract a reject code from the error so + // it can be retained. When not possible, fall back to + // a non standard error. + rejectCode, found := extractRejectCode(err) + if !found { + rejectCode = RejectNonstandard + } + str := fmt.Sprintf("transaction %s is not standard: %s", transactionID, err) + return transactionRuleError(rejectCode, str) + } + } + + return nil +} + +func (mp *mempool) validateTransactionInContext(transaction *externalapi.DomainTransaction) error { + transactionID := consensushashing.TransactionID(transaction) + if transaction.Mass > mp.config.MaximumMassAcceptedByBlock { + return transactionRuleError(RejectInvalid, fmt.Sprintf("transaction %s mass is %d which is "+ + "higher than the maxmimum of %d", transactionID, + transaction.Mass, mp.config.MaximumMassAcceptedByBlock)) + } + + if !mp.config.AcceptNonStandard { + err := mp.checkTransactionStandardInContext(transaction) + if err != nil { + // Attempt to extract a reject code from the error so + // it can be retained. When not possible, fall back to + // a non standard error. + rejectCode, found := extractRejectCode(err) + if !found { + rejectCode = RejectNonstandard + } + str := fmt.Sprintf("transaction inputs %s are not standard: %s", + consensushashing.TransactionID(transaction), err) + return transactionRuleError(rejectCode, str) + } + } + + return nil +} diff --git a/domain/miningmanager/miningmanager.go b/domain/miningmanager/miningmanager.go index bb393c100..f34126a86 100644 --- a/domain/miningmanager/miningmanager.go +++ b/domain/miningmanager/miningmanager.go @@ -1,19 +1,21 @@ package miningmanager import ( - consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" miningmanagermodel "github.com/kaspanet/kaspad/domain/miningmanager/model" ) // MiningManager creates block templates for mining as well as maintaining // known transactions that have no yet been added to any block type MiningManager interface { - GetBlockTemplate(coinbaseData *consensusexternalapi.DomainCoinbaseData) (*consensusexternalapi.DomainBlock, error) - GetTransaction(transactionID *consensusexternalapi.DomainTransactionID) (*consensusexternalapi.DomainTransaction, bool) - AllTransactions() []*consensusexternalapi.DomainTransaction + GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainBlock, error) + GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) + AllTransactions() []*externalapi.DomainTransaction TransactionCount() int - HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction) ([]*consensusexternalapi.DomainTransaction, error) - ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction, allowOrphan bool) error + HandleNewBlockTransactions(txs []*externalapi.DomainTransaction) ([]*externalapi.DomainTransaction, error) + ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) ( + acceptedTransactions []*externalapi.DomainTransaction, err error) + RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) } type miningManager struct { @@ -22,32 +24,40 @@ type miningManager struct { } // GetBlockTemplate creates a block template for a miner to consume -func (mm *miningManager) GetBlockTemplate(coinbaseData *consensusexternalapi.DomainCoinbaseData) (*consensusexternalapi.DomainBlock, error) { +func (mm *miningManager) GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainBlock, error) { return mm.blockTemplateBuilder.GetBlockTemplate(coinbaseData) } // HandleNewBlock handles the transactions for a new block that was just added to the DAG -func (mm *miningManager) HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction) ([]*consensusexternalapi.DomainTransaction, error) { +func (mm *miningManager) HandleNewBlockTransactions(txs []*externalapi.DomainTransaction) ([]*externalapi.DomainTransaction, error) { return mm.mempool.HandleNewBlockTransactions(txs) } // ValidateAndInsertTransaction validates the given transaction, and // adds it to the set of known transactions that have not yet been // added to any block -func (mm *miningManager) ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction, allowOrphan bool) error { - return mm.mempool.ValidateAndInsertTransaction(transaction, allowOrphan) +func (mm *miningManager) ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, + isHighPriority bool, allowOrphan bool) (acceptedTransactions []*externalapi.DomainTransaction, err error) { + + return mm.mempool.ValidateAndInsertTransaction(transaction, isHighPriority, allowOrphan) } func (mm *miningManager) GetTransaction( - transactionID *consensusexternalapi.DomainTransactionID) (*consensusexternalapi.DomainTransaction, bool) { + transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { return mm.mempool.GetTransaction(transactionID) } -func (mm *miningManager) AllTransactions() []*consensusexternalapi.DomainTransaction { +func (mm *miningManager) AllTransactions() []*externalapi.DomainTransaction { return mm.mempool.AllTransactions() } func (mm *miningManager) TransactionCount() int { return mm.mempool.TransactionCount() } + +func (mm *miningManager) RevalidateHighPriorityTransactions() ( + validTransactions []*externalapi.DomainTransaction, err error) { + + return mm.mempool.RevalidateHighPriorityTransactions() +} diff --git a/domain/miningmanager/miningmanager_test.go b/domain/miningmanager/miningmanager_test.go index 4cde5d3d1..cf9ead85c 100644 --- a/domain/miningmanager/miningmanager_test.go +++ b/domain/miningmanager/miningmanager_test.go @@ -1,10 +1,12 @@ package miningmanager_test import ( - "github.com/kaspanet/kaspad/domain/miningmanager/mempool" + "reflect" "strings" "testing" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool" + "github.com/kaspanet/kaspad/domain/consensus" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/testapi" @@ -33,11 +35,11 @@ func TestValidateAndInsertTransaction(t *testing.T) { defer teardown(false) miningFactory := miningmanager.NewFactory() - miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params) + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempool.DefaultConfig(&consensusConfig.Params)) transactionsToInsert := make([]*externalapi.DomainTransaction, 10) for i := range transactionsToInsert { transactionsToInsert[i] = createTransactionWithUTXOEntry(t, i) - err = miningManager.ValidateAndInsertTransaction(transactionsToInsert[i], true) + _, err = miningManager.ValidateAndInsertTransaction(transactionsToInsert[i], false, true) if err != nil { t.Fatalf("ValidateAndInsertTransaction: %v", err) } @@ -56,11 +58,11 @@ func TestValidateAndInsertTransaction(t *testing.T) { // The parent's transaction was inserted by consensus(AddBlock), and we want to verify that // the transaction is not considered an orphan and inserted into the mempool. - transactionNotAnOrphan, err := createChildTxWhenParentTxWasAddedByConsensus(consensusConfig, tc) + transactionNotAnOrphan, err := createChildAndParentTxsAndAddParentToConsensus(tc) if err != nil { t.Fatalf("Error in createParentAndChildrenTransaction: %v", err) } - err = miningManager.ValidateAndInsertTransaction(transactionNotAnOrphan, true) + _, err = miningManager.ValidateAndInsertTransaction(transactionNotAnOrphan, false, true) if err != nil { t.Fatalf("ValidateAndInsertTransaction: %v", err) } @@ -81,9 +83,9 @@ func TestImmatureSpend(t *testing.T) { defer teardown(false) miningFactory := miningmanager.NewFactory() - miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params) + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempool.DefaultConfig(&consensusConfig.Params)) tx := createTransactionWithUTXOEntry(t, 0) - err = miningManager.ValidateAndInsertTransaction(tx, false) + _, err = miningManager.ValidateAndInsertTransaction(tx, false, false) txRuleError := &mempool.TxRuleError{} if !errors.As(err, txRuleError) || txRuleError.RejectCode != mempool.RejectImmatureSpend { t.Fatalf("Unexpected error %+v", err) @@ -108,14 +110,48 @@ func TestInsertDoubleTransactionsToMempool(t *testing.T) { defer teardown(false) miningFactory := miningmanager.NewFactory() - miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params) + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempool.DefaultConfig(&consensusConfig.Params)) transaction := createTransactionWithUTXOEntry(t, 0) - err = miningManager.ValidateAndInsertTransaction(transaction, true) + _, err = miningManager.ValidateAndInsertTransaction(transaction, false, true) if err != nil { t.Fatalf("ValidateAndInsertTransaction: %v", err) } - err = miningManager.ValidateAndInsertTransaction(transaction, true) - if err == nil || !strings.Contains(err.Error(), "already have transaction") { + _, err = miningManager.ValidateAndInsertTransaction(transaction, false, true) + if err == nil || !strings.Contains(err.Error(), "is already in the mempool") { + t.Fatalf("ValidateAndInsertTransaction: %v", err) + } + }) +} + +// TestDoubleSpendInMempool verifies that an attempt to insert a transaction double-spending +// another transaction already in the mempool will result in raising an appropriate error. +func TestDoubleSpendInMempool(t *testing.T) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + consensusConfig.BlockCoinbaseMaturity = 0 + factory := consensus.NewFactory() + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestDoubleSpendInMempool") + if err != nil { + t.Fatalf("Error setting up TestConsensus: %+v", err) + } + defer teardown(false) + + miningFactory := miningmanager.NewFactory() + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempool.DefaultConfig(&consensusConfig.Params)) + transaction, err := createChildAndParentTxsAndAddParentToConsensus(tc) + if err != nil { + t.Fatalf("Error creating transaction: %+v", err) + } + _, err = miningManager.ValidateAndInsertTransaction(transaction, false, true) + if err != nil { + t.Fatalf("ValidateAndInsertTransaction: %v", err) + } + + doubleSpendingTransaction := transaction.Clone() + doubleSpendingTransaction.ID = nil + doubleSpendingTransaction.Outputs[0].Value-- // do some minor change so that txID is different + + _, err = miningManager.ValidateAndInsertTransaction(doubleSpendingTransaction, false, true) + if err == nil || !strings.Contains(err.Error(), "already spent by transaction") { t.Fatalf("ValidateAndInsertTransaction: %v", err) } }) @@ -133,12 +169,12 @@ func TestHandleNewBlockTransactions(t *testing.T) { defer teardown(false) miningFactory := miningmanager.NewFactory() - miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params) + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempool.DefaultConfig(&consensusConfig.Params)) transactionsToInsert := make([]*externalapi.DomainTransaction, 10) for i := range transactionsToInsert { transaction := createTransactionWithUTXOEntry(t, i) transactionsToInsert[i] = transaction - err = miningManager.ValidateAndInsertTransaction(transaction, true) + _, err = miningManager.ValidateAndInsertTransaction(transaction, false, true) if err != nil { t.Fatalf("ValidateAndInsertTransaction: %v", err) } @@ -186,22 +222,22 @@ func domainBlocksToBlockIds(blocks []*externalapi.DomainTransaction) []*external return blockIDs } -// TestDoubleSpends verifies that any transactions which are now double spends as a result of the block's new transactions +// TestDoubleSpendWithBlock verifies that any transactions which are now double spends as a result of the block's new transactions // will be removed from the mempool. -func TestDoubleSpends(t *testing.T) { +func TestDoubleSpendWithBlock(t *testing.T) { testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { consensusConfig.BlockCoinbaseMaturity = 0 factory := consensus.NewFactory() - tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestDoubleSpends") + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestDoubleSpendWithBlock") if err != nil { t.Fatalf("Failed setting up TestConsensus: %+v", err) } defer teardown(false) miningFactory := miningmanager.NewFactory() - miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params) + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempool.DefaultConfig(&consensusConfig.Params)) transactionInTheMempool := createTransactionWithUTXOEntry(t, 0) - err = miningManager.ValidateAndInsertTransaction(transactionInTheMempool, true) + _, err = miningManager.ValidateAndInsertTransaction(transactionInTheMempool, false, true) if err != nil { t.Fatalf("ValidateAndInsertTransaction: %v", err) } @@ -222,7 +258,6 @@ func TestDoubleSpends(t *testing.T) { // TestOrphanTransactions verifies that a transaction could be a part of a new block template, only if it's not an orphan. func TestOrphanTransactions(t *testing.T) { testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { - consensusConfig.BlockCoinbaseMaturity = 0 factory := consensus.NewFactory() tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestOrphanTransactions") @@ -232,14 +267,14 @@ func TestOrphanTransactions(t *testing.T) { defer teardown(false) miningFactory := miningmanager.NewFactory() - miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params) + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempool.DefaultConfig(&consensusConfig.Params)) // Before each parent transaction, We will add two blocks by consensus in order to fund the parent transactions. parentTransactions, childTransactions, err := createArraysOfParentAndChildrenTransactions(tc) if err != nil { t.Fatalf("Error in createArraysOfParentAndChildrenTransactions: %v", err) } for _, orphanTransaction := range childTransactions { - err = miningManager.ValidateAndInsertTransaction(orphanTransaction, true) + _, err = miningManager.ValidateAndInsertTransaction(orphanTransaction, false, true) if err != nil { t.Fatalf("ValidateAndInsertTransaction: %v", err) } @@ -284,7 +319,7 @@ func TestOrphanTransactions(t *testing.T) { } _, err = miningManager.HandleNewBlockTransactions(blockParentsTransactions.Transactions) if err != nil { - t.Fatalf("HandleNewBlockTransactions: %v", err) + t.Fatalf("HandleNewBlockTransactions: %+v", err) } transactionsMempool = miningManager.AllTransactions() if len(transactionsMempool) != len(childTransactions) { @@ -318,6 +353,198 @@ func TestOrphanTransactions(t *testing.T) { }) } +func TestHighPriorityTransactions(t *testing.T) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + consensusConfig.BlockCoinbaseMaturity = 0 + factory := consensus.NewFactory() + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestDoubleSpendWithBlock") + if err != nil { + t.Fatalf("Failed setting up TestConsensus: %+v", err) + } + defer teardown(false) + + miningFactory := miningmanager.NewFactory() + mempoolConfig := mempool.DefaultConfig(&consensusConfig.Params) + mempoolConfig.MaximumTransactionCount = 1 + mempoolConfig.MaximumOrphanTransactionCount = 1 + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempoolConfig) + + // Create 3 pairs of transaction parent-and-child pairs: 1 low priority and 2 high priority + lowPriorityParentTransaction, lowPriorityChildTransaction, err := createParentAndChildrenTransactions(tc) + if err != nil { + t.Fatalf("error creating low-priority transaction pair: %+v", err) + } + firstHighPriorityParentTransaction, firstHighPriorityChildTransaction, err := createParentAndChildrenTransactions(tc) + if err != nil { + t.Fatalf("error creating first high-priority transaction pair: %+v", err) + } + secondHighPriorityParentTransaction, secondHighPriorityChildTransaction, err := createParentAndChildrenTransactions(tc) + if err != nil { + t.Fatalf("error creating second high-priority transaction pair: %+v", err) + } + + // Submit all the children, make sure the 2 highPriority ones remain in the orphan pool + _, err = miningManager.ValidateAndInsertTransaction(lowPriorityChildTransaction, false, true) + if err != nil { + t.Fatalf("error submitting low-priority transaction: %+v", err) + } + _, err = miningManager.ValidateAndInsertTransaction(firstHighPriorityChildTransaction, true, true) + if err != nil { + t.Fatalf("error submitting first high-priority transaction: %+v", err) + } + _, err = miningManager.ValidateAndInsertTransaction(secondHighPriorityChildTransaction, true, true) + if err != nil { + t.Fatalf("error submitting second high-priority transaction: %+v", err) + } + // There's no API to check what stayed in the orphan pool, but we'll find it out when we begin to unorphan + + // Submit all the parents. + // Low priority transaction will only accept the parent, since the child was evicted from orphanPool + lowPriorityAcceptedTransactions, err := + miningManager.ValidateAndInsertTransaction(lowPriorityParentTransaction, false, true) + if err != nil { + t.Fatalf("error submitting low-priority transaction: %+v", err) + } + expectedLowPriorityAcceptedTransactions := []*externalapi.DomainTransaction{lowPriorityParentTransaction} + if !reflect.DeepEqual(lowPriorityAcceptedTransactions, expectedLowPriorityAcceptedTransactions) { + t.Errorf("Expected only lowPriorityParent (%v) to be in lowPriorityAcceptedTransactions, but got %v", + consensushashing.TransactionIDs(expectedLowPriorityAcceptedTransactions), + consensushashing.TransactionIDs(lowPriorityAcceptedTransactions)) + } + + // Both high priority transactions should accept parent and child + + // Insert firstHighPriorityParentTransaction + firstHighPriorityAcceptedTransactions, err := + miningManager.ValidateAndInsertTransaction(firstHighPriorityParentTransaction, true, true) + if err != nil { + t.Fatalf("error submitting first high-priority transaction: %+v", err) + } + expectedFirstHighPriorityAcceptedTransactions := + []*externalapi.DomainTransaction{firstHighPriorityParentTransaction, firstHighPriorityChildTransaction} + if !reflect.DeepEqual(firstHighPriorityAcceptedTransactions, expectedFirstHighPriorityAcceptedTransactions) { + t.Errorf( + "Expected both firstHighPriority transaction (%v) to be in firstHighPriorityAcceptedTransactions, but got %v", + consensushashing.TransactionIDs(firstHighPriorityAcceptedTransactions), + consensushashing.TransactionIDs(expectedFirstHighPriorityAcceptedTransactions)) + } + // Insert secondHighPriorityParentTransaction + secondHighPriorityAcceptedTransactions, err := + miningManager.ValidateAndInsertTransaction(secondHighPriorityParentTransaction, true, true) + if err != nil { + t.Fatalf("error submitting second high-priority transaction: %+v", err) + } + expectedSecondHighPriorityAcceptedTransactions := + []*externalapi.DomainTransaction{secondHighPriorityParentTransaction, secondHighPriorityChildTransaction} + if !reflect.DeepEqual(secondHighPriorityAcceptedTransactions, expectedSecondHighPriorityAcceptedTransactions) { + t.Errorf( + "Expected both secondHighPriority transaction (%v) to be in secondHighPriorityAcceptedTransactions, but got %v", + consensushashing.TransactionIDs(secondHighPriorityAcceptedTransactions), + consensushashing.TransactionIDs(expectedSecondHighPriorityAcceptedTransactions)) + } + }) +} + +func TestRevalidateHighPriorityTransactions(t *testing.T) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + consensusConfig.BlockCoinbaseMaturity = 0 + factory := consensus.NewFactory() + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestRevalidateHighPriorityTransactions") + if err != nil { + t.Fatalf("Failed setting up TestConsensus: %+v", err) + } + defer teardown(false) + + miningFactory := miningmanager.NewFactory() + mempoolConfig := mempool.DefaultConfig(&consensusConfig.Params) + miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params, mempoolConfig) + + // Create two valid transactions that double-spend each other (childTransaction1, childTransaction2) + parentTransaction, childTransaction1, err := createParentAndChildrenTransactions(tc) + if err != nil { + t.Fatalf("Error creating parentTransaction and childTransaction1: %+v", err) + } + tips, err := tc.Tips() + if err != nil { + t.Fatalf("Error getting tips: %+v", err) + } + + fundingBlock, _, err := tc.AddBlock(tips, nil, []*externalapi.DomainTransaction{parentTransaction}) + if err != nil { + t.Fatalf("Error getting function block: %+v", err) + } + + childTransaction2 := childTransaction1.Clone() + childTransaction2.Outputs[0].Value-- // decrement value to change id + + // Mine 1 block with confirming childTransaction1 and 2 blocks confirming childTransaction2, so that + // childTransaction2 is accepted + tip1, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock}, nil, + []*externalapi.DomainTransaction{childTransaction1}) + if err != nil { + t.Fatalf("Error adding tip1: %+v", err) + } + tip2, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock}, nil, + []*externalapi.DomainTransaction{childTransaction2}) + if err != nil { + t.Fatalf("Error adding tip2: %+v", err) + } + _, _, err = tc.AddBlock([]*externalapi.DomainHash{tip2}, nil, nil) + if err != nil { + t.Fatalf("Error mining on top of tip2: %+v", err) + } + + // Add to mempool transaction that spends childTransaction2 (as high priority) + spendingTransaction, err := testutils.CreateTransaction(childTransaction2, 1000) + if err != nil { + t.Fatalf("Error creating spendingTransaction: %+v", err) + } + _, err = miningManager.ValidateAndInsertTransaction(spendingTransaction, true, false) + if err != nil { + t.Fatalf("Error inserting spendingTransaction: %+v", err) + } + + // Revalidate, to make sure spendingTransaction is still valid + validTransactions, err := miningManager.RevalidateHighPriorityTransactions() + if err != nil { + t.Fatalf("Error from first RevalidateHighPriorityTransactions: %+v", err) + } + if len(validTransactions) != 1 || !validTransactions[0].Equal(spendingTransaction) { + t.Fatalf("Expected to have spendingTransaction as only validTransaction returned from "+ + "RevalidateHighPriorityTransactions, but got %v instead", validTransactions) + } + + // Mine 2 more blocks on top of tip1, to re-org out childTransaction1, thus making spendingTransaction invalid + for i := 0; i < 2; i++ { + tip1, _, err = tc.AddBlock([]*externalapi.DomainHash{tip1}, nil, nil) + if err != nil { + t.Fatalf("Error mining on top of tip1: %+v", err) + } + } + + // Make sure spendingTransaction is still in mempool + allTransactions := miningManager.AllTransactions() + if len(allTransactions) != 1 || !allTransactions[0].Equal(spendingTransaction) { + t.Fatalf("Expected to have spendingTransaction as only validTransaction returned from "+ + "RevalidateHighPriorityTransactions, but got %v instead", validTransactions) + } + + // Revalidate again, this time validTransactions should be empty + validTransactions, err = miningManager.RevalidateHighPriorityTransactions() + if err != nil { + t.Fatalf("Error from first RevalidateHighPriorityTransactions: %+v", err) + } + if len(validTransactions) != 0 { + t.Fatalf("Expected to have empty validTransactions, but got %v instead", validTransactions) + } + // And also AllTransactions should be empty as well + allTransactions = miningManager.AllTransactions() + if len(allTransactions) != 0 { + t.Fatalf("Expected to have empty allTransactions, but got %v instead", allTransactions) + } + }) +} + func createTransactionWithUTXOEntry(t *testing.T, i int) *externalapi.DomainTransaction { prevOutTxID := externalapi.DomainTransactionID{} prevOutPoint := externalapi.DomainOutpoint{TransactionID: prevOutTxID, Index: uint32(i)} @@ -369,8 +596,8 @@ func createArraysOfParentAndChildrenTransactions(tc testapi.TestConsensus) ([]*e return parentTransactions, transactions, nil } -func createParentAndChildrenTransactions(tc testapi.TestConsensus) (*externalapi.DomainTransaction, - *externalapi.DomainTransaction, error) { +func createParentAndChildrenTransactions(tc testapi.TestConsensus) (txParent *externalapi.DomainTransaction, + txChild *externalapi.DomainTransaction, err error) { // We will add two blocks by consensus before the parent transactions, in order to fund the parent transactions. tips, err := tc.Tips() @@ -397,19 +624,19 @@ func createParentAndChildrenTransactions(tc testapi.TestConsensus) (*externalapi return nil, nil, errors.Wrap(err, "GetBlock: ") } fundingTransactionForParent := fundingBlockForParent.Transactions[transactionhelper.CoinbaseTransactionIndex] - txParent, err := testutils.CreateTransaction(fundingTransactionForParent, 1000) + txParent, err = testutils.CreateTransaction(fundingTransactionForParent, 1000) if err != nil { return nil, nil, err } - txChild, err := testutils.CreateTransaction(txParent, 1000) + txChild, err = testutils.CreateTransaction(txParent, 1000) if err != nil { return nil, nil, err } return txParent, txChild, nil } -func createChildTxWhenParentTxWasAddedByConsensus(consensusConfig *consensus.Config, tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) { - firstBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil) +func createChildAndParentTxsAndAddParentToConsensus(tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) { + firstBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{tc.DAGParams().GenesisHash}, nil, nil) if err != nil { return nil, errors.Wrapf(err, "AddBlock: %v", err) } diff --git a/domain/miningmanager/model/interface_mempool.go b/domain/miningmanager/model/interface_mempool.go index e4a3a5f6a..d2a383f7e 100644 --- a/domain/miningmanager/model/interface_mempool.go +++ b/domain/miningmanager/model/interface_mempool.go @@ -1,17 +1,20 @@ package model import ( - consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" ) // Mempool maintains a set of known transactions that // are intended to be mined into new blocks type Mempool interface { - HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction) ([]*consensusexternalapi.DomainTransaction, error) - BlockCandidateTransactions() []*consensusexternalapi.DomainTransaction - ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction, allowOrphan bool) error - RemoveTransactions(txs []*consensusexternalapi.DomainTransaction) error - GetTransaction(transactionID *consensusexternalapi.DomainTransactionID) (*consensusexternalapi.DomainTransaction, bool) - AllTransactions() []*consensusexternalapi.DomainTransaction + HandleNewBlockTransactions(txs []*externalapi.DomainTransaction) ([]*externalapi.DomainTransaction, error) + BlockCandidateTransactions() []*externalapi.DomainTransaction + ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) ( + acceptedTransactions []*externalapi.DomainTransaction, err error) + RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error + GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) + AllTransactions() []*externalapi.DomainTransaction TransactionCount() int + RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) + IsTransactionOutputDust(output *externalapi.DomainTransactionOutput) bool } diff --git a/go.mod b/go.mod index 58f8f42e5..64c221853 100644 --- a/go.mod +++ b/go.mod @@ -3,27 +3,15 @@ module github.com/kaspanet/kaspad go 1.16 require ( - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/sprig v2.22.0+incompatible // indirect - github.com/aokoli/goutils v1.1.1 // indirect github.com/btcsuite/btcutil v1.0.2 github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd github.com/btcsuite/winsvc v1.0.0 github.com/davecgh/go-spew v1.1.1 - github.com/envoyproxy/protoc-gen-validate v0.6.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 - github.com/google/uuid v1.2.0 // indirect - github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect github.com/jessevdk/go-flags v1.4.0 github.com/jrick/logrotate v1.0.0 github.com/kaspanet/go-muhash v0.0.4 github.com/kaspanet/go-secp256k1 v0.0.7 - github.com/kaspanet/protoc-gen-doc v1.4.0 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mwitkow/go-proto-validators v0.3.2 // indirect github.com/pkg/errors v0.9.1 github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/tyler-smith/go-bip39 v1.1.0 @@ -33,4 +21,5 @@ require ( google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 // indirect google.golang.org/grpc v1.38.0 google.golang.org/protobuf v1.26.0 + gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index d59a88892..29d055384 100644 --- a/go.sum +++ b/go.sum @@ -1,73 +1,35 @@ -cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= -github.com/aokoli/goutils v1.1.1 h1:/hA+Ywo3AxoDZY5ZMnkiEkUvkK4BPp927ax110KCqqg= -github.com/aokoli/goutils v1.1.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= -github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 h1:q1oJaUPdmpDm/VyXosjgPgr6wS7c5iV2p0PwJD73bUI= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.1 h1:4CF52PCseTFt4bE+Yk3dIpdVi7XWuPVMhPtm4FaIJPM= -github.com/envoyproxy/protoc-gen-validate v0.6.1/go.mod h1:txg5va2Qkip90uYoSKH+nkAAmXrb2j3iq4FLwdrCbXQ= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -87,20 +49,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7 h1:ux/56T2xqZO/3cP1I2F86qpeoYPCOzk+KF/UH/Ar+lk= -github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= -github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -110,84 +61,37 @@ github.com/kaspanet/go-muhash v0.0.4 h1:CQrm1RTJpQy+h4ZFjj9qq42K5fmA5QTGifzb47p4 github.com/kaspanet/go-muhash v0.0.4/go.mod h1:10bPW5mO1vNHPSejaAh9ZTtLZE16jzEvgaP7f3Q5s/8= github.com/kaspanet/go-secp256k1 v0.0.7 h1:WHnrwopKB6ZeHSbdAwwxNhTqflm56XT1mM6LF4/OvOs= github.com/kaspanet/go-secp256k1 v0.0.7/go.mod h1:cFbxhxKkxqHX5eIwUGKARkph19PehipDPJejWB+H0jM= -github.com/kaspanet/protoc-gen-doc v1.4.0 h1:d2U/0F9b8AVP6mCymHaHn3Ry+Zc8LXV0UGpfj+WS/w0= -github.com/kaspanet/protoc-gen-doc v1.4.0/go.mod h1:0kD54MmtdrGv/V/z+24+WN3LS607NTvPbraKoxIMDPM= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/lyft/protoc-gen-star v0.5.1 h1:sImehRT+p7lW9n6R7MQc5hVgzWGEkDVZU4AsBQ4Isu8= -github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= -github.com/mwitkow/go-proto-validators v0.3.2 h1:qRlmpTzm2pstMKKzTdvwPCF5QfBNURSlAgN/R+qbKos= -github.com/mwitkow/go-proto-validators v0.3.2/go.mod h1:ej0Qp0qMgHN/KtDyUt+Q1/tA7a5VarXUOUxD+oeD30w= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/pseudomuto/protokit v0.2.0 h1:hlnBDcy3YEDXH7kc9gV+NLaN0cDzhDvD1s7Y6FZ8RpM= -github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.3.4 h1:8q6vk3hthlpb2SouZcnBVKboxWQWMDNF38bwholZrJc= -github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -196,26 +100,18 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -226,36 +122,26 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 h1:pc16UedxnxXXtGxHCSUhafAoVHQZ0yXl8ZelMH4EETc= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -263,8 +149,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -279,19 +163,14 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/infrastructure/config/config.go b/infrastructure/config/config.go index d130d34ca..a3f8bdc85 100644 --- a/infrastructure/config/config.go +++ b/infrastructure/config/config.go @@ -109,7 +109,7 @@ type Flags struct { LogLevel string `short:"d" long:"loglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify =,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` Upnp bool `long:"upnp" description:"Use UPnP to map our listening port outside of NAT"` MinRelayTxFee float64 `long:"minrelaytxfee" description:"The minimum transaction fee in KAS/kB to be considered a non-zero fee."` - MaxOrphanTxs int `long:"maxorphantx" description:"Max number of orphan transactions to keep in memory"` + MaxOrphanTxs uint64 `long:"maxorphantx" description:"Max number of orphan transactions to keep in memory"` BlockMaxMass uint64 `long:"blockmaxmass" description:"Maximum transaction mass to be used when creating a block"` UserAgentComments []string `long:"uacomment" description:"Comment to add to the user agent -- See BIP 14 for more information."` NoPeerBloomFilters bool `long:"nopeerbloomfilters" description:"Disable bloom filtering support"` @@ -485,16 +485,6 @@ func LoadConfig() (*Config, error) { return nil, err } - // Limit the max orphan count to a sane value. - if cfg.MaxOrphanTxs < 0 { - str := "%s: The maxorphantx option may not be less than 0 " + - "-- parsed [%d]" - err := errors.Errorf(str, funcName, cfg.MaxOrphanTxs) - fmt.Fprintln(os.Stderr, err) - fmt.Fprintln(os.Stderr, usageMessage) - return nil, err - } - // Look for illegal characters in the user agent comments. for _, uaComment := range cfg.UserAgentComments { if strings.ContainsAny(uaComment, "/:()") { diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/messages_grpc.pb.go b/infrastructure/network/netadapter/server/grpcserver/protowire/messages_grpc.pb.go index 3e0081ab8..102e61cfd 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/messages_grpc.pb.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/messages_grpc.pb.go @@ -11,7 +11,7 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion7 +const _ = grpc.SupportPackageIsVersion6 // P2PClient is the client API for P2P service. // @@ -71,19 +71,12 @@ type P2PServer interface { type UnimplementedP2PServer struct { } -func (UnimplementedP2PServer) MessageStream(P2P_MessageStreamServer) error { +func (*UnimplementedP2PServer) MessageStream(P2P_MessageStreamServer) error { return status.Errorf(codes.Unimplemented, "method MessageStream not implemented") } -func (UnimplementedP2PServer) mustEmbedUnimplementedP2PServer() {} +func (*UnimplementedP2PServer) mustEmbedUnimplementedP2PServer() {} -// UnsafeP2PServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to P2PServer will -// result in compilation errors. -type UnsafeP2PServer interface { - mustEmbedUnimplementedP2PServer() -} - -func RegisterP2PServer(s grpc.ServiceRegistrar, srv P2PServer) { +func RegisterP2PServer(s *grpc.Server, srv P2PServer) { s.RegisterService(&_P2P_serviceDesc, srv) } @@ -186,19 +179,12 @@ type RPCServer interface { type UnimplementedRPCServer struct { } -func (UnimplementedRPCServer) MessageStream(RPC_MessageStreamServer) error { +func (*UnimplementedRPCServer) MessageStream(RPC_MessageStreamServer) error { return status.Errorf(codes.Unimplemented, "method MessageStream not implemented") } -func (UnimplementedRPCServer) mustEmbedUnimplementedRPCServer() {} +func (*UnimplementedRPCServer) mustEmbedUnimplementedRPCServer() {} -// UnsafeRPCServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to RPCServer will -// result in compilation errors. -type UnsafeRPCServer interface { - mustEmbedUnimplementedRPCServer() -} - -func RegisterRPCServer(s grpc.ServiceRegistrar, srv RPCServer) { +func RegisterRPCServer(s *grpc.Server, srv RPCServer) { s.RegisterService(&_RPC_serviceDesc, srv) } diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go index aeaf75a01..3561a9cd3 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go @@ -2268,6 +2268,7 @@ type SubmitTransactionRequestMessage struct { unknownFields protoimpl.UnknownFields Transaction *RpcTransaction `protobuf:"bytes,1,opt,name=transaction,proto3" json:"transaction,omitempty"` + AllowOrphan bool `protobuf:"varint,2,opt,name=allowOrphan,proto3" json:"allowOrphan,omitempty"` } func (x *SubmitTransactionRequestMessage) Reset() { @@ -2309,6 +2310,13 @@ func (x *SubmitTransactionRequestMessage) GetTransaction() *RpcTransaction { return nil } +func (x *SubmitTransactionRequestMessage) GetAllowOrphan() bool { + if x != nil { + return x.AllowOrphan + } + return false +} + type SubmitTransactionResponseMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -5552,363 +5560,366 @@ var file_rpc_proto_rawDesc = []byte{ 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x5e, 0x0a, 0x1f, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x70, 0x63, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x74, 0x0a, 0x20, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 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, - 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x37, 0x0a, 0x35, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x22, 0x64, 0x0a, 0x36, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, - 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, - 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, - 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xb3, 0x01, 0x0a, 0x34, 0x56, - 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, - 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x68, - 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, - 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x41, 0x0a, - 0x10, 0x61, 0x64, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, - 0x69, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x10, - 0x61, 0x64, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x22, 0x62, 0x0a, 0x0a, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x12, - 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x12, 0x40, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x73, 0x22, 0x5b, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x16, 0x61, 0x63, 0x63, - 0x65, 0x70, 0x74, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x61, 0x63, 0x63, 0x65, 0x70, - 0x74, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x73, 0x22, 0x5e, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, - 0x30, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x6e, - 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x22, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x05, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x70, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, - 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x22, 0x41, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x22, 0x66, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x80, 0x01, 0x0a, 0x1f, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x70, 0x63, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4f, + 0x72, 0x70, 0x68, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x6c, 0x6c, + 0x6f, 0x77, 0x4f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x22, 0x74, 0x0a, 0x20, 0x53, 0x75, 0x62, 0x6d, + 0x69, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 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, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, - 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x54, - 0x0a, 0x34, 0x47, 0x65, 0x74, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x46, - 0x72, 0x6f, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, - 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x48, 0x61, 0x73, 0x68, 0x22, 0xe0, 0x01, 0x0a, 0x35, 0x47, 0x65, 0x74, 0x56, 0x69, 0x72, 0x74, - 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, - 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, - 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x10, 0x61, 0x64, 0x64, 0x65, - 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x43, - 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x10, 0x61, 0x64, 0x64, 0x65, 0x64, - 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x8b, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x77, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x77, 0x48, 0x61, 0x73, 0x68, 0x12, 0x24, 0x0a, - 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x73, 0x12, 0x30, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x95, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, - 0x73, 0x68, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, - 0x2e, 0x52, 0x70, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, - 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x1d, 0x0a, - 0x1b, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x8c, 0x01, 0x0a, - 0x1c, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, - 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, - 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x1f, 0x0a, 0x1d, 0x47, - 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x9e, 0x03, 0x0a, - 0x1e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x67, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, - 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, - 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x70, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x54, - 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x74, 0x4d, - 0x65, 0x64, 0x69, 0x61, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x76, 0x69, 0x72, - 0x74, 0x75, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, - 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x50, - 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x70, - 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, - 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, + 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x37, + 0x0a, 0x35, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x64, 0x0a, 0x36, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, - 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x55, 0x0a, - 0x25, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, - 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, - 0x74, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x11, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x48, 0x61, 0x73, 0x68, 0x22, 0x54, 0x0a, 0x26, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x46, - 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, + 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xb3, 0x01, + 0x0a, 0x34, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, + 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, + 0x12, 0x41, 0x0a, 0x10, 0x61, 0x64, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x52, 0x10, 0x61, 0x64, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x73, 0x22, 0x62, 0x0a, 0x0a, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x40, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, + 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, + 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x22, 0x5b, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x16, + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x61, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x73, 0x22, 0x5e, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, + 0x73, 0x68, 0x12, 0x30, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x29, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x70, 0x63, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x41, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x75, 0x62, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x22, 0x66, 0x0a, 0x1c, 0x47, 0x65, 0x74, + 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x61, 0x73, + 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x67, 0x61, 0x73, + 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, + 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x22, 0x54, 0x0a, 0x34, 0x47, 0x65, 0x74, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0xe0, 0x01, 0x0a, 0x35, 0x47, 0x65, 0x74, 0x56, + 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x38, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, + 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x10, 0x61, + 0x64, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, + 0x65, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x10, 0x61, 0x64, + 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x27, 0x0a, 0x25, 0x4e, 0x6f, - 0x74, 0x69, 0x66, 0x79, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, - 0x6c, 0x69, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x54, 0x0a, 0x26, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x46, 0x69, 0x6e, - 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x8b, 0x01, 0x0a, 0x17, 0x47, + 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x77, 0x48, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x77, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x30, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x95, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, + 0x69, 0x72, 0x65, 0x2e, 0x52, 0x70, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, + 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x22, 0x1d, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x8c, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x1e, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, + 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x1f, + 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x67, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x9e, 0x03, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x67, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x70, 0x48, 0x61, 0x73, + 0x68, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x70, 0x48, 0x61, + 0x73, 0x68, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, + 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, + 0x75, 0x6c, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x70, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x64, 0x69, + 0x61, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x61, + 0x73, 0x74, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x13, + 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, + 0x68, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x76, 0x69, 0x72, 0x74, 0x75, + 0x61, 0x6c, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x2a, + 0x0a, 0x10, 0x70, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x61, + 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x75, 0x6e, 0x69, 0x6e, + 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x69, + 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, + 0x63, 0x6f, 0x72, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, + 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x22, 0x55, 0x0a, 0x25, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x66, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x54, 0x0a, 0x26, 0x52, 0x65, 0x73, 0x6f, 0x6c, + 0x76, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, + 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, + 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x27, 0x0a, + 0x25, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, + 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x54, 0x0a, 0x26, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x55, 0x0a, 0x23, + 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6e, 0x67, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, + 0x61, 0x73, 0x68, 0x22, 0x5b, 0x0a, 0x2b, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, + 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x66, + 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, + 0x22, 0x18, 0x0a, 0x16, 0x53, 0x68, 0x75, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x45, 0x0a, 0x17, 0x53, 0x68, + 0x75, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, + 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x22, 0x70, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x73, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x22, 0x61, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x40, 0x0a, 0x20, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x55, 0x74, 0x78, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x4f, 0x0a, 0x21, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x79, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x55, 0x0a, 0x23, 0x46, 0x69, 0x6e, - 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x2e, 0x0a, 0x12, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x76, 0x69, - 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, - 0x22, 0x5b, 0x0a, 0x2b, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, - 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, - 0x2c, 0x0a, 0x11, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6e, 0x61, - 0x6c, 0x69, 0x74, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x18, 0x0a, - 0x16, 0x53, 0x68, 0x75, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x45, 0x0a, 0x17, 0x53, 0x68, 0x75, 0x74, 0x44, - 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, - 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x70, - 0x0a, 0x18, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x20, - 0x0a, 0x0b, 0x69, 0x73, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x22, 0x61, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, - 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x22, 0x40, 0x0a, 0x20, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x74, 0x78, - 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x4f, 0x0a, 0x21, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, - 0x74, 0x78, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x95, 0x01, 0x0a, 0x1f, 0x55, 0x74, 0x78, 0x6f, 0x73, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x61, 0x64, - 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x61, 0x64, 0x64, - 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, - 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 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, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, - 0x2e, 0x52, 0x70, 0x63, 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, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x70, 0x63, 0x55, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x47, 0x0a, - 0x27, 0x53, 0x74, 0x6f, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x55, 0x74, - 0x78, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x56, 0x0a, 0x28, 0x53, 0x74, 0x6f, 0x70, 0x4e, 0x6f, - 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, - 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x41, - 0x0a, 0x21, 0x47, 0x65, 0x74, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, - 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, - 0x72, 0x69, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, - 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x22, 0x31, 0x0a, 0x2f, 0x47, 0x65, 0x74, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, - 0x53, 0x63, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x7c, 0x0a, 0x30, 0x47, 0x65, 0x74, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, - 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, - 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x6c, 0x75, 0x65, 0x53, - 0x63, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x6c, 0x75, 0x65, - 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, + 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x95, 0x01, 0x0a, 0x1f, 0x55, 0x74, + 0x78, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, + 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, + 0x61, 0x64, 0x64, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, + 0x72, 0x65, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x64, 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, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, + 0x69, 0x72, 0x65, 0x2e, 0x52, 0x70, 0x63, 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, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x70, 0x63, 0x55, 0x74, 0x78, 0x6f, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x22, 0x47, 0x0a, 0x27, 0x53, 0x74, 0x6f, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, + 0x67, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x56, 0x0a, 0x28, 0x53, 0x74, 0x6f, + 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x22, 0x3b, 0x0a, 0x39, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, + 0x72, 0x22, 0x41, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x55, 0x74, 0x78, 0x6f, + 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x65, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, + 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, + 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x22, 0x31, 0x0a, 0x2f, 0x47, 0x65, 0x74, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, + 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, + 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x7c, 0x0a, 0x30, 0x47, 0x65, 0x74, 0x56, 0x69, 0x72, + 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x6c, + 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, + 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3b, 0x0a, 0x39, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, + 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x68, 0x0a, 0x3a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x68, - 0x0a, 0x3a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, - 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x82, 0x01, 0x0a, 0x38, 0x56, 0x69, 0x72, - 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x46, 0x0a, 0x1e, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, - 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1e, 0x76, - 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, - 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x2c, 0x0a, - 0x2a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, - 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x59, 0x0a, 0x2b, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, - 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x55, 0x0a, 0x29, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x82, 0x01, 0x0a, 0x38, + 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x46, 0x0a, 0x1e, 0x76, 0x69, 0x72, 0x74, + 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x42, 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x1e, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x53, 0x63, 0x6f, 0x72, 0x65, + 0x22, 0x2c, 0x0a, 0x2a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, - 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x76, 0x69, - 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x31, 0x0a, - 0x2f, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, - 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x22, 0x5e, 0x0a, 0x30, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x59, + 0x0a, 0x2b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, + 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x55, 0x0a, 0x29, 0x56, 0x69, 0x72, + 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, + 0x6c, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, + 0x22, 0x31, 0x0a, 0x2f, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x4f, 0x76, 0x65, - 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, - 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x22, 0x30, 0x0a, 0x2e, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x55, 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x22, 0x38, 0x0a, 0x36, 0x53, 0x74, 0x6f, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x69, 0x6e, 0x67, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x55, - 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x65, 0x0a, 0x37, - 0x53, 0x74, 0x6f, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x75, + 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x22, 0x5e, 0x0a, 0x30, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x22, 0x23, 0x0a, 0x11, 0x42, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0x40, 0x0a, 0x12, 0x42, 0x61, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, - 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x25, 0x0a, 0x13, 0x55, 0x6e, - 0x62, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x70, 0x22, 0x42, 0x0a, 0x14, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x7c, - 0x0a, 0x16, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x32, 0x70, 0x49, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x32, 0x70, 0x49, 0x64, 0x12, 0x20, - 0x0a, 0x0b, 0x6d, 0x65, 0x6d, 0x70, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x70, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, - 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x6c, 0x0a, 0x2c, - 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, - 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, - 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0a, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0x93, 0x01, 0x0a, 0x2d, 0x45, - 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, - 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x16, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, - 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, - 0x63, 0x6f, 0x6e, 0x64, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, - 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, - 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x6f, 0x72, 0x22, 0x30, 0x0a, 0x2e, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, + 0x64, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x38, 0x0a, 0x36, 0x53, 0x74, 0x6f, 0x70, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x65, 0x0a, 0x37, 0x53, 0x74, 0x6f, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, + 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, + 0x53, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x23, 0x0a, 0x11, 0x42, 0x61, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0x40, 0x0a, 0x12, 0x42, + 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, + 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x25, 0x0a, + 0x13, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x70, 0x22, 0x42, 0x0a, 0x14, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x7c, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, + 0x32, 0x70, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x32, 0x70, 0x49, + 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x65, 0x6d, 0x70, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x70, 0x6f, 0x6f, 0x6c, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, + 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, + 0x6c, 0x0a, 0x2c, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0x93, 0x01, + 0x0a, 0x2d, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x36, 0x0a, 0x16, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, + 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x16, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, + 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, + 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, + 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto index 2ceeb7590..5e1876c52 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto @@ -273,6 +273,7 @@ message AddPeerResponseMessage{ // SubmitTransactionRequestMessage submits a transaction to the mempool message SubmitTransactionRequestMessage{ RpcTransaction transaction = 1; + bool allowOrphan = 2; } message SubmitTransactionResponseMessage{ diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_submit_transaction.go b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_submit_transaction.go index 44387edc9..ffaf6f213 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_submit_transaction.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_submit_transaction.go @@ -1,9 +1,10 @@ package protowire import ( + "math" + "github.com/kaspanet/kaspad/app/appmessage" "github.com/pkg/errors" - "math" ) func (x *KaspadMessage_SubmitTransactionRequest) toAppMessage() (appmessage.Message, error) { @@ -16,6 +17,7 @@ func (x *KaspadMessage_SubmitTransactionRequest) toAppMessage() (appmessage.Mess func (x *KaspadMessage_SubmitTransactionRequest) fromAppMessage(message *appmessage.SubmitTransactionRequestMessage) error { x.SubmitTransactionRequest = &SubmitTransactionRequestMessage{ Transaction: &RpcTransaction{}, + AllowOrphan: message.AllowOrphan, } x.SubmitTransactionRequest.Transaction.fromAppMessage(message.Transaction) return nil @@ -31,6 +33,7 @@ func (x *SubmitTransactionRequestMessage) toAppMessage() (appmessage.Message, er } return &appmessage.SubmitTransactionRequestMessage{ Transaction: rpcTransaction, + AllowOrphan: x.AllowOrphan, }, nil }