Compare commits

...

51 Commits

Author SHA1 Message Date
tal
691756be6e Updated changelog.txt for version v0.10.3 2021-06-06 15:55:57 +03:00
stasatdaglabs
63453ddf6b Update changelog.txt for v0.10.2.
(cherry picked from commit 91866dd61c)
2021-06-06 15:49:14 +03:00
stasatdaglabs
f70c1d5ae5 Update changelog for v0.10.1. 2021-06-06 15:48:20 +03:00
Ori Newman
5ef24ae37e Merge branch 'v0.10.3-dev' of github.com:kaspanet/kaspad into v0.10.3-dev 2021-05-31 18:24:19 +03:00
stasatdaglabs
83e631548f Implement NotifyVirtualDaaScoreChanged (#1737)
* Add notifyVirtualDaaScoreChanged to protowire.

* Add notifyVirtualDaaScoreChanged to the rest of kaspad.

* Add notifyVirtualDaaScoreChanged to the rest of kaspad.

* Test the DAA score notification in TestVirtualSelectedParentBlueScore.

* Rename TestVirtualSelectedParentBlueScore to TestVirtualSelectedParentBlueScoreAndVirtualDAAScore.
2021-05-31 18:13:44 +03:00
stasatdaglabs
0adfb2dfbb Implement NotifyVirtualDaaScoreChanged (#1737)
* Add notifyVirtualDaaScoreChanged to protowire.

* Add notifyVirtualDaaScoreChanged to the rest of kaspad.

* Add notifyVirtualDaaScoreChanged to the rest of kaspad.

* Test the DAA score notification in TestVirtualSelectedParentBlueScore.

* Rename TestVirtualSelectedParentBlueScore to TestVirtualSelectedParentBlueScoreAndVirtualDAAScore.
2021-05-31 18:11:08 +03:00
stasatdaglabs
4461f56ca3 Update to version 0.10.3 2021-05-18 13:54:33 +03:00
Ori Newman
a18f2f8802 Fix overflow when checking coinbase maturity and don't ban peers that send transactions with immature spend (#1722)
* Fix overflow when checking coinbase maturity and don't ban peers that send transactions with immature spend

* Fix tests

Co-authored-by: Svarog <feanorr@gmail.com>
2021-05-14 13:02:50 +03:00
Ori Newman
04dc1947ff Fix calcTxSequenceLockFromReferencedUTXOEntries for loop break condition (#1723)
Co-authored-by: Svarog <feanorr@gmail.com>
2021-05-14 12:49:53 +03:00
Ori Newman
36c56f73bf Add VirtualDaaScore to GetBlockDagInfo (#1719)
Co-authored-by: Svarog <feanorr@gmail.com>
2021-05-14 12:36:20 +03:00
Ori Newman
b76ca41109 serializeAddress should always serialize as IPv6, since it assumes the IP size is 16 bytes (#1720) 2021-05-13 17:42:59 +03:00
Ori Newman
50fd86e287 Fix getBlock and getBlocks RPC commands to return blocks and transactions properly (#1716)
* Fix getBlock RPC command to return transactions

* Fix getBlocks RPC command to return transactions and blocks

* Add GetBlockEvenIfHeaderOnly and use it for getBlock and getBlocks

* Implement GetBlockEvenIfHeaderOnly for fakeRelayInvsContext

* Use less nested code
2021-05-13 15:45:03 +03:00
stasatdaglabs
79cf0d64d0 Update to version 0.10.2 2021-05-09 14:33:47 +03:00
Ori Newman
b405ea50e5 Calculate virtual's acceptance data and multiset after importing a new pruning point (#1700) 2021-05-05 18:13:00 +03:00
Ori Newman
ccfe8a45dd Create v0.10.1 2021-05-05 17:09:30 +03:00
stasatdaglabs
9dd8136e4b Add v0.10.0 to the changelog. (#1692) 2021-04-26 15:01:02 +03:00
stasatdaglabs
eb1703b948 Fix the mempool-limits stability test (#1690)
* Add -v to the `go test` command.

* Generate a new keypair for mempool-limits.

* Set mempool-limits to time out only after 24 hours.
2021-04-25 16:27:47 +03:00
stasatdaglabs
a6da3251d0 Generalize stability-tests/docker/Dockerfile. (#1685) 2021-04-21 12:53:37 +03:00
stasatdaglabs
bf198948c4 Update the testnet version to testnet-5. (#1683) 2021-04-20 14:35:41 +03:00
Svarog
dfd8b3423d Implement new mechanism for updating UTXO Diffs (#1671)
* Use selectedParent instead of selectedTip for non-selectedTip blocks in restoreSingleBlockStatus

* Cache the selectedParent for re-use in a resolveSingleBlockStatus chain

* Implement and use reverseUTXOSet

* Reverse blocks in correct order

* Support resolveBlockStatus without separate stagingAreas for usage of testConsensus

* Handle the case where the tip of the resolved block is not the next selectedTip

* Unify isResolveTip

* Some minor fixes and cleanup

* Add full finality window re-org test to stability-slow

* rename: useSeparateStagingAreasPerBlock -> useSeparateStagingAreaPerBlock

* Better logs in resolveSingleBlockStatus

* A few retouches to reverseUTXODiffs

* TEMPORARY COMMIT: EXTRAT ALL DIFFFROMS TO SEPARATE METHODS

* TEMPORARY COMMIT: REMOVE DIFFICULTY CHECKS IN DEVNET

* Don't pre-allocate in utxo-algebra, since the numbers are not known ahead-of-time

* Add some logs to reverseUTXODiffs

* Revert "TEMPORARY COMMIT: REMOVE DIFFICULTY CHECKS IN DEVNET"

This reverts commit c0af9dc6ad.

* Revert "TEMPORARY COMMIT: EXTRAT ALL DIFFFROMS TO SEPARATE METHODS"

This reverts commit 4fcca1b48c.

* Remove redundant paranthesis

* Revise some logs messages

* Rename:oneBlockBeforeCurrentUTXOSet -> lastResolvedBlockUTXOSet

* Don't break if the block was resolved as invalid

* rename unverifiedBlocks to recentlyVerifiedBlcks in reverseUTXODiffs

* Add errors.New to the panic, for a stack trace

* Reverse the UTXODiffs after the main block has been commited

* Use the correct value for previousUTXODiff

* Add test for ReverseUTXODiff

* Fix some names and comments

* Update TestReverseUTXODiffs to use consensus.Config

* Fix comments mentioning 'oneBlockBeforeTip'
2021-04-20 10:26:55 +03:00
Svarog
28bfc0fb9c Move pow package from model to utils (#1681) 2021-04-19 15:35:36 +03:00
Elichai Turkel
83beae4463 Add consensus.Config as a wrapper for dagParams (#1680)
* Add a new consensus.Config wrapper to dagParams

* Update all tests to use consensus.Config
2021-04-19 09:07:34 +03:00
Elichai Turkel
a6ebe83198 Disable validate commitment unless explictly requested (#1679)
* Add a flag for sanity check pruning point utxo set and do the sanity check only if it's enabled

* add description to EnableSanityCheckPruningUTXOSet

* review fix

Co-authored-by: Svarog <feanorr@gmail.com>
2021-04-18 17:55:17 +03:00
Svarog
acdc59b565 Add version file to database (#1678)
* Add version file to database

* Remove redundant code

* Check for version before opening the database, create version file after

* Create version file before opening the database
2021-04-14 12:35:39 +03:00
stasatdaglabs
15811b0bcb Fix missing VerboseData in BlockAddedNotifications. (#1675)
Co-authored-by: Ori Newman <orinewman1@gmail.com>
Co-authored-by: Svarog <feanorr@gmail.com>
2021-04-13 11:35:06 +03:00
Svarog
a8a7e3dd9b Add windows to the CI + fix errors when testing on Windows (#1674)
* Add windows to the CI

* Cast syscall.Stdin into an integer

* DataDir -> AppDir in service_windows.go

* Rename mempool-limits package to something non-main

* Close database after re-assigining to it

* Up rpcTimout to 10 seconds
2021-04-12 14:53:34 +03:00
Svarog
3f193e9219 Use a separate folder for every network under ~/.kaspawallet (#1673) 2021-04-11 18:07:21 +03:00
stasatdaglabs
dfa24d8353 Implement a stability test for mempool limits (#1647)
* Copy some boilerplate from the other stability tests.

* Fix a copy+paste error in run.sh.

* Copy over some stability test boilerplate go code.

* Run kaspad in the background.

* Catch panics and initialize the RPC client.

* Mine enough blocks to fund filling up the mempool.

* Extract coinbase transactions out of the generated blocks.

* Tidy up a bit.

* Implement submitting transactions.

* Lower the amount of outputs in each transaction.

* Verify that the mempool size has the expected amount of transactions.

* Pregenerate enough funds before submitting the first transaction so that block creation doesn't interfere with the test.

* Empty mempool out by continuously adding blocks to the DAG.

* Handle orphan transactions when overfilling the mempool.

* Increase mempoolSizeLimit to 1m.

* Fix a comment.

* Fix a comment.

* Add mempool-limits to run-slow.sh.

* Rename generateTransactionsWithLotsOfOutputs to generateTransactionsWithMultipleOutputs.

* Rename generateCoinbaseTransaction to mineBlockAndGetCoinbaseTransaction.

* Make generateFundingCoinbaseTransactions return an object instead of store a global variable.

* Convert mempool-limits into a Go test.

* Convert panics to t.Fatalfs.

* Fix a comment.

* Increase mempoolSizeLimit to 1m.

* Run TestMempoolLimits only if RUN_STABILITY_TESTS is set.

* Move the run of mempool-limits in run-slow.sh.

* Add a comment above fundingCoinbaseTransactions.

* Make a couple of stylistic changes.

* Use transactionhelper.CoinbaseTransactionIndex instead of hardcoding 0.

* Make uninteresting errors print %+v instead of %s.

Co-authored-by: Svarog <feanorr@gmail.com>
2021-04-11 16:59:11 +03:00
Elichai Turkel
3c3ad1425d Make moving the pruning point faster (#1660)
* Add oldPruningPoint to pruningStore

* Make the pruning store work with utxo diff and return an iterator over pruning point utxoset

* Redesign pruning point utxo storage by creating a diff and modifying the old pruning utxo set

* Fix review comments

* Rename updatePruningPointUTXOSet
2021-04-11 11:17:13 +03:00
Svarog
9bb8123391 Don't include selectedParentHash in block verbose data if it's nil + Fix test vectors in rpc-stability (#1668)
* Fix submitBlockRequest in rpc-stability/commands.json

* Don't include selectedParentHash in block verbose data if it's nil (a.k.a. genesis)
2021-04-08 15:34:10 +03:00
Svarog
347dd8fc4b Support resolveBlockStatus without separate stagingAreas for usage of testConsensus (#1666) 2021-04-08 11:26:17 +03:00
Ori Newman
d2cccd2829 Add ECDSA support to the wallet (#1664)
* Add ECDSA support to the wallet

* Fix genkeypair

* Fix typo and rename var
2021-04-06 17:25:09 +03:00
Ori Newman
7186f83095 Add OpCheckMultiSigECDSA (#1663)
Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
2021-04-06 16:29:16 +03:00
Ori Newman
5c394c2951 Add PubKeyECDSATy (#1662) 2021-04-06 15:56:31 +03:00
Ori Newman
a786cdc15e Add ECDSA support (#1657)
* Add ECDSA support

* Add domain separation to ECDSA sighash

* Use InfallibleWrite instead of Write

* Rename funcs

* Fix wrong use if vm.sigCache

* Add TestCalculateSignatureHashECDSA

* Add consts

* Fix comment and test name

* Move consts to the top

* Fix comment
2021-04-06 14:27:18 +03:00
Ori Newman
6dd3d4a9e7 Add dump unencrypted data sub command to the wallet (#1661) 2021-04-06 12:29:13 +03:00
stasatdaglabs
73b36f12f0 Implement importing private keys into the wallet (#1655)
* Implement importing private keys into the wallet.

* Fix bad --import default.

* Fix typo in --import annotation.

* Make go lint happy.

* Make go lint happier.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-04-05 18:10:33 +03:00
stasatdaglabs
a795a9e619 Add a size limit to the address manager (#1652)
* Remove a random address from the address manager if it's full.

* Implement TestOverfillAddressManager.

* Add connectionFailedCount to addresses.

* Mark connection failures.

* Mark connection successes.

* Implement removing by most connection failures.

* Expand TestOverfillAddressManager.

* Add comments.

* Use a better method for finding the address with the greatest connectionFailedCount.

* Fix a comment.

* Compare addresses by IP in TestOverfillAddressManager.

* Add a comment for updateNotBanned.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-04-05 17:56:13 +03:00
Ori Newman
0be1bba408 Fix TestAddresses (#1656) 2021-04-05 16:24:22 +03:00
Ori Newman
6afc06ce58 Replace p2pkh with p2pk (#1650)
* Replace p2pkh with p2pk

* Fix tests

* Fix comments and variable names

* Add README.md for genkeypair

* Rename pubkey->publicKey

* Rename p2pkh to p2pk

* Use util.PublicKeySize where needed

* Remove redundant pointer

* Fix comment

* Rename pubKey->publicKey
2021-04-05 14:35:34 +03:00
stasatdaglabs
d01a213f3d Add a show-address subcommand to kaspawallet (#1653)
* Add a show-address subcommand to kaspawallet.

* Update the description of the key-file command line parameter.
2021-04-05 14:22:03 +03:00
stasatdaglabs
7ad8ce521c Implement reconnection logic within the RPC client (#1643)
* Add a reconnect mechanism to RPCClient.

* Fix Reconnect().

* Connect the internal reconnection logic to the miner reconnection logic.

* Rename shouldReconnect to isClosed.

* Move safe reconnection logic from the miner to rpcclient.

* Remove sleep from HandleSubmitBlock.

* Properly handle client errors and only disconnect if we're already connected.

* Make go lint happy.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-04-05 13:57:28 +03:00
Ori Newman
86ba80a091 Improve wallet functionality (#1636)
* Add basic wallet library

* Add CLI

* Add multisig support

* Add persistence to wallet

* Add tests

* go mod tidy

* Fix lint errors

* Fix wallet send command

* Always use the password as byte slice

* Remove redundant empty string

* Use different salt per private key

* Don't sign a signed transaction

* Add comment

* Remove old wallet

* Change directory permissions

* Use NormalizeRPCServerAddress

* Fix compilation errors
2021-03-31 15:58:22 +03:00
Ori Newman
088e2114c2 Disconnect from RPC client after finishing the simple sync test (#1641) 2021-03-31 13:44:42 +03:00
stasatdaglabs
2854d91688 Add missing call to broadcastTransactionsAfterBlockAdded (#1639)
* Add missing call to broadcastTransactionsAfterBlockAdded.

* Fix a comment.

* Fix a comment some more.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-03-31 10:28:02 +03:00
Ori Newman
af10b59181 Use go-secp256k1 v0.0.5 (#1640) 2021-03-30 18:01:56 +03:00
stasatdaglabs
c5b0394bbc In RPC, use RPCTransactions and RPCBlocks instead of TransactionMessages and BlockMessages (#1609)
* Replace BlockMessage with RpcBlock in rpc.proto.

* Convert everything in kaspad to use RPCBlocks and fix tests.

* Fix compilation errors in stability tests and the miner.

* Update TransactionVerboseData in rpc.proto.

* Update TransactionVerboseData in the rest of kaspad.

* Make golint happy.

* Include RpcTransactionVerboseData in RpcTransaction instead of the other way around.

* Regenerate rpc.pb.go after merge.

* Update appmessage types.

* Update appmessage request and response types.

* Reimplement conversion functions between appmessage.RPCTransaction and protowire.RpcTransaction.

* Extract RpcBlockHeader toAppMessage/fromAppMessage out of RpcBlock.

* Fix compilation errors in getBlock, getBlocks, and submitBlock.

* Fix compilation errors in getMempoolEntry.

* Fix compilation errors in notifyBlockAdded.

* Update verbosedata.go.

* Fix compilation errors in getBlock and getBlocks.

* Fix compilation errors in getBlocks tests.

* Fix conversions between getBlocks message types.

* Fix integration tests.

* Fix a comment.

* Add selectedParent to the verbose block response.
2021-03-30 17:43:02 +03:00
Ori Newman
9266d179a9 Add a test with two signed inputs (#1628)
* Add TestSigningTwoInputs

* Rename fundingBlockHash to block1Hash

* Fix error message

Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
2021-03-30 16:54:54 +03:00
Ori Newman
321792778e Add mass limit to mempool (#1627)
* Add mass limit to mempool

* Pass only params instead of multiple configuration options

* Remove acceptNonStd from mempool constructor

* Remove acceptNonStd from mempool constructor

* Fix test compilation
2021-03-30 15:37:56 +03:00
talelbaz
70f3fa9893 Update miningManager test (#1593)
* [NOD-1429] add mining manager unit tests

* [NOD-1429] Add additional test

* found a bug, so stopped working on this test until the bug will be fix.

* Update miningmanager_test.go test.

* Delete payloadHash field - not used anymore in the current version.

* Change the condition for comparing slices instead of pointers.

* Fix due to review notes - change names, use testutils.CreateTransaction function and adds comments.

* Changes after fetch&merge to v0.10.0-dev

* Create a new function createChildTxWhenParentTxWasAddedByConsensus and add a comment

* Add an argument to create_transaction function and fix review notes

* Optimization

* Change to blockID(instead of the all transaction) in the error messages and fix review notes

* Change to blockID(instead of the all transaction) in the error messages and fix review notes

* Change format of error messages.

* Change name ofa variable

* Use go:embed to embed sample-kaspad.conf (only on go1.16)

* Revert "Use go:embed to embed sample-kaspad.conf (only on go1.16)"

This reverts commit bd28052b92.

Co-authored-by: karim1king <karimkaspersky@yahoo.com>
Co-authored-by: tal <tal@daglabs.com>
Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-03-30 13:52:40 +03:00
Svarog
4e18031483 Resolve each block status in it's own staging area (#1634) 2021-03-30 11:04:43 +03:00
233 changed files with 11766 additions and 5718 deletions

View File

@@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-16.04, macos-10.15 ]
os: [ ubuntu-16.04, macos-10.15, windows-2019 ]
name: Testing on on ${{ matrix.os }}
steps:

View File

@@ -85,12 +85,6 @@ func (app *kaspadApp) main(startedChan chan<- struct{}) error {
profiling.Start(app.cfg.Profile, log)
}
// Perform upgrades to kaspad as new versions require it.
if err := doUpgrades(); err != nil {
log.Error(err)
return err
}
// Return now if an interrupt signal was triggered.
if signal.InterruptRequested(interrupt) {
return nil
@@ -163,12 +157,6 @@ func (app *kaspadApp) main(startedChan chan<- struct{}) error {
return nil
}
// doUpgrades performs upgrades to kaspad as new versions require it.
// currently it's a placeholder we got from kaspad upstream, that does nothing
func doUpgrades() error {
return nil
}
// dbPath returns the path to the block database given a database type.
func databasePath(cfg *config.Config) string {
return filepath.Join(cfg.AppDir, "data")
@@ -181,6 +169,17 @@ func removeDatabase(cfg *config.Config) error {
func openDB(cfg *config.Config) (database.Database, error) {
dbPath := databasePath(cfg)
err := checkDatabaseVersion(dbPath)
if err != nil {
return nil, err
}
log.Infof("Loading database from '%s'", dbPath)
return ldb.NewLevelDB(dbPath, leveldbCacheSizeMiB)
db, err := ldb.NewLevelDB(dbPath, leveldbCacheSizeMiB)
if err != nil {
return nil, err
}
return db, nil
}

View File

@@ -3,6 +3,7 @@ package appmessage
import (
"encoding/hex"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
@@ -294,3 +295,70 @@ func DomainOutpointAndUTXOEntryPairsToOutpointAndUTXOEntryPairs(
}
return domainOutpointAndUTXOEntryPairs
}
// DomainBlockToRPCBlock converts DomainBlocks to RPCBlocks
func DomainBlockToRPCBlock(block *externalapi.DomainBlock) *RPCBlock {
header := &RPCBlockHeader{
Version: uint32(block.Header.Version()),
ParentHashes: hashes.ToStrings(block.Header.ParentHashes()),
HashMerkleRoot: block.Header.HashMerkleRoot().String(),
AcceptedIDMerkleRoot: block.Header.AcceptedIDMerkleRoot().String(),
UTXOCommitment: block.Header.UTXOCommitment().String(),
Timestamp: block.Header.TimeInMilliseconds(),
Bits: block.Header.Bits(),
Nonce: block.Header.Nonce(),
}
transactions := make([]*RPCTransaction, len(block.Transactions))
for i, transaction := range block.Transactions {
transactions[i] = DomainTransactionToRPCTransaction(transaction)
}
return &RPCBlock{
Header: header,
Transactions: transactions,
}
}
// RPCBlockToDomainBlock converts `block` into a DomainBlock
func RPCBlockToDomainBlock(block *RPCBlock) (*externalapi.DomainBlock, error) {
parentHashes := make([]*externalapi.DomainHash, len(block.Header.ParentHashes))
for i, parentHash := range block.Header.ParentHashes {
domainParentHashes, err := externalapi.NewDomainHashFromString(parentHash)
if err != nil {
return nil, err
}
parentHashes[i] = domainParentHashes
}
hashMerkleRoot, err := externalapi.NewDomainHashFromString(block.Header.HashMerkleRoot)
if err != nil {
return nil, err
}
acceptedIDMerkleRoot, err := externalapi.NewDomainHashFromString(block.Header.AcceptedIDMerkleRoot)
if err != nil {
return nil, err
}
utxoCommitment, err := externalapi.NewDomainHashFromString(block.Header.UTXOCommitment)
if err != nil {
return nil, err
}
header := blockheader.NewImmutableBlockHeader(
uint16(block.Header.Version),
parentHashes,
hashMerkleRoot,
acceptedIDMerkleRoot,
utxoCommitment,
block.Header.Timestamp,
block.Header.Bits,
block.Header.Nonce)
transactions := make([]*externalapi.DomainTransaction, len(block.Transactions))
for i, transaction := range block.Transactions {
domainTransaction, err := RPCTransactionToDomainTransaction(transaction)
if err != nil {
return nil, err
}
transactions[i] = domainTransaction
}
return &externalapi.DomainBlock{
Header: header,
Transactions: transactions,
}, nil
}

View File

@@ -137,6 +137,9 @@ const (
CmdPruningPointUTXOSetOverrideNotificationMessage
CmdStopNotifyingPruningPointUTXOSetOverrideRequestMessage
CmdStopNotifyingPruningPointUTXOSetOverrideResponseMessage
CmdNotifyVirtualDaaScoreChangedRequestMessage
CmdNotifyVirtualDaaScoreChangedResponseMessage
CmdVirtualDaaScoreChangedNotificationMessage
)
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
@@ -248,6 +251,9 @@ var RPCMessageCommandToString = map[MessageCommand]string{
CmdPruningPointUTXOSetOverrideNotificationMessage: "PruningPointUTXOSetOverrideNotification",
CmdStopNotifyingPruningPointUTXOSetOverrideRequestMessage: "StopNotifyingPruningPointUTXOSetOverrideRequest",
CmdStopNotifyingPruningPointUTXOSetOverrideResponseMessage: "StopNotifyingPruningPointUTXOSetOverrideResponse",
CmdNotifyVirtualDaaScoreChangedRequestMessage: "NotifyVirtualDaaScoreChangedRequest",
CmdNotifyVirtualDaaScoreChangedResponseMessage: "NotifyVirtualDaaScoreChangedResponse",
CmdVirtualDaaScoreChangedNotificationMessage: "VirtualDaaScoreChangedNotification",
}
// Message is an interface that describes a kaspa message. A type that

View File

@@ -25,7 +25,7 @@ func NewGetBlockRequestMessage(hash string, includeTransactionVerboseData bool)
// its respective RPC message
type GetBlockResponseMessage struct {
baseMessage
BlockVerboseData *BlockVerboseData
Block *RPCBlock
Error *RPCError
}
@@ -39,70 +39,3 @@ func (msg *GetBlockResponseMessage) Command() MessageCommand {
func NewGetBlockResponseMessage() *GetBlockResponseMessage {
return &GetBlockResponseMessage{}
}
// BlockVerboseData holds verbose data about a block
type BlockVerboseData struct {
Hash string
Version uint16
VersionHex string
HashMerkleRoot string
AcceptedIDMerkleRoot string
UTXOCommitment string
TxIDs []string
TransactionVerboseData []*TransactionVerboseData
Time int64
Nonce uint64
Bits string
Difficulty float64
ParentHashes []string
ChildrenHashes []string
SelectedParentHash string
BlueScore uint64
IsHeaderOnly bool
}
// TransactionVerboseData holds verbose data about a transaction
type TransactionVerboseData struct {
TxID string
Hash string
Size uint64
Version uint16
LockTime uint64
SubnetworkID string
Gas uint64
Payload string
TransactionVerboseInputs []*TransactionVerboseInput
TransactionVerboseOutputs []*TransactionVerboseOutput
BlockHash string
Time uint64
BlockTime uint64
}
// TransactionVerboseInput holds data about a transaction input
type TransactionVerboseInput struct {
TxID string
OutputIndex uint32
ScriptSig *ScriptSig
Sequence uint64
}
// ScriptSig holds data about a script signature
type ScriptSig struct {
Asm string
Hex string
}
// TransactionVerboseOutput holds data about a transaction output
type TransactionVerboseOutput struct {
Value uint64
Index uint32
ScriptPubKey *ScriptPubKeyResult
}
// ScriptPubKeyResult holds data about a script public key
type ScriptPubKeyResult struct {
Hex string
Type string
Address string
Version uint16
}

View File

@@ -28,6 +28,7 @@ type GetBlockDAGInfoResponseMessage struct {
Difficulty float64
PastMedianTime int64
PruningPointHash string
VirtualDAAScore uint64
Error *RPCError
}

View File

@@ -23,7 +23,7 @@ func NewGetBlockTemplateRequestMessage(payAddress string) *GetBlockTemplateReque
// its respective RPC message
type GetBlockTemplateResponseMessage struct {
baseMessage
MsgBlock *MsgBlock
Block *RPCBlock
IsSynced bool
Error *RPCError
@@ -35,9 +35,9 @@ func (msg *GetBlockTemplateResponseMessage) Command() MessageCommand {
}
// NewGetBlockTemplateResponseMessage returns a instance of the message
func NewGetBlockTemplateResponseMessage(msgBlock *MsgBlock, isSynced bool) *GetBlockTemplateResponseMessage {
func NewGetBlockTemplateResponseMessage(block *RPCBlock, isSynced bool) *GetBlockTemplateResponseMessage {
return &GetBlockTemplateResponseMessage{
MsgBlock: msgBlock,
Block: block,
IsSynced: isSynced,
}
}

View File

@@ -5,7 +5,7 @@ package appmessage
type GetBlocksRequestMessage struct {
baseMessage
LowHash string
IncludeBlockVerboseData bool
IncludeBlocks bool
IncludeTransactionVerboseData bool
}
@@ -15,11 +15,11 @@ func (msg *GetBlocksRequestMessage) Command() MessageCommand {
}
// NewGetBlocksRequestMessage returns a instance of the message
func NewGetBlocksRequestMessage(lowHash string, includeBlockVerboseData bool,
func NewGetBlocksRequestMessage(lowHash string, includeBlocks bool,
includeTransactionVerboseData bool) *GetBlocksRequestMessage {
return &GetBlocksRequestMessage{
LowHash: lowHash,
IncludeBlockVerboseData: includeBlockVerboseData,
IncludeBlocks: includeBlocks,
IncludeTransactionVerboseData: includeTransactionVerboseData,
}
}
@@ -28,8 +28,8 @@ func NewGetBlocksRequestMessage(lowHash string, includeBlockVerboseData bool,
// its respective RPC message
type GetBlocksResponseMessage struct {
baseMessage
BlockHashes []string
BlockVerboseData []*BlockVerboseData
BlockHashes []string
Blocks []*RPCBlock
Error *RPCError
}
@@ -40,11 +40,6 @@ func (msg *GetBlocksResponseMessage) Command() MessageCommand {
}
// NewGetBlocksResponseMessage returns a instance of the message
func NewGetBlocksResponseMessage(blockHashes []string, blockHexes []string,
blockVerboseData []*BlockVerboseData) *GetBlocksResponseMessage {
return &GetBlocksResponseMessage{
BlockHashes: blockHashes,
BlockVerboseData: blockVerboseData,
}
func NewGetBlocksResponseMessage() *GetBlocksResponseMessage {
return &GetBlocksResponseMessage{}
}

View File

@@ -11,8 +11,8 @@ func (msg *GetInfoRequestMessage) Command() MessageCommand {
return CmdGetInfoRequestMessage
}
// NewGeInfoRequestMessage returns a instance of the message
func NewGeInfoRequestMessage() *GetInfoRequestMessage {
// NewGetInfoRequestMessage returns a instance of the message
func NewGetInfoRequestMessage() *GetInfoRequestMessage {
return &GetInfoRequestMessage{}
}

View File

@@ -28,8 +28,8 @@ type GetMempoolEntryResponseMessage struct {
// MempoolEntry represents a transaction in the mempool.
type MempoolEntry struct {
Fee uint64
TransactionVerboseData *TransactionVerboseData
Fee uint64
Transaction *RPCTransaction
}
// Command returns the protocol command string for the message
@@ -38,11 +38,11 @@ func (msg *GetMempoolEntryResponseMessage) Command() MessageCommand {
}
// NewGetMempoolEntryResponseMessage returns a instance of the message
func NewGetMempoolEntryResponseMessage(fee uint64, transactionVerboseData *TransactionVerboseData) *GetMempoolEntryResponseMessage {
func NewGetMempoolEntryResponseMessage(fee uint64, transaction *RPCTransaction) *GetMempoolEntryResponseMessage {
return &GetMempoolEntryResponseMessage{
Entry: &MempoolEntry{
Fee: fee,
TransactionVerboseData: transactionVerboseData,
Fee: fee,
Transaction: transaction,
},
}
}

View File

@@ -37,8 +37,7 @@ func NewNotifyBlockAddedResponseMessage() *NotifyBlockAddedResponseMessage {
// its respective RPC message
type BlockAddedNotificationMessage struct {
baseMessage
Block *MsgBlock
BlockVerboseData *BlockVerboseData
Block *RPCBlock
}
// Command returns the protocol command string for the message
@@ -47,9 +46,8 @@ func (msg *BlockAddedNotificationMessage) Command() MessageCommand {
}
// NewBlockAddedNotificationMessage returns a instance of the message
func NewBlockAddedNotificationMessage(block *MsgBlock, blockVerboseData *BlockVerboseData) *BlockAddedNotificationMessage {
func NewBlockAddedNotificationMessage(block *RPCBlock) *BlockAddedNotificationMessage {
return &BlockAddedNotificationMessage{
Block: block,
BlockVerboseData: blockVerboseData,
Block: block,
}
}

View File

@@ -0,0 +1,55 @@
package appmessage
// NotifyVirtualDaaScoreChangedRequestMessage is an appmessage corresponding to
// its respective RPC message
type NotifyVirtualDaaScoreChangedRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *NotifyVirtualDaaScoreChangedRequestMessage) Command() MessageCommand {
return CmdNotifyVirtualDaaScoreChangedRequestMessage
}
// NewNotifyVirtualDaaScoreChangedRequestMessage returns a instance of the message
func NewNotifyVirtualDaaScoreChangedRequestMessage() *NotifyVirtualDaaScoreChangedRequestMessage {
return &NotifyVirtualDaaScoreChangedRequestMessage{}
}
// NotifyVirtualDaaScoreChangedResponseMessage is an appmessage corresponding to
// its respective RPC message
type NotifyVirtualDaaScoreChangedResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *NotifyVirtualDaaScoreChangedResponseMessage) Command() MessageCommand {
return CmdNotifyVirtualDaaScoreChangedResponseMessage
}
// NewNotifyVirtualDaaScoreChangedResponseMessage returns a instance of the message
func NewNotifyVirtualDaaScoreChangedResponseMessage() *NotifyVirtualDaaScoreChangedResponseMessage {
return &NotifyVirtualDaaScoreChangedResponseMessage{}
}
// VirtualDaaScoreChangedNotificationMessage is an appmessage corresponding to
// its respective RPC message
type VirtualDaaScoreChangedNotificationMessage struct {
baseMessage
VirtualDaaScore uint64
}
// Command returns the protocol command string for the message
func (msg *VirtualDaaScoreChangedNotificationMessage) Command() MessageCommand {
return CmdVirtualDaaScoreChangedNotificationMessage
}
// NewVirtualDaaScoreChangedNotificationMessage returns a instance of the message
func NewVirtualDaaScoreChangedNotificationMessage(
virtualDaaScore uint64) *VirtualDaaScoreChangedNotificationMessage {
return &VirtualDaaScoreChangedNotificationMessage{
VirtualDaaScore: virtualDaaScore,
}
}

View File

@@ -4,7 +4,7 @@ package appmessage
// its respective RPC message
type SubmitBlockRequestMessage struct {
baseMessage
Block *MsgBlock
Block *RPCBlock
}
// Command returns the protocol command string for the message
@@ -13,7 +13,7 @@ func (msg *SubmitBlockRequestMessage) Command() MessageCommand {
}
// NewSubmitBlockRequestMessage returns a instance of the message
func NewSubmitBlockRequestMessage(block *MsgBlock) *SubmitBlockRequestMessage {
func NewSubmitBlockRequestMessage(block *RPCBlock) *SubmitBlockRequestMessage {
return &SubmitBlockRequestMessage{
Block: block,
}
@@ -57,3 +57,35 @@ func (msg *SubmitBlockResponseMessage) Command() MessageCommand {
func NewSubmitBlockResponseMessage() *SubmitBlockResponseMessage {
return &SubmitBlockResponseMessage{}
}
// RPCBlock is a kaspad block representation meant to be
// used over RPC
type RPCBlock struct {
Header *RPCBlockHeader
Transactions []*RPCTransaction
VerboseData *RPCBlockVerboseData
}
// RPCBlockHeader is a kaspad block header representation meant to be
// used over RPC
type RPCBlockHeader struct {
Version uint32
ParentHashes []string
HashMerkleRoot string
AcceptedIDMerkleRoot string
UTXOCommitment string
Timestamp int64
Bits uint32
Nonce uint64
}
// RPCBlockVerboseData holds verbose data about a block
type RPCBlockVerboseData struct {
Hash string
Difficulty float64
SelectedParentHash string
TransactionIDs []string
IsHeaderOnly bool
BlueScore uint64
ChildrenHashes []string
}

View File

@@ -50,6 +50,7 @@ type RPCTransaction struct {
SubnetworkID string
Gas uint64
Payload string
VerboseData *RPCTransactionVerboseData
}
// RPCTransactionInput is a kaspad transaction input representation
@@ -58,6 +59,7 @@ type RPCTransactionInput struct {
PreviousOutpoint *RPCOutpoint
SignatureScript string
Sequence uint64
VerboseData *RPCTransactionInputVerboseData
}
// RPCScriptPublicKey is a kaspad ScriptPublicKey representation
@@ -71,6 +73,7 @@ type RPCScriptPublicKey struct {
type RPCTransactionOutput struct {
Amount uint64
ScriptPublicKey *RPCScriptPublicKey
VerboseData *RPCTransactionOutputVerboseData
}
// RPCOutpoint is a kaspad outpoint representation meant to be used
@@ -88,3 +91,22 @@ type RPCUTXOEntry struct {
BlockDAAScore uint64
IsCoinbase bool
}
// RPCTransactionVerboseData holds verbose data about a transaction
type RPCTransactionVerboseData struct {
TransactionID string
Hash string
Size uint64
BlockHash string
BlockTime uint64
}
// RPCTransactionInputVerboseData holds data about a transaction input
type RPCTransactionInputVerboseData struct {
}
// RPCTransactionOutputVerboseData holds data about a transaction output
type RPCTransactionOutputVerboseData struct {
ScriptPublicKeyType string
ScriptPublicKeyAddress string
}

View File

@@ -4,23 +4,19 @@ import (
"fmt"
"sync/atomic"
"github.com/kaspanet/kaspad/domain/utxoindex"
infrastructuredatabase "github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/kaspanet/kaspad/domain"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/protocol"
"github.com/kaspanet/kaspad/app/rpc"
"github.com/kaspanet/kaspad/domain"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/utxoindex"
"github.com/kaspanet/kaspad/infrastructure/config"
infrastructuredatabase "github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
"github.com/kaspanet/kaspad/infrastructure/network/dnsseed"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
"github.com/kaspanet/kaspad/util/panics"
)
@@ -82,7 +78,13 @@ func (a *ComponentManager) Stop() {
func NewComponentManager(cfg *config.Config, db infrastructuredatabase.Database, interrupt chan<- struct{}) (
*ComponentManager, error) {
domain, err := domain.New(cfg.ActiveNetParams, db, cfg.IsArchivalNode)
consensusConfig := consensus.Config{
Params: *cfg.ActiveNetParams,
IsArchival: cfg.IsArchivalNode,
EnableSanityCheckPruningUTXOSet: cfg.EnableSanityCheckPruningUTXOSet,
}
domain, err := domain.New(&consensusConfig, db)
if err != nil {
return nil, err
}

57
app/db_version.go Normal file
View File

@@ -0,0 +1,57 @@
package app
import (
"os"
"path"
"strconv"
"github.com/pkg/errors"
)
const currentDatabaseVersion = 1
func checkDatabaseVersion(dbPath string) (err error) {
versionFileName := versionFilePath(dbPath)
versionBytes, err := os.ReadFile(versionFileName)
if err != nil {
if os.IsNotExist(err) { // If version file doesn't exist, we assume that the database is new
return createDatabaseVersionFile(dbPath, versionFileName)
}
return err
}
databaseVersion, err := strconv.Atoi(string(versionBytes))
if err != nil {
return err
}
if databaseVersion != currentDatabaseVersion {
// TODO: Once there's more then one database version, it might make sense to add upgrade logic at this point
return errors.Errorf("Invalid database version %d. Expected version: %d", databaseVersion, currentDatabaseVersion)
}
return nil
}
func createDatabaseVersionFile(dbPath string, versionFileName string) error {
err := os.MkdirAll(dbPath, 0700)
if err != nil {
return err
}
versionFile, err := os.Create(versionFileName)
if err != nil {
return nil
}
defer versionFile.Close()
versionString := strconv.Itoa(currentDatabaseVersion)
_, err = versionFile.Write([]byte(versionString))
return err
}
func versionFilePath(dbPath string) string {
dbVersionFileName := path.Join(dbPath, "version")
return dbVersionFileName
}

View File

@@ -37,12 +37,14 @@ func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock,
newBlockInsertionResults = append(newBlockInsertionResults, unorphaningResult.blockInsertionResult)
}
allAcceptedTransactions := make([]*externalapi.DomainTransaction, 0)
for i, newBlock := range newBlocks {
log.Debugf("OnNewBlock: passing block %s transactions to mining manager", hash)
_, err = f.Domain().MiningManager().HandleNewBlockTransactions(newBlock.Transactions)
acceptedTransactions, err := f.Domain().MiningManager().HandleNewBlockTransactions(newBlock.Transactions)
if err != nil {
return err
}
allAcceptedTransactions = append(allAcceptedTransactions, acceptedTransactions...)
if f.onBlockAddedToDAGHandler != nil {
log.Debugf("OnNewBlock: calling f.onBlockAddedToDAGHandler for block %s", hash)
@@ -54,7 +56,7 @@ func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock,
}
}
return nil
return f.broadcastTransactionsAfterBlockAdded(newBlocks, allAcceptedTransactions)
}
// OnPruningPointUTXOSetOverride calls the handler function whenever the UTXO set
@@ -67,9 +69,9 @@ func (f *FlowContext) OnPruningPointUTXOSetOverride() error {
}
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(
block *externalapi.DomainBlock, transactionsAcceptedToMempool []*externalapi.DomainTransaction) error {
addedBlocks []*externalapi.DomainBlock, transactionsAcceptedToMempool []*externalapi.DomainTransaction) error {
f.updateTransactionsToRebroadcast(block)
f.updateTransactionsToRebroadcast(addedBlocks)
// Don't relay transactions when in IBD.
if f.IsIBDRunning() {

View File

@@ -25,14 +25,17 @@ func (f *FlowContext) AddTransaction(tx *externalapi.DomainTransaction) error {
return f.Broadcast(inv)
}
func (f *FlowContext) updateTransactionsToRebroadcast(block *externalapi.DomainBlock) {
func (f *FlowContext) updateTransactionsToRebroadcast(addedBlocks []*externalapi.DomainBlock) {
f.transactionsToRebroadcastLock.Lock()
defer f.transactionsToRebroadcastLock.Unlock()
// Note: if the block is red, its transactions won't be rebroadcasted
// anymore, although they are not included in the UTXO set.
// This is probably ok, since red blocks are quite rare.
for _, tx := range block.Transactions {
delete(f.transactionsToRebroadcast, *consensushashing.TransactionID(tx))
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))
}
}
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
"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/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
@@ -129,7 +130,11 @@ type fakeRelayInvsContext struct {
rwLock sync.RWMutex
}
func (f *fakeRelayInvsContext) GetBlockChildren(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
func (f *fakeRelayInvsContext) GetBlockEvenIfHeaderOnly(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) {
panic("implement me")
}
func (f *fakeRelayInvsContext) GetBlockRelations(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, *externalapi.DomainHash, []*externalapi.DomainHash, error) {
panic(errors.Errorf("called unimplemented function from test '%s'", f.testName))
}
@@ -1496,7 +1501,7 @@ func TestHandleRelayInvs(t *testing.T) {
},
}
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
for _, test := range tests {
// This is done to avoid race condition
@@ -1511,7 +1516,7 @@ func TestHandleRelayInvs(t *testing.T) {
errChan := make(chan error)
context := &fakeRelayInvsContext{
testName: test.name,
params: params,
params: &consensusConfig.Params,
finishedIBD: make(chan struct{}),
trySetIBDRunningResponse: true,

View File

@@ -1,15 +1,16 @@
package testing
import (
"testing"
"time"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/protocol/flows/addressexchange"
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"testing"
"time"
)
type fakeReceiveAddressesContext struct{}
@@ -19,7 +20,7 @@ func (f fakeReceiveAddressesContext) AddressManager() *addressmanager.AddressMan
}
func TestReceiveAddressesErrors(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
incomingRoute := router.NewRoute()
outgoingRoute := router.NewRoute()
peer := peerpkg.New(nil)

View File

@@ -64,17 +64,22 @@ func (m *Manager) NotifyBlockAddedToDAG(block *externalapi.DomainBlock, blockIns
return err
}
err = m.notifyVirtualDaaScoreChanged()
if err != nil {
return err
}
err = m.notifyVirtualSelectedParentChainChanged(blockInsertionResult)
if err != nil {
return err
}
msgBlock := appmessage.DomainBlockToMsgBlock(block)
blockVerboseData, err := m.context.BuildBlockVerboseData(block.Header, block, false)
rpcBlock := appmessage.DomainBlockToRPCBlock(block)
err = m.context.PopulateBlockWithVerboseData(rpcBlock, block.Header, block, false)
if err != nil {
return err
}
blockAddedNotification := appmessage.NewBlockAddedNotificationMessage(msgBlock, blockVerboseData)
blockAddedNotification := appmessage.NewBlockAddedNotificationMessage(rpcBlock)
return m.context.NotificationManager.NotifyBlockAdded(blockAddedNotification)
}
@@ -153,6 +158,19 @@ func (m *Manager) notifyVirtualSelectedParentBlueScoreChanged() error {
return m.context.NotificationManager.NotifyVirtualSelectedParentBlueScoreChanged(notification)
}
func (m *Manager) notifyVirtualDaaScoreChanged() error {
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyVirtualDaaScoreChanged")
defer onEnd()
virtualInfo, err := m.context.Domain.Consensus().GetVirtualInfo()
if err != nil {
return err
}
notification := appmessage.NewVirtualDaaScoreChangedNotificationMessage(virtualInfo.DAAScore)
return m.context.NotificationManager.NotifyVirtualDaaScoreChanged(notification)
}
func (m *Manager) notifyVirtualSelectedParentChainChanged(blockInsertionResult *externalapi.BlockInsertionResult) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyVirtualSelectedParentChainChanged")
defer onEnd()

View File

@@ -44,6 +44,7 @@ var handlers = map[appmessage.MessageCommand]handler{
appmessage.CmdGetInfoRequestMessage: rpchandlers.HandleGetInfo,
appmessage.CmdNotifyPruningPointUTXOSetOverrideRequestMessage: rpchandlers.HandleNotifyPruningPointUTXOSetOverrideRequest,
appmessage.CmdStopNotifyingPruningPointUTXOSetOverrideRequestMessage: rpchandlers.HandleStopNotifyingPruningPointUTXOSetOverrideRequest,
appmessage.CmdNotifyVirtualDaaScoreChangedRequestMessage: rpchandlers.HandleNotifyVirtualDaaScoreChanged,
}
func (m *Manager) routerInitializer(router *router.Router, netConnection *netadapter.NetConnection) {

View File

@@ -30,6 +30,7 @@ type NotificationListener struct {
propagateFinalityConflictResolvedNotifications bool
propagateUTXOsChangedNotifications bool
propagateVirtualSelectedParentBlueScoreChangedNotifications bool
propagateVirtualDaaScoreChangedNotifications bool
propagatePruningPointUTXOSetOverrideNotifications bool
propagateUTXOsChangedNotificationAddresses map[utxoindex.ScriptPublicKeyString]*UTXOsChangedNotificationAddress
@@ -181,6 +182,25 @@ func (nm *NotificationManager) NotifyVirtualSelectedParentBlueScoreChanged(
return nil
}
// NotifyVirtualDaaScoreChanged notifies the notification manager that the DAG's
// virtual DAA score has changed
func (nm *NotificationManager) NotifyVirtualDaaScoreChanged(
notification *appmessage.VirtualDaaScoreChangedNotificationMessage) error {
nm.RLock()
defer nm.RUnlock()
for router, listener := range nm.listeners {
if listener.propagateVirtualDaaScoreChangedNotifications {
err := router.OutgoingRoute().Enqueue(notification)
if err != nil {
return err
}
}
}
return nil
}
// NotifyPruningPointUTXOSetOverride notifies the notification manager that the UTXO index
// reset due to pruning point change via IBD.
func (nm *NotificationManager) NotifyPruningPointUTXOSetOverride() error {
@@ -308,6 +328,12 @@ func (nl *NotificationListener) PropagateVirtualSelectedParentBlueScoreChangedNo
nl.propagateVirtualSelectedParentBlueScoreChangedNotifications = true
}
// PropagateVirtualDaaScoreChangedNotifications instructs the listener to send
// virtual DAA score notifications to the remote listener
func (nl *NotificationListener) PropagateVirtualDaaScoreChangedNotifications() {
nl.propagateVirtualDaaScoreChangedNotifications = true
}
// PropagatePruningPointUTXOSetOverrideNotifications instructs the listener to send pruning point UTXO set override notifications
// to the remote listener.
func (nl *NotificationListener) PropagatePruningPointUTXOSetOverrideNotifications() {

View File

@@ -2,14 +2,10 @@ package rpccontext
import (
"encoding/hex"
"fmt"
"math"
"math/big"
"strconv"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/difficulty"
difficultyPackage "github.com/kaspanet/kaspad/util/difficulty"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
@@ -26,79 +22,6 @@ import (
// ErrBuildBlockVerboseDataInvalidBlock indicates that a block that was given to BuildBlockVerboseData is invalid.
var ErrBuildBlockVerboseDataInvalidBlock = errors.New("ErrBuildBlockVerboseDataInvalidBlock")
// BuildBlockVerboseData builds a BlockVerboseData from the given blockHeader.
// A block may optionally also be given if it's available in the calling context.
func (ctx *Context) BuildBlockVerboseData(blockHeader externalapi.BlockHeader, block *externalapi.DomainBlock,
includeTransactionVerboseData bool) (*appmessage.BlockVerboseData, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "BuildBlockVerboseData")
defer onEnd()
hash := consensushashing.HeaderHash(blockHeader)
blockInfo, err := ctx.Domain.Consensus().GetBlockInfo(hash)
if err != nil {
return nil, err
}
if blockInfo.BlockStatus == externalapi.StatusInvalid {
return nil, errors.Wrap(ErrBuildBlockVerboseDataInvalidBlock, "cannot build verbose data for "+
"invalid block")
}
childrenHashes, err := ctx.Domain.Consensus().GetBlockChildren(hash)
if err != nil {
return nil, err
}
result := &appmessage.BlockVerboseData{
Hash: hash.String(),
Version: blockHeader.Version(),
VersionHex: fmt.Sprintf("%08x", blockHeader.Version()),
HashMerkleRoot: blockHeader.HashMerkleRoot().String(),
AcceptedIDMerkleRoot: blockHeader.AcceptedIDMerkleRoot().String(),
UTXOCommitment: blockHeader.UTXOCommitment().String(),
ParentHashes: hashes.ToStrings(blockHeader.ParentHashes()),
ChildrenHashes: hashes.ToStrings(childrenHashes),
Nonce: blockHeader.Nonce(),
Time: blockHeader.TimeInMilliseconds(),
Bits: strconv.FormatInt(int64(blockHeader.Bits()), 16),
Difficulty: ctx.GetDifficultyRatio(blockHeader.Bits(), ctx.Config.ActiveNetParams),
BlueScore: blockInfo.BlueScore,
IsHeaderOnly: blockInfo.BlockStatus == externalapi.StatusHeaderOnly,
}
if blockInfo.BlockStatus != externalapi.StatusHeaderOnly {
if block == nil {
block, err = ctx.Domain.Consensus().GetBlock(hash)
if err != nil {
return nil, err
}
}
txIDs := make([]string, len(block.Transactions))
for i, tx := range block.Transactions {
txIDs[i] = consensushashing.TransactionID(tx).String()
}
result.TxIDs = txIDs
if includeTransactionVerboseData {
transactionVerboseData := make([]*appmessage.TransactionVerboseData, len(block.Transactions))
for i, tx := range block.Transactions {
txID := consensushashing.TransactionID(tx).String()
data, err := ctx.BuildTransactionVerboseData(tx, txID, blockHeader, hash.String())
if err != nil {
return nil, err
}
transactionVerboseData[i] = data
}
result.TransactionVerboseData = transactionVerboseData
}
}
return result, nil
}
// GetDifficultyRatio returns the proof-of-work difficulty as a multiple of the
// minimum difficulty using the passed bits field from the header of a block.
func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) float64 {
@@ -106,7 +29,7 @@ func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) fl
// converted back to a number. Note this is not the same as the proof of
// work limit directly because the block difficulty is encoded in a block
// with the compact form which loses precision.
target := difficulty.CompactToBig(bits)
target := difficultyPackage.CompactToBig(bits)
difficulty := new(big.Rat).SetFrac(params.PowMax, target)
diff, _ := difficulty.Float64()
@@ -117,100 +40,128 @@ func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) fl
return diff
}
// BuildTransactionVerboseData builds a TransactionVerboseData from
// the given parameters
func (ctx *Context) BuildTransactionVerboseData(tx *externalapi.DomainTransaction, txID string,
blockHeader externalapi.BlockHeader, blockHash string) (
*appmessage.TransactionVerboseData, error) {
// PopulateBlockWithVerboseData populates the given `block` with verbose
// data from `domainBlockHeader` and optionally from `domainBlock`
func (ctx *Context) PopulateBlockWithVerboseData(block *appmessage.RPCBlock, domainBlockHeader externalapi.BlockHeader,
domainBlock *externalapi.DomainBlock, includeTransactionVerboseData bool) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "BuildTransactionVerboseData")
defer onEnd()
blockHash := consensushashing.HeaderHash(domainBlockHeader)
txReply := &appmessage.TransactionVerboseData{
TxID: txID,
Hash: consensushashing.TransactionHash(tx).String(),
Size: estimatedsize.TransactionEstimatedSerializedSize(tx),
TransactionVerboseInputs: ctx.buildTransactionVerboseInputs(tx),
TransactionVerboseOutputs: ctx.buildTransactionVerboseOutputs(tx, nil),
Version: tx.Version,
LockTime: tx.LockTime,
SubnetworkID: tx.SubnetworkID.String(),
Gas: tx.Gas,
Payload: hex.EncodeToString(tx.Payload),
blockInfo, err := ctx.Domain.Consensus().GetBlockInfo(blockHash)
if err != nil {
return err
}
if blockHeader != nil {
txReply.Time = uint64(blockHeader.TimeInMilliseconds())
txReply.BlockTime = uint64(blockHeader.TimeInMilliseconds())
txReply.BlockHash = blockHash
if blockInfo.BlockStatus == externalapi.StatusInvalid {
return errors.Wrap(ErrBuildBlockVerboseDataInvalidBlock, "cannot build verbose data for "+
"invalid block")
}
return txReply, nil
}
_, selectedParentHash, childrenHashes, err := ctx.Domain.Consensus().GetBlockRelations(blockHash)
if err != nil {
return err
}
func (ctx *Context) buildTransactionVerboseInputs(tx *externalapi.DomainTransaction) []*appmessage.TransactionVerboseInput {
inputs := make([]*appmessage.TransactionVerboseInput, len(tx.Inputs))
for i, transactionInput := range tx.Inputs {
// The disassembled string will contain [error] inline
// if the script doesn't fully parse, so ignore the
// error here.
disbuf, _ := txscript.DisasmString(constants.MaxScriptPublicKeyVersion, transactionInput.SignatureScript)
block.VerboseData = &appmessage.RPCBlockVerboseData{
Hash: blockHash.String(),
Difficulty: ctx.GetDifficultyRatio(domainBlockHeader.Bits(), ctx.Config.ActiveNetParams),
ChildrenHashes: hashes.ToStrings(childrenHashes),
IsHeaderOnly: blockInfo.BlockStatus == externalapi.StatusHeaderOnly,
BlueScore: blockInfo.BlueScore,
}
// selectedParentHash will be nil in the genesis block
if selectedParentHash != nil {
block.VerboseData.SelectedParentHash = selectedParentHash.String()
}
input := &appmessage.TransactionVerboseInput{}
input.TxID = transactionInput.PreviousOutpoint.TransactionID.String()
input.OutputIndex = transactionInput.PreviousOutpoint.Index
input.Sequence = transactionInput.Sequence
input.ScriptSig = &appmessage.ScriptSig{
Asm: disbuf,
Hex: hex.EncodeToString(transactionInput.SignatureScript),
if blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
return nil
}
// Get the block if we didn't receive it previously
if domainBlock == nil {
domainBlock, err = ctx.Domain.Consensus().GetBlockEvenIfHeaderOnly(blockHash)
if err != nil {
return err
}
inputs[i] = input
}
return inputs
}
transactionIDs := make([]string, len(domainBlock.Transactions))
for i, transaction := range domainBlock.Transactions {
transactionIDs[i] = consensushashing.TransactionID(transaction).String()
}
block.VerboseData.TransactionIDs = transactionIDs
// buildTransactionVerboseOutputs returns a slice of JSON objects for the outputs of the passed
// transaction.
func (ctx *Context) buildTransactionVerboseOutputs(tx *externalapi.DomainTransaction, filterAddrMap map[string]struct{}) []*appmessage.TransactionVerboseOutput {
outputs := make([]*appmessage.TransactionVerboseOutput, len(tx.Outputs))
for i, transactionOutput := range tx.Outputs {
// Ignore the error here since an error means the script
// couldn't parse and there is no additional information about
// it anyways.
scriptClass, addr, _ := txscript.ExtractScriptPubKeyAddress(
transactionOutput.ScriptPublicKey, ctx.Config.ActiveNetParams)
// Encode the addresses while checking if the address passes the
// filter when needed.
passesFilter := len(filterAddrMap) == 0
var encodedAddr string
if addr != nil {
encodedAddr = addr.EncodeAddress()
// If the filter doesn't already pass, make it pass if
// the address exists in the filter.
if _, exists := filterAddrMap[encodedAddr]; exists {
passesFilter = true
if includeTransactionVerboseData {
for _, transaction := range block.Transactions {
err := ctx.PopulateTransactionWithVerboseData(transaction, domainBlockHeader)
if err != nil {
return err
}
}
if !passesFilter {
continue
}
output := &appmessage.TransactionVerboseOutput{}
output.Index = uint32(i)
output.Value = transactionOutput.Value
output.ScriptPubKey = &appmessage.ScriptPubKeyResult{
Version: transactionOutput.ScriptPublicKey.Version,
Address: encodedAddr,
Hex: hex.EncodeToString(transactionOutput.ScriptPublicKey.Script),
Type: scriptClass.String(),
}
outputs[i] = output
}
return outputs
return nil
}
// PopulateTransactionWithVerboseData populates the given `transaction` with
// verbose data from `domainTransaction`
func (ctx *Context) PopulateTransactionWithVerboseData(
transaction *appmessage.RPCTransaction, domainBlockHeader externalapi.BlockHeader) error {
domainTransaction, err := appmessage.RPCTransactionToDomainTransaction(transaction)
if err != nil {
return err
}
transaction.VerboseData = &appmessage.RPCTransactionVerboseData{
TransactionID: consensushashing.TransactionID(domainTransaction).String(),
Hash: consensushashing.TransactionHash(domainTransaction).String(),
Size: estimatedsize.TransactionEstimatedSerializedSize(domainTransaction),
}
if domainBlockHeader != nil {
transaction.VerboseData.BlockHash = consensushashing.HeaderHash(domainBlockHeader).String()
transaction.VerboseData.BlockTime = uint64(domainBlockHeader.TimeInMilliseconds())
}
for _, input := range transaction.Inputs {
ctx.populateTransactionInputWithVerboseData(input)
}
for _, output := range transaction.Outputs {
err := ctx.populateTransactionOutputWithVerboseData(output)
if err != nil {
return err
}
}
return nil
}
func (ctx *Context) populateTransactionInputWithVerboseData(transactionInput *appmessage.RPCTransactionInput) {
transactionInput.VerboseData = &appmessage.RPCTransactionInputVerboseData{}
}
func (ctx *Context) populateTransactionOutputWithVerboseData(transactionOutput *appmessage.RPCTransactionOutput) error {
scriptPublicKey, err := hex.DecodeString(transactionOutput.ScriptPublicKey.Script)
if err != nil {
return err
}
domainScriptPublicKey := &externalapi.ScriptPublicKey{
Script: scriptPublicKey,
Version: transactionOutput.ScriptPublicKey.Version,
}
// Ignore the error here since an error means the script
// couldn't be parsed and there's no additional information about
// it anyways
scriptPublicKeyType, scriptPublicKeyAddress, _ := txscript.ExtractScriptPubKeyAddress(
domainScriptPublicKey, ctx.Config.ActiveNetParams)
var encodedScriptPublicKeyAddress string
if scriptPublicKeyAddress != nil {
encodedScriptPublicKeyAddress = scriptPublicKeyAddress.EncodeAddress()
}
transactionOutput.VerboseData = &appmessage.RPCTransactionOutputVerboseData{
ScriptPublicKeyType: scriptPublicKeyType.String(),
ScriptPublicKeyAddress: encodedScriptPublicKeyAddress,
}
return nil
}

View File

@@ -20,7 +20,7 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
return errorMessage, nil
}
header, err := context.Domain.Consensus().GetBlockHeader(hash)
block, err := context.Domain.Consensus().GetBlockEvenIfHeaderOnly(hash)
if err != nil {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s not found", hash)
@@ -29,7 +29,13 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
response := appmessage.NewGetBlockResponseMessage()
blockVerboseData, err := context.BuildBlockVerboseData(header, nil, getBlockRequest.IncludeTransactionVerboseData)
if getBlockRequest.IncludeTransactionVerboseData {
response.Block = appmessage.DomainBlockToRPCBlock(block)
} else {
response.Block = appmessage.DomainBlockToRPCBlock(&externalapi.DomainBlock{Header: block.Header})
}
err = context.PopulateBlockWithVerboseData(response.Block, block.Header, block, getBlockRequest.IncludeTransactionVerboseData)
if err != nil {
if errors.Is(err, rpccontext.ErrBuildBlockVerboseDataInvalidBlock) {
errorMessage := &appmessage.GetBlockResponseMessage{}
@@ -39,7 +45,5 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
return nil, err
}
response.BlockVerboseData = blockVerboseData
return response, nil
}

View File

@@ -35,6 +35,7 @@ func HandleGetBlockDAGInfo(context *rpccontext.Context, _ *router.Router, _ appm
response.VirtualParentHashes = hashes.ToStrings(virtualInfo.ParentHashes)
response.Difficulty = context.GetDifficultyRatio(virtualInfo.Bits, context.Config.ActiveNetParams)
response.PastMedianTime = virtualInfo.PastMedianTime
response.VirtualDAAScore = virtualInfo.DAAScore
pruningPoint, err := context.Domain.Consensus().PruningPoint()
if err != nil {

View File

@@ -31,12 +31,12 @@ func HandleGetBlockTemplate(context *rpccontext.Context, _ *router.Router, reque
if err != nil {
return nil, err
}
msgBlock := appmessage.DomainBlockToMsgBlock(templateBlock)
rpcBlock := appmessage.DomainBlockToRPCBlock(templateBlock)
isSynced, err := context.ProtocolManager.ShouldMine()
if err != nil {
return nil, err
}
return appmessage.NewGetBlockTemplateResponseMessage(msgBlock, isSynced), nil
return appmessage.NewGetBlockTemplateResponseMessage(rpcBlock, isSynced), nil
}

View File

@@ -18,8 +18,8 @@ const (
func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getBlocksRequest := request.(*appmessage.GetBlocksRequestMessage)
// Validate that user didn't set IncludeTransactionVerboseData without setting IncludeBlockVerboseData
if !getBlocksRequest.IncludeBlockVerboseData && getBlocksRequest.IncludeTransactionVerboseData {
// Validate that user didn't set IncludeTransactionVerboseData without setting IncludeBlocks
if !getBlocksRequest.IncludeBlocks && getBlocksRequest.IncludeTransactionVerboseData {
return &appmessage.GetBlocksResponseMessage{
Error: appmessage.RPCErrorf(
"If includeTransactionVerboseData is set, then includeBlockVerboseData must be set as well"),
@@ -81,26 +81,28 @@ func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appm
}
// Prepare the response
response := &appmessage.GetBlocksResponseMessage{
BlockHashes: hashes.ToStrings(blockHashes),
}
// Retrieve all block data in case BlockVerboseData was requested
if getBlocksRequest.IncludeBlockVerboseData {
response.BlockVerboseData = make([]*appmessage.BlockVerboseData, len(blockHashes))
response := appmessage.NewGetBlocksResponseMessage()
response.BlockHashes = hashes.ToStrings(blockHashes)
if getBlocksRequest.IncludeBlocks {
rpcBlocks := make([]*appmessage.RPCBlock, len(blockHashes))
for i, blockHash := range blockHashes {
blockHeader, err := context.Domain.Consensus().GetBlockHeader(blockHash)
if err != nil {
return nil, err
}
blockVerboseData, err := context.BuildBlockVerboseData(blockHeader, nil,
getBlocksRequest.IncludeTransactionVerboseData)
block, err := context.Domain.Consensus().GetBlockEvenIfHeaderOnly(blockHash)
if err != nil {
return nil, err
}
response.BlockVerboseData[i] = blockVerboseData
if getBlocksRequest.IncludeTransactionVerboseData {
rpcBlocks[i] = appmessage.DomainBlockToRPCBlock(block)
} else {
rpcBlocks[i] = appmessage.DomainBlockToRPCBlock(&externalapi.DomainBlock{Header: block.Header})
}
err = context.PopulateBlockWithVerboseData(rpcBlocks[i], block.Header, nil, getBlocksRequest.IncludeTransactionVerboseData)
if err != nil {
return nil, err
}
}
response.Blocks = rpcBlocks
}
return response, nil
}

View File

@@ -15,7 +15,6 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/miningmanager"
"github.com/kaspanet/kaspad/infrastructure/config"
)
@@ -28,18 +27,18 @@ func (d fakeDomain) Consensus() externalapi.Consensus { return d }
func (d fakeDomain) MiningManager() miningmanager.MiningManager { return nil }
func TestHandleGetBlocks(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
stagingArea := model.NewStagingArea()
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestHandleGetBlocks")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestHandleGetBlocks")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
fakeContext := rpccontext.Context{
Config: &config.Config{Flags: &config.Flags{NetworkFlags: config.NetworkFlags{ActiveNetParams: params}}},
Config: &config.Config{Flags: &config.Flags{NetworkFlags: config.NetworkFlags{ActiveNetParams: &consensusConfig.Params}}},
Domain: fakeDomain{tc},
}
@@ -81,7 +80,7 @@ func TestHandleGetBlocks(t *testing.T) {
// \ | /
// etc.
expectedOrder := make([]*externalapi.DomainHash, 0, 40)
mergingBlock := params.GenesisHash
mergingBlock := consensusConfig.GenesisHash
for i := 0; i < 10; i++ {
splitBlocks := make([]*externalapi.DomainHash, 0, 3)
for j := 0; j < 3; j++ {
@@ -134,13 +133,13 @@ func TestHandleGetBlocks(t *testing.T) {
virtualSelectedParent, actualBlocks.BlockHashes)
}
expectedOrder = append([]*externalapi.DomainHash{params.GenesisHash}, expectedOrder...)
expectedOrder = append([]*externalapi.DomainHash{consensusConfig.GenesisHash}, expectedOrder...)
actualOrder := getBlocks(nil)
if !reflect.DeepEqual(actualOrder.BlockHashes, hashes.ToStrings(expectedOrder)) {
t.Fatalf("TestHandleGetBlocks \nexpected: %v \nactual:\n%v", expectedOrder, actualOrder.BlockHashes)
}
requestAllExplictly := getBlocks(params.GenesisHash)
requestAllExplictly := getBlocks(consensusConfig.GenesisHash)
if !reflect.DeepEqual(requestAllExplictly.BlockHashes, hashes.ToStrings(expectedOrder)) {
t.Fatalf("TestHandleGetBlocks \nexpected: \n%v\n. actual:\n%v", expectedOrder, requestAllExplictly.BlockHashes)
}

View File

@@ -3,25 +3,22 @@ package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetMempoolEntries handles the respectively named RPC command
func HandleGetMempoolEntries(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
transactions := context.Domain.MiningManager().AllTransactions()
entries := make([]*appmessage.MempoolEntry, 0, len(transactions))
for _, tx := range transactions {
transactionVerboseData, err := context.BuildTransactionVerboseData(
tx, consensushashing.TransactionID(tx).String(), nil, "")
for _, transaction := range transactions {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
entries = append(entries, &appmessage.MempoolEntry{
Fee: tx.Fee,
TransactionVerboseData: transactionVerboseData,
Fee: transaction.Fee,
Transaction: rpcTransaction,
})
}

View File

@@ -24,12 +24,11 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques
errorMessage.Error = appmessage.RPCErrorf("Transaction %s was not found", transactionID)
return errorMessage, nil
}
transactionVerboseData, err := context.BuildTransactionVerboseData(
transaction, getMempoolEntryRequest.TxID, nil, "")
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err = context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
return appmessage.NewGetMempoolEntryResponseMessage(transaction.Fee, transactionVerboseData), nil
return appmessage.NewGetMempoolEntryResponseMessage(transaction.Fee, rpcTransaction), nil
}

View File

@@ -0,0 +1,19 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleNotifyVirtualDaaScoreChanged handles the respectively named RPC command
func HandleNotifyVirtualDaaScoreChanged(context *rpccontext.Context, router *router.Router, _ appmessage.Message) (appmessage.Message, error) {
listener, err := context.NotificationManager.Listener(router)
if err != nil {
return nil, err
}
listener.PropagateVirtualDaaScoreChangedNotifications()
response := appmessage.NewNotifyVirtualDaaScoreChangedResponseMessage()
return response, nil
}

View File

@@ -14,9 +14,6 @@ import (
func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
submitBlockRequest := request.(*appmessage.SubmitBlockRequestMessage)
msgBlock := submitBlockRequest.Block
domainBlock := appmessage.MsgBlockToDomainBlock(msgBlock)
if context.ProtocolManager.IsIBDRunning() {
return &appmessage.SubmitBlockResponseMessage{
Error: appmessage.RPCErrorf("Block not submitted - IBD is running"),
@@ -24,7 +21,15 @@ func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request ap
}, nil
}
err := context.ProtocolManager.AddBlock(domainBlock)
domainBlock, err := appmessage.RPCBlockToDomainBlock(submitBlockRequest.Block)
if err != nil {
return &appmessage.SubmitBlockResponseMessage{
Error: appmessage.RPCErrorf("Could not parse block: %s", err),
RejectReason: appmessage.RejectReasonBlockInvalid,
}, nil
}
err = context.ProtocolManager.AddBlock(domainBlock)
if err != nil {
isProtocolOrRuleError := errors.As(err, &ruleerrors.RuleError{}) || errors.As(err, &protocolerrors.ProtocolError{})
if !isProtocolOrRuleError {

View File

@@ -1,11 +1,100 @@
Kaspad v0.10.3 - 2021-06-06
===========================
Non-breaking changes:
* Implement NotifyVirtualDaaScoreChanged (#1737)
Kaspad v0.10.2 - 2021-05-18
===========================
Non-breaking changes:
* Fix getBlock and getBlocks RPC commands to return blocks and transactions properly (#1716)
* serializeAddress should always serialize as IPv6, since it assumes the IP size is 16 bytes (#1720)
* Add VirtualDaaScore to GetBlockDagInfo (#1719)
* Fix calcTxSequenceLockFromReferencedUTXOEntries for loop break condition (#1723)
* Fix overflow when checking coinbase maturity and don't ban peers that send transactions with immature spend (#1722)
Kaspad v0.10.1 - 2021-05-11
===========================
* Calculate virtual's acceptance data and multiset after importing a new pruning point (#1700)
Kaspad v0.10.0 - 2021-04-26
===========================
Major changes include:
* Implementing a signature hashing scheme similar to BIP-143
* Replacing HASH160 with BLAKE2B
* Replacing ECMH with MuHash
* Removing RIPEMD160 and SHA1 from the codebase entirely
* Making P2PKH transactions non-standard
* Vastly enhancing the CLI wallet
* Restructuring kaspad's app/home directory
* Modifying block and transaction types in the RPC to be easier to consume clientside
A partial list of the more-important commits is as follows:
* Fix data race in GetBlockChildren (#1579)
* Remove payload hash (#1583)
* Add the mempool size to getInfo RPC command (#1584)
* Change the difficulty to be calculated based on the same block instead of its selected parent (#1591)
* Adjust the difficulty in the first difficultyAdjustmentWindowSize blocks (#1592)
* Adding DAA score (#1596)
* Use DAA score where needed (#1602)
* Remove the Services field from NetAddress. (#1610)
* Fix getBlocks to not add the anticone when some blocks were filtered by GetHashesBetween (#1611)
* Restructure the default ~/.kaspad directory layout (#1613)
* Replace the HomeDir flag with a AppDir flag (#1615)
* Implement BIP-143-like sighash (#1598)
* Change --datadir to --appdir and remove symmetrical connection in stability tests (#1617)
* Use BLAKE2B instead of HASH160, and get rid of any usage of RIPEMD160 and SHA1 (#1618)
* Replace ECMH with Muhash (#1624)
* Add support for multiple staging areas (#1633)
* Make sure the ghostdagDataStore cache is at least DifficultyAdjustmentBlockWindow sized (#1635)
* Resolve each block status in it's own staging area (#1634)
* Add mass limit to mempool (#1627)
* In RPC, use RPCTransactions and RPCBlocks instead of TransactionMessages and BlockMessages (#1609)
* Use go-secp256k1 v0.0.5 (#1640)
* Add a show-address subcommand to kaspawallet (#1653)
* Replace p2pkh with p2pk (#1650)
* Implement importing private keys into the wallet (#1655)
* Add dump unencrypted data sub command to the wallet (#1661)
* Add ECDSA support (#1657)
* Add OpCheckMultiSigECDSA (#1663)
* Add ECDSA support to the wallet (#1664)
* Make moving the pruning point faster (#1660)
* Implement new mechanism for updating UTXO Diffs (#1671)
Kaspad v0.9.2 - 2021-03-31
===========================
* Increase the route capacity of InvTransaction messages. (#1603) (#1637)
Kaspad v0.9.1 - 2021-03-14
===========================
* Testnet network reset
Kaspad v0.9.0 - 2021-03-04
===========================
* Merge big subdags in pick virtual parents (#1574)
* Write in the reject message the tx rejection reason (#1573)
* Add nil checks for protowire (#1570)
* Increase getBlocks limit to 1000 (#1572)
* Return RPC error if getBlock's lowHash doesn't exist (#1569)
* Add default dns-seeder to testnet (#1568)
* Fix utxoindex deserialization (#1566)
* Add pruning point hash to GetBlockDagInfo response (#1565)
* Use EmitUnpopulated so that kaspactl prints all fields, even the default ones (#1561)
* Stop logging an error whenever an RPC/P2P connection is canceled (#1562)
* Cleanup the logger and make it asynchronous (#1524)
* Close all iterators (#1542)
* Add childrenHashes to GetBlock/s RPC commands (#1560)
* Add ScriptPublicKey.Version to RPC (#1559)
* Fix the target block rate to create less bursty mining (#1554)
Kaspad v0.8.10 - 2021-02-25
===========================
[*] Fix bug where invalid mempool transactions were not removed (#1551)
[*] Add RPC reconnection to the miner (#1552)
[*] Remove virtual diff parents - only selectedTip is virtualDiffParent now (#1550)
[*] Fix UTXO index (#1548)
[*] Prevent fast failing (#1545)
[*] Increase the sleep time in kaspaminer when the node is not synced (#1544)
[*] Disallow header only blocks on RPC, relay and when requesting IBD full blocks (#1537)
[*] Make templateManager hold a DomainBlock and isSynced bool instead of a GetBlockTemplateResponseMessage (#1538)
* Fix bug where invalid mempool transactions were not removed (#1551)
* Add RPC reconnection to the miner (#1552)
* Remove virtual diff parents - only selectedTip is virtualDiffParent now (#1550)
* Fix UTXO index (#1548)
* Prevent fast failing (#1545)
* Increase the sleep time in kaspaminer when the node is not synced (#1544)
* Disallow header only blocks on RPC, relay and when requesting IBD full blocks (#1537)
* Make templateManager hold a DomainBlock and isSynced bool instead of a GetBlockTemplateResponseMessage (#1538)

9
cmd/genkeypair/README.md Normal file
View File

@@ -0,0 +1,9 @@
genkeypair
========
A tool for generating private-key-address pairs.
Note: This tool prints unencrypted private keys and is not recommended for day
to day use, and is intended mainly for tests.
In order to manage your funds it's recommended to use [kaspawallet](../kaspawallet)

26
cmd/genkeypair/config.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/infrastructure/config"
)
type configFlags struct {
config.NetworkFlags
}
func parseConfig() (*configFlags, error) {
cfg := &configFlags{}
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
_, err := parser.Parse()
if err != nil {
return nil, err
}
err = cfg.ResolveNetwork(parser)
if err != nil {
return nil, err
}
return cfg, nil
}

27
cmd/genkeypair/main.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/util"
)
func main() {
cfg, err := parseConfig()
if err != nil {
panic(err)
}
privateKey, publicKey, err := libkaspawallet.CreateKeyPair(false)
if err != nil {
panic(err)
}
addr, err := util.NewAddressPublicKey(publicKey, cfg.NetParams().Prefix)
if err != nil {
panic(err)
}
fmt.Printf("Private key: %x\n", privateKey)
fmt.Printf("Address: %s\n", addr)
}

View File

@@ -5,72 +5,32 @@ import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/pkg/errors"
"sync"
"sync/atomic"
"time"
)
const minerTimeout = 10 * time.Second
type minerClient struct {
isReconnecting uint32
clientLock sync.RWMutex
rpcClient *rpcclient.RPCClient
*rpcclient.RPCClient
cfg *configFlags
blockAddedNotificationChan chan struct{}
}
func (mc *minerClient) safeRPCClient() *rpcclient.RPCClient {
mc.clientLock.RLock()
defer mc.clientLock.RUnlock()
return mc.rpcClient
}
func (mc *minerClient) reconnect() {
swapped := atomic.CompareAndSwapUint32(&mc.isReconnecting, 0, 1)
if !swapped {
return
}
defer atomic.StoreUint32(&mc.isReconnecting, 0)
mc.clientLock.Lock()
defer mc.clientLock.Unlock()
retryDuration := time.Second
const maxRetryDuration = time.Minute
log.Infof("Reconnecting RPC connection")
for {
err := mc.connect()
if err == nil {
return
}
if retryDuration < time.Minute {
retryDuration *= 2
} else {
retryDuration = maxRetryDuration
}
log.Errorf("Got error '%s' while reconnecting. Trying again in %s", err, retryDuration)
time.Sleep(retryDuration)
}
}
func (mc *minerClient) connect() error {
rpcAddress, err := mc.cfg.NetParams().NormalizeRPCServerAddress(mc.cfg.RPCServer)
if err != nil {
return err
}
mc.rpcClient, err = rpcclient.NewRPCClient(rpcAddress)
rpcClient, err := rpcclient.NewRPCClient(rpcAddress)
if err != nil {
return err
}
mc.rpcClient.SetTimeout(minerTimeout)
mc.rpcClient.SetLogger(backendLog, logger.LevelTrace)
mc.RPCClient = rpcClient
mc.SetTimeout(minerTimeout)
mc.SetLogger(backendLog, logger.LevelTrace)
err = mc.rpcClient.RegisterForBlockAddedNotifications(func(_ *appmessage.BlockAddedNotificationMessage) {
err = mc.RegisterForBlockAddedNotifications(func(_ *appmessage.BlockAddedNotificationMessage) {
select {
case mc.blockAddedNotificationChan <- struct{}{}:
default:

View File

@@ -40,7 +40,7 @@ func main() {
if err != nil {
panic(errors.Wrap(err, "error connecting to the RPC server"))
}
defer client.safeRPCClient().Disconnect()
defer client.Disconnect()
miningAddr, err := util.DecodeAddress(cfg.MiningAddr, cfg.ActiveNetParams.Prefix)
if err != nil {

View File

@@ -6,18 +6,14 @@ import (
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/cmd/kaspaminer/templatemanager"
"github.com/kaspanet/kaspad/domain/consensus/model/pow"
"github.com/kaspanet/kaspad/util/difficulty"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/cmd/kaspaminer/templatemanager"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/pow"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/difficulty"
"github.com/pkg/errors"
)
@@ -114,13 +110,17 @@ func logHashRate() {
func handleFoundBlock(client *minerClient, block *externalapi.DomainBlock) error {
blockHash := consensushashing.BlockHash(block)
log.Infof("Submitting block %s to %s", blockHash, client.safeRPCClient().Address())
log.Infof("Submitting block %s to %s", blockHash, client.Address())
rejectReason, err := client.safeRPCClient().SubmitBlock(block)
rejectReason, err := client.SubmitBlock(block)
if err != nil {
if nativeerrors.Is(err, router.ErrTimeout) {
log.Warnf("Got timeout while submitting block %s to %s: %s", blockHash, client.safeRPCClient().Address(), err)
client.reconnect()
log.Warnf("Got timeout while submitting block %s to %s: %s", blockHash, client.Address(), err)
return client.Reconnect()
}
if nativeerrors.Is(err, router.ErrRouteClosed) {
log.Debugf("Got route is closed while requesting block template from %s. "+
"The client is most likely reconnecting", client.Address())
return nil
}
if rejectReason == appmessage.RejectReasonIsInIBD {
@@ -129,7 +129,7 @@ func handleFoundBlock(client *minerClient, block *externalapi.DomainBlock) error
time.Sleep(waitTime)
return nil
}
return errors.Wrapf(err, "Error submitting block %s to %s", blockHash, client.safeRPCClient().Address())
return errors.Wrapf(err, "Error submitting block %s to %s", blockHash, client.Address())
}
return nil
}
@@ -188,17 +188,29 @@ func getBlockForMining(mineWhenNotSynced bool) *externalapi.DomainBlock {
func templatesLoop(client *minerClient, miningAddr util.Address, errChan chan error) {
getBlockTemplate := func() {
template, err := client.safeRPCClient().GetBlockTemplate(miningAddr.String())
template, err := client.GetBlockTemplate(miningAddr.String())
if nativeerrors.Is(err, router.ErrTimeout) {
log.Warnf("Got timeout while requesting block template from %s: %s", client.safeRPCClient().Address(), err)
client.reconnect()
log.Warnf("Got timeout while requesting block template from %s: %s", client.Address(), err)
reconnectErr := client.Reconnect()
if reconnectErr != nil {
errChan <- reconnectErr
}
return
}
if nativeerrors.Is(err, router.ErrRouteClosed) {
log.Debugf("Got route is closed while requesting block template from %s. "+
"The client is most likely reconnecting", client.Address())
return
}
if err != nil {
errChan <- errors.Wrapf(err, "Error getting block template from %s", client.safeRPCClient().Address())
errChan <- errors.Wrapf(err, "Error getting block template from %s", client.Address())
return
}
err = templatemanager.Set(template)
if err != nil {
errChan <- errors.Wrapf(err, "Error setting block template from %s", client.Address())
return
}
templatemanager.Set(template)
}
getBlockTemplate()

View File

@@ -23,10 +23,14 @@ func Get() (*externalapi.DomainBlock, bool) {
}
// Set sets the current template to work on
func Set(template *appmessage.GetBlockTemplateResponseMessage) {
block := appmessage.MsgBlockToDomainBlock(template.MsgBlock)
func Set(template *appmessage.GetBlockTemplateResponseMessage) error {
block, err := appmessage.RPCBlockToDomainBlock(template.Block)
if err != nil {
return err
}
lock.Lock()
defer lock.Unlock()
currentTemplate = block
isSynced = template.IsSynced
return nil
}

View File

@@ -2,29 +2,40 @@ package main
import (
"fmt"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/util"
)
func balance(conf *balanceConfig) error {
client, err := rpcclient.NewRPCClient(conf.RPCServer)
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
if err != nil {
return err
}
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{conf.Address})
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
virtualSelectedParentBlueScoreResponse, err := client.GetVirtualSelectedParentBlueScore()
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
if err != nil {
return err
}
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{addr.String()})
if err != nil {
return err
}
blockDAGInfo, err := client.GetBlockDAGInfo()
if err != nil {
return err
}
virtualSelectedParentBlueScore := virtualSelectedParentBlueScoreResponse.BlueScore
var availableBalance, pendingBalance uint64
for _, entry := range getUTXOsByAddressesResponse.Entries {
if isUTXOSpendable(entry, virtualSelectedParentBlueScore, conf.ActiveNetParams.BlockCoinbaseMaturity) {
if isUTXOSpendable(entry, blockDAGInfo.VirtualDAAScore, conf.ActiveNetParams.BlockCoinbaseMaturity) {
availableBalance += entry.UTXOEntry.Amount
} else {
pendingBalance += entry.UTXOEntry.Amount

View File

@@ -0,0 +1,34 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
)
func broadcast(conf *broadcastConfig) error {
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
if err != nil {
return err
}
psTxBytes, err := hex.DecodeString(conf.Transaction)
if err != nil {
return err
}
tx, err := libkaspawallet.ExtractTransaction(psTxBytes)
if err != nil {
return err
}
transactionID, err := sendTransaction(client, tx)
if err != nil {
return err
}
fmt.Println("Transaction was sent successfully")
fmt.Printf("Transaction ID: \t%s\n", transactionID)
return nil
}

31
cmd/kaspawallet/common.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"fmt"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"os"
)
func isUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
if !entry.UTXOEntry.IsCoinbase {
return true
}
blockBlueScore := entry.UTXOEntry.BlockDAAScore
return blockBlueScore+coinbaseMaturity < virtualDAAScore
}
func printErrorAndExit(err error) {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
func connectToRPC(params *dagconfig.Params, rpcServer string) (*rpcclient.RPCClient, error) {
rpcAddress, err := params.NormalizeRPCServerAddress(rpcServer)
if err != nil {
return nil, err
}
return rpcclient.NewRPCClient(rpcAddress)
}

198
cmd/kaspawallet/config.go Normal file
View File

@@ -0,0 +1,198 @@
package main
import (
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/pkg/errors"
"os"
"github.com/jessevdk/go-flags"
)
const (
createSubCmd = "create"
balanceSubCmd = "balance"
sendSubCmd = "send"
createUnsignedTransactionSubCmd = "create-unsigned-transaction"
signSubCmd = "sign"
broadcastSubCmd = "broadcast"
showAddressSubCmd = "show-address"
dumpUnencryptedDataSubCmd = "dump-unencrypted-data"
)
type configFlags struct {
config.NetworkFlags
}
type createConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
MinimumSignatures uint32 `long:"min-signatures" short:"m" description:"Minimum required signatures" default:"1"`
NumPrivateKeys uint32 `long:"num-private-keys" short:"k" description:"Number of private keys" default:"1"`
NumPublicKeys uint32 `long:"num-public-keys" short:"n" description:"Total number of keys" default:"1"`
ECDSA bool `long:"ecdsa" description:"Create an ECDSA wallet"`
Import bool `long:"import" short:"i" description:"Import private keys (as opposed to generating them)"`
config.NetworkFlags
}
type balanceConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
config.NetworkFlags
}
type sendConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"`
config.NetworkFlags
}
type createUnsignedTransactionConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"`
config.NetworkFlags
}
type signConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Transaction string `long:"transaction" short:"t" description:"The unsigned transaction to sign on (encoded in hex)" required:"true"`
config.NetworkFlags
}
type broadcastConfig struct {
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Transaction string `long:"transaction" short:"t" description:"The signed transaction to broadcast (encoded in hex)" required:"true"`
config.NetworkFlags
}
type showAddressConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
config.NetworkFlags
}
type dumpUnencryptedDataConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
config.NetworkFlags
}
func parseCommandLine() (subCommand string, config interface{}) {
cfg := &configFlags{}
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
createConf := &createConfig{}
parser.AddCommand(createSubCmd, "Creates a new wallet",
"Creates a private key and 3 public addresses, one for each of MainNet, TestNet and DevNet", createConf)
balanceConf := &balanceConfig{}
parser.AddCommand(balanceSubCmd, "Shows the balance of a public address",
"Shows the balance for a public address in Kaspa", balanceConf)
sendConf := &sendConfig{}
parser.AddCommand(sendSubCmd, "Sends a Kaspa transaction to a public address",
"Sends a Kaspa transaction to a public address", sendConf)
createUnsignedTransactionConf := &createUnsignedTransactionConfig{}
parser.AddCommand(createUnsignedTransactionSubCmd, "Create an unsigned Kaspa transaction",
"Create an unsigned Kaspa transaction", createUnsignedTransactionConf)
signConf := &signConfig{}
parser.AddCommand(signSubCmd, "Sign the given partially signed transaction",
"Sign the given partially signed transaction", signConf)
broadcastConf := &broadcastConfig{}
parser.AddCommand(broadcastSubCmd, "Broadcast the given transaction",
"Broadcast the given transaction", broadcastConf)
showAddressConf := &showAddressConfig{}
parser.AddCommand(showAddressSubCmd, "Shows the public address of the current wallet",
"Shows the public address of the current wallet", showAddressConf)
dumpUnencryptedDataConf := &dumpUnencryptedDataConfig{}
parser.AddCommand(dumpUnencryptedDataSubCmd, "Prints the unencrypted wallet data",
"Prints the unencrypted wallet data including its private keys. Anyone that sees it can access "+
"the funds. Use only on safe environment.", dumpUnencryptedDataConf)
_, err := parser.Parse()
if err != nil {
var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
os.Exit(0)
} else {
os.Exit(1)
}
return "", nil
}
switch parser.Command.Active.Name {
case createSubCmd:
combineNetworkFlags(&createConf.NetworkFlags, &cfg.NetworkFlags)
err := createConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = createConf
case balanceSubCmd:
combineNetworkFlags(&balanceConf.NetworkFlags, &cfg.NetworkFlags)
err := balanceConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = balanceConf
case sendSubCmd:
combineNetworkFlags(&sendConf.NetworkFlags, &cfg.NetworkFlags)
err := sendConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = sendConf
case createUnsignedTransactionSubCmd:
combineNetworkFlags(&createUnsignedTransactionConf.NetworkFlags, &cfg.NetworkFlags)
err := createUnsignedTransactionConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = createUnsignedTransactionConf
case signSubCmd:
combineNetworkFlags(&signConf.NetworkFlags, &cfg.NetworkFlags)
err := signConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = signConf
case broadcastSubCmd:
combineNetworkFlags(&broadcastConf.NetworkFlags, &cfg.NetworkFlags)
err := broadcastConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = broadcastConf
case showAddressSubCmd:
combineNetworkFlags(&showAddressConf.NetworkFlags, &cfg.NetworkFlags)
err := showAddressConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = showAddressConf
case dumpUnencryptedDataSubCmd:
combineNetworkFlags(&dumpUnencryptedDataConf.NetworkFlags, &cfg.NetworkFlags)
err := dumpUnencryptedDataConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = dumpUnencryptedDataConf
}
return parser.Command.Active.Name, config
}
func combineNetworkFlags(dst, src *config.NetworkFlags) {
dst.Testnet = dst.Testnet || src.Testnet
dst.Simnet = dst.Simnet || src.Simnet
dst.Devnet = dst.Devnet || src.Devnet
if dst.OverrideDAGParamsFile == "" {
dst.OverrideDAGParamsFile = src.OverrideDAGParamsFile
}
}

71
cmd/kaspawallet/create.go Normal file
View File

@@ -0,0 +1,71 @@
package main
import (
"bufio"
"encoding/hex"
"fmt"
"os"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/pkg/errors"
)
func create(conf *createConfig) error {
var encryptedPrivateKeys []*keys.EncryptedPrivateKey
var publicKeys [][]byte
var err error
if !conf.Import {
encryptedPrivateKeys, publicKeys, err = keys.CreateKeyPairs(conf.NumPrivateKeys, conf.ECDSA)
} else {
encryptedPrivateKeys, publicKeys, err = keys.ImportKeyPairs(conf.NumPrivateKeys)
}
if err != nil {
return err
}
for i, publicKey := range publicKeys {
fmt.Printf("Public key of private key #%d:\n%x\n\n", i+1, publicKey)
}
reader := bufio.NewReader(os.Stdin)
for i := conf.NumPrivateKeys; i < conf.NumPublicKeys; i++ {
fmt.Printf("Enter public key #%d here:\n", i+1)
line, isPrefix, err := reader.ReadLine()
if err != nil {
return err
}
fmt.Println()
if isPrefix {
return errors.Errorf("Public key is too long")
}
publicKey, err := hex.DecodeString(string(line))
if err != nil {
return err
}
publicKeys = append(publicKeys, publicKey)
}
err = keys.WriteKeysFile(
conf.NetParams(), conf.KeysFile, encryptedPrivateKeys, publicKeys, conf.MinimumSignatures, conf.ECDSA)
if err != nil {
return err
}
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
if err != nil {
return err
}
fmt.Printf("The wallet address is:\n%s\n", addr)
return nil
}

View File

@@ -0,0 +1,62 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/util"
)
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
toAddress, err := util.DecodeAddress(conf.ToAddress, conf.NetParams().Prefix)
if err != nil {
return err
}
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
if err != nil {
return err
}
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
if err != nil {
return err
}
utxos, err := fetchSpendableUTXOs(conf.NetParams(), client, fromAddress.String())
if err != nil {
return err
}
sendAmountSompi := uint64(conf.SendAmount * util.SompiPerKaspa)
const feePerInput = 1000
selectedUTXOs, changeSompi, err := selectUTXOs(utxos, sendAmountSompi, feePerInput)
if err != nil {
return err
}
psTx, err := libkaspawallet.CreateUnsignedTransaction(keysFile.PublicKeys,
keysFile.MinimumSignatures,
keysFile.ECDSA,
[]*libkaspawallet.Payment{{
Address: toAddress,
Amount: sendAmountSompi,
}, {
Address: fromAddress,
Amount: changeSompi,
}}, selectedUTXOs)
if err != nil {
return err
}
fmt.Println("Created unsigned transaction")
fmt.Println(hex.EncodeToString(psTx))
return nil
}

View File

@@ -0,0 +1,70 @@
package main
import (
"bufio"
"fmt"
"os"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/pkg/errors"
)
func dumpUnencryptedData(conf *dumpUnencryptedDataConfig) error {
err := confirmDump()
if err != nil {
return err
}
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
privateKeys, err := keysFile.DecryptPrivateKeys()
if err != nil {
return err
}
privateKeysPublicKeys := make(map[string]struct{})
for i, privateKey := range privateKeys {
fmt.Printf("Private key #%d:\n%x\n\n", i+1, privateKey)
publicKey, err := libkaspawallet.PublicKeyFromPrivateKey(privateKey)
if err != nil {
return err
}
privateKeysPublicKeys[string(publicKey)] = struct{}{}
}
i := 1
for _, publicKey := range keysFile.PublicKeys {
if _, exists := privateKeysPublicKeys[string(publicKey)]; exists {
continue
}
fmt.Printf("Public key #%d:\n%x\n\n", i, publicKey)
i++
}
fmt.Printf("Minimum number of signatures: %d\n", keysFile.MinimumSignatures)
return nil
}
func confirmDump() error {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("This operation will print your unencrypted keys on the screen. Anyone that sees this information " +
"will be able to steal your funds. Are you sure you want to proceed (y/N)? ")
line, isPrefix, err := reader.ReadLine()
if err != nil {
return err
}
fmt.Println()
if isPrefix || string(line) != "y" {
return errors.Errorf("Dump aborted by user")
}
return nil
}

View File

@@ -0,0 +1,110 @@
package keys
import (
"bufio"
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/pkg/errors"
"os"
)
// CreateKeyPairs generates `numKeys` number of key pairs.
func CreateKeyPairs(numKeys uint32, ecdsa bool) (encryptedPrivateKeys []*EncryptedPrivateKey, publicKeys [][]byte, err error) {
return createKeyPairsFromFunction(numKeys, func(_ uint32) ([]byte, []byte, error) {
return libkaspawallet.CreateKeyPair(ecdsa)
})
}
// ImportKeyPairs imports a `numKeys` of private keys and generates key pairs out of them.
func ImportKeyPairs(numKeys uint32) (encryptedPrivateKeys []*EncryptedPrivateKey, publicKeys [][]byte, err error) {
return createKeyPairsFromFunction(numKeys, func(keyIndex uint32) ([]byte, []byte, error) {
fmt.Printf("Enter private key #%d here:\n", keyIndex+1)
reader := bufio.NewReader(os.Stdin)
line, isPrefix, err := reader.ReadLine()
if err != nil {
return nil, nil, err
}
if isPrefix {
return nil, nil, errors.Errorf("Private key is too long")
}
privateKey, err := hex.DecodeString(string(line))
if err != nil {
return nil, nil, err
}
publicKey, err := libkaspawallet.PublicKeyFromPrivateKey(privateKey)
if err != nil {
return nil, nil, err
}
return privateKey, publicKey, nil
})
}
func createKeyPairsFromFunction(numKeys uint32, keyPairFunction func(keyIndex uint32) ([]byte, []byte, error)) (
encryptedPrivateKeys []*EncryptedPrivateKey, publicKeys [][]byte, err error) {
password := getPassword("Enter password for the key file:")
confirmPassword := getPassword("Confirm password:")
if subtle.ConstantTimeCompare(password, confirmPassword) != 1 {
return nil, nil, errors.New("Passwords are not identical")
}
encryptedPrivateKeys = make([]*EncryptedPrivateKey, 0, numKeys)
for i := uint32(0); i < numKeys; i++ {
privateKey, publicKey, err := keyPairFunction(i)
if err != nil {
return nil, nil, err
}
publicKeys = append(publicKeys, publicKey)
encryptedPrivateKey, err := encryptPrivateKey(privateKey, password)
if err != nil {
return nil, nil, err
}
encryptedPrivateKeys = append(encryptedPrivateKeys, encryptedPrivateKey)
}
return encryptedPrivateKeys, publicKeys, nil
}
func generateSalt() ([]byte, error) {
salt := make([]byte, 16)
_, err := rand.Read(salt)
if err != nil {
return nil, err
}
return salt, nil
}
func encryptPrivateKey(privateKey []byte, password []byte) (*EncryptedPrivateKey, error) {
salt, err := generateSalt()
if err != nil {
return nil, err
}
aead, err := getAEAD(password, salt)
if err != nil {
return nil, err
}
// Select a random nonce, and leave capacity for the ciphertext.
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(privateKey)+aead.Overhead())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
// Encrypt the message and append the ciphertext to the nonce.
cipher := aead.Seal(nonce, nonce, privateKey, nil)
return &EncryptedPrivateKey{
cipher: cipher,
salt: salt,
}, nil
}

View File

@@ -0,0 +1,41 @@
package keys
import (
"fmt"
"golang.org/x/term"
"os"
"os/signal"
"syscall"
)
// getPassword was adapted from https://gist.github.com/jlinoff/e8e26b4ffa38d379c7f1891fd174a6d0#file-getpassword2-go
func getPassword(prompt string) []byte {
// Get the initial state of the terminal.
initialTermState, e1 := term.GetState(int(syscall.Stdin))
if e1 != nil {
panic(e1)
}
// Restore it in the event of an interrupt.
// CITATION: Konstantin Shaposhnikov - https://groups.google.com/forum/#!topic/golang-nuts/kTVAbtee9UA
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
<-c
_ = term.Restore(int(syscall.Stdin), initialTermState)
os.Exit(1)
}()
// Now get the password.
fmt.Print(prompt)
p, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
panic(err)
}
// Stop looking for ^C on the channel.
signal.Stop(c)
return p
}

View File

@@ -0,0 +1,257 @@
package keys
import (
"bufio"
"crypto/cipher"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/chacha20poly1305"
)
var (
defaultAppDir = util.AppDir("kaspawallet", false)
)
func defaultKeysFile(netParams *dagconfig.Params) string {
return filepath.Join(defaultAppDir, netParams.Name, "keys.json")
}
type encryptedPrivateKeyJSON struct {
Cipher string `json:"cipher"`
Salt string `json:"salt"`
}
type keysFileJSON struct {
EncryptedPrivateKeys []*encryptedPrivateKeyJSON `json:"encryptedPrivateKeys"`
PublicKeys []string `json:"publicKeys"`
MinimumSignatures uint32 `json:"minimumSignatures"`
ECDSA bool `json:"ecdsa"`
}
// EncryptedPrivateKey represents an encrypted private key
type EncryptedPrivateKey struct {
cipher []byte
salt []byte
}
// Data holds all the data related to the wallet keys
type Data struct {
encryptedPrivateKeys []*EncryptedPrivateKey
PublicKeys [][]byte
MinimumSignatures uint32
ECDSA bool
}
func (d *Data) toJSON() *keysFileJSON {
encryptedPrivateKeysJSON := make([]*encryptedPrivateKeyJSON, len(d.encryptedPrivateKeys))
for i, encryptedPrivateKey := range d.encryptedPrivateKeys {
encryptedPrivateKeysJSON[i] = &encryptedPrivateKeyJSON{
Cipher: hex.EncodeToString(encryptedPrivateKey.cipher),
Salt: hex.EncodeToString(encryptedPrivateKey.salt),
}
}
publicKeysHex := make([]string, len(d.PublicKeys))
for i, publicKey := range d.PublicKeys {
publicKeysHex[i] = hex.EncodeToString(publicKey)
}
return &keysFileJSON{
EncryptedPrivateKeys: encryptedPrivateKeysJSON,
PublicKeys: publicKeysHex,
MinimumSignatures: d.MinimumSignatures,
ECDSA: d.ECDSA,
}
}
func (d *Data) fromJSON(fileJSON *keysFileJSON) error {
d.MinimumSignatures = fileJSON.MinimumSignatures
d.ECDSA = fileJSON.ECDSA
d.encryptedPrivateKeys = make([]*EncryptedPrivateKey, len(fileJSON.EncryptedPrivateKeys))
for i, encryptedPrivateKeyJSON := range fileJSON.EncryptedPrivateKeys {
cipher, err := hex.DecodeString(encryptedPrivateKeyJSON.Cipher)
if err != nil {
return err
}
salt, err := hex.DecodeString(encryptedPrivateKeyJSON.Salt)
if err != nil {
return err
}
d.encryptedPrivateKeys[i] = &EncryptedPrivateKey{
cipher: cipher,
salt: salt,
}
}
d.PublicKeys = make([][]byte, len(fileJSON.PublicKeys))
for i, publicKey := range fileJSON.PublicKeys {
var err error
d.PublicKeys[i], err = hex.DecodeString(publicKey)
if err != nil {
return err
}
}
return nil
}
// DecryptPrivateKeys asks the user to enter the password for the private keys and
// returns the decrypted private keys.
func (d *Data) DecryptPrivateKeys() ([][]byte, error) {
password := getPassword("Password:")
privateKeys := make([][]byte, len(d.encryptedPrivateKeys))
for i, encryptedPrivateKey := range d.encryptedPrivateKeys {
var err error
privateKeys[i], err = decryptPrivateKey(encryptedPrivateKey, password)
if err != nil {
return nil, err
}
}
return privateKeys, nil
}
// ReadKeysFile returns the data related to the keys file
func ReadKeysFile(netParams *dagconfig.Params, path string) (*Data, error) {
if path == "" {
path = defaultKeysFile(netParams)
}
file, err := os.Open(path)
if err != nil {
return nil, err
}
decoder := json.NewDecoder(file)
decoder.DisallowUnknownFields()
decodedFile := &keysFileJSON{}
err = decoder.Decode(&decodedFile)
if err != nil {
return nil, err
}
keysFile := &Data{}
err = keysFile.fromJSON(decodedFile)
if err != nil {
return nil, err
}
return keysFile, nil
}
func createFileDirectoryIfDoesntExist(path string) error {
dir := filepath.Dir(path)
exists, err := pathExists(dir)
if err != nil {
return err
}
if exists {
return nil
}
return os.MkdirAll(dir, 0700)
}
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
// WriteKeysFile writes a keys file with the given data
func WriteKeysFile(netParams *dagconfig.Params, path string, encryptedPrivateKeys []*EncryptedPrivateKey,
publicKeys [][]byte, minimumSignatures uint32, ecdsa bool) error {
if path == "" {
path = defaultKeysFile(netParams)
}
exists, err := pathExists(path)
if err != nil {
return err
}
if exists {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("The file %s already exists. Are you sure you want to override it (type 'y' to approve)? ", path)
line, _, err := reader.ReadLine()
if err != nil {
return err
}
if string(line) != "y" {
return errors.Errorf("aborted keys file creation")
}
}
err = createFileDirectoryIfDoesntExist(path)
if err != nil {
return err
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer file.Close()
keysFile := &Data{
encryptedPrivateKeys: encryptedPrivateKeys,
PublicKeys: publicKeys,
MinimumSignatures: minimumSignatures,
ECDSA: ecdsa,
}
encoder := json.NewEncoder(file)
err = encoder.Encode(keysFile.toJSON())
if err != nil {
return err
}
fmt.Printf("Wrote the keys into %s\n", path)
return nil
}
func getAEAD(password, salt []byte) (cipher.AEAD, error) {
key := argon2.IDKey(password, salt, 1, 64*1024, uint8(runtime.NumCPU()), 32)
return chacha20poly1305.NewX(key)
}
func decryptPrivateKey(encryptedPrivateKey *EncryptedPrivateKey, password []byte) ([]byte, error) {
aead, err := getAEAD(password, encryptedPrivateKey.salt)
if err != nil {
return nil, err
}
if len(encryptedPrivateKey.cipher) < aead.NonceSize() {
return nil, errors.New("ciphertext too short")
}
// Split nonce and ciphertext.
nonce, ciphertext := encryptedPrivateKey.cipher[:aead.NonceSize()], encryptedPrivateKey.cipher[aead.NonceSize():]
// Decrypt the message and check it wasn't tampered with.
return aead.Open(nil, nonce, ciphertext, nil)
}

View File

@@ -0,0 +1,93 @@
package libkaspawallet
import (
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
// CreateKeyPair generates a private-public key pair
func CreateKeyPair(ecdsa bool) ([]byte, []byte, error) {
if ecdsa {
return createKeyPairECDSA()
}
return createKeyPair()
}
func createKeyPair() ([]byte, []byte, error) {
keyPair, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate private key")
}
publicKey, err := keyPair.SchnorrPublicKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to serialize public key")
}
return keyPair.SerializePrivateKey()[:], publicKeySerialized[:], nil
}
func createKeyPairECDSA() ([]byte, []byte, error) {
keyPair, err := secp256k1.GenerateECDSAPrivateKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate private key")
}
publicKey, err := keyPair.ECDSAPublicKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to serialize public key")
}
return keyPair.Serialize()[:], publicKeySerialized[:], nil
}
// PublicKeyFromPrivateKey returns the public key associated with a private key
func PublicKeyFromPrivateKey(privateKeyBytes []byte) ([]byte, error) {
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKeyBytes)
if err != nil {
return nil, errors.Wrap(err, "Failed to deserialize private key")
}
publicKey, err := keyPair.SchnorrPublicKey()
if err != nil {
return nil, errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return nil, errors.Wrap(err, "Failed to serialize public key")
}
return publicKeySerialized[:], nil
}
// Address returns the address associated with the given public keys and minimum signatures parameters.
func Address(params *dagconfig.Params, pubKeys [][]byte, minimumSignatures uint32, ecdsa bool) (util.Address, error) {
sortPublicKeys(pubKeys)
if uint32(len(pubKeys)) < minimumSignatures {
return nil, errors.Errorf("The minimum amount of signatures (%d) is greater than the amount of "+
"provided public keys (%d)", minimumSignatures, len(pubKeys))
}
if len(pubKeys) == 1 {
if ecdsa {
return util.NewAddressPublicKeyECDSA(pubKeys[0][:], params.Prefix)
}
return util.NewAddressPublicKey(pubKeys[0][:], params.Prefix)
}
redeemScript, err := multiSigRedeemScript(pubKeys, minimumSignatures, ecdsa)
if err != nil {
return nil, err
}
return util.NewAddressScriptHash(redeemScript, params.Prefix)
}

View File

@@ -0,0 +1,3 @@
//go:generate protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative wallet.proto
package protoserialization

View File

@@ -0,0 +1,915 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.12.3
// source: wallet.proto
package protoserialization
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
type PartiallySignedTransaction struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Tx *TransactionMessage `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"`
PartiallySignedInputs []*PartiallySignedInput `protobuf:"bytes,2,rep,name=partiallySignedInputs,proto3" json:"partiallySignedInputs,omitempty"`
}
func (x *PartiallySignedTransaction) Reset() {
*x = PartiallySignedTransaction{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PartiallySignedTransaction) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PartiallySignedTransaction) ProtoMessage() {}
func (x *PartiallySignedTransaction) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PartiallySignedTransaction.ProtoReflect.Descriptor instead.
func (*PartiallySignedTransaction) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{0}
}
func (x *PartiallySignedTransaction) GetTx() *TransactionMessage {
if x != nil {
return x.Tx
}
return nil
}
func (x *PartiallySignedTransaction) GetPartiallySignedInputs() []*PartiallySignedInput {
if x != nil {
return x.PartiallySignedInputs
}
return nil
}
type PartiallySignedInput struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RedeemScript []byte `protobuf:"bytes,1,opt,name=redeemScript,proto3" json:"redeemScript,omitempty"`
PrevOutput *TransactionOutput `protobuf:"bytes,2,opt,name=prevOutput,proto3" json:"prevOutput,omitempty"`
MinimumSignatures uint32 `protobuf:"varint,3,opt,name=minimumSignatures,proto3" json:"minimumSignatures,omitempty"`
PubKeySignaturePairs []*PubKeySignaturePair `protobuf:"bytes,4,rep,name=pubKeySignaturePairs,proto3" json:"pubKeySignaturePairs,omitempty"`
}
func (x *PartiallySignedInput) Reset() {
*x = PartiallySignedInput{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PartiallySignedInput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PartiallySignedInput) ProtoMessage() {}
func (x *PartiallySignedInput) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PartiallySignedInput.ProtoReflect.Descriptor instead.
func (*PartiallySignedInput) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{1}
}
func (x *PartiallySignedInput) GetRedeemScript() []byte {
if x != nil {
return x.RedeemScript
}
return nil
}
func (x *PartiallySignedInput) GetPrevOutput() *TransactionOutput {
if x != nil {
return x.PrevOutput
}
return nil
}
func (x *PartiallySignedInput) GetMinimumSignatures() uint32 {
if x != nil {
return x.MinimumSignatures
}
return 0
}
func (x *PartiallySignedInput) GetPubKeySignaturePairs() []*PubKeySignaturePair {
if x != nil {
return x.PubKeySignaturePairs
}
return nil
}
type PubKeySignaturePair struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PubKey []byte `protobuf:"bytes,1,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
}
func (x *PubKeySignaturePair) Reset() {
*x = PubKeySignaturePair{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PubKeySignaturePair) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PubKeySignaturePair) ProtoMessage() {}
func (x *PubKeySignaturePair) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PubKeySignaturePair.ProtoReflect.Descriptor instead.
func (*PubKeySignaturePair) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{2}
}
func (x *PubKeySignaturePair) GetPubKey() []byte {
if x != nil {
return x.PubKey
}
return nil
}
func (x *PubKeySignaturePair) GetSignature() []byte {
if x != nil {
return x.Signature
}
return nil
}
type SubnetworkId struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"`
}
func (x *SubnetworkId) Reset() {
*x = SubnetworkId{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubnetworkId) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubnetworkId) ProtoMessage() {}
func (x *SubnetworkId) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubnetworkId.ProtoReflect.Descriptor instead.
func (*SubnetworkId) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{3}
}
func (x *SubnetworkId) GetBytes() []byte {
if x != nil {
return x.Bytes
}
return nil
}
type TransactionMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
Inputs []*TransactionInput `protobuf:"bytes,2,rep,name=inputs,proto3" json:"inputs,omitempty"`
Outputs []*TransactionOutput `protobuf:"bytes,3,rep,name=outputs,proto3" json:"outputs,omitempty"`
LockTime uint64 `protobuf:"varint,4,opt,name=lockTime,proto3" json:"lockTime,omitempty"`
SubnetworkId *SubnetworkId `protobuf:"bytes,5,opt,name=subnetworkId,proto3" json:"subnetworkId,omitempty"`
Gas uint64 `protobuf:"varint,6,opt,name=gas,proto3" json:"gas,omitempty"`
Payload []byte `protobuf:"bytes,8,opt,name=payload,proto3" json:"payload,omitempty"`
}
func (x *TransactionMessage) Reset() {
*x = TransactionMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionMessage) ProtoMessage() {}
func (x *TransactionMessage) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionMessage.ProtoReflect.Descriptor instead.
func (*TransactionMessage) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{4}
}
func (x *TransactionMessage) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *TransactionMessage) GetInputs() []*TransactionInput {
if x != nil {
return x.Inputs
}
return nil
}
func (x *TransactionMessage) GetOutputs() []*TransactionOutput {
if x != nil {
return x.Outputs
}
return nil
}
func (x *TransactionMessage) GetLockTime() uint64 {
if x != nil {
return x.LockTime
}
return 0
}
func (x *TransactionMessage) GetSubnetworkId() *SubnetworkId {
if x != nil {
return x.SubnetworkId
}
return nil
}
func (x *TransactionMessage) GetGas() uint64 {
if x != nil {
return x.Gas
}
return 0
}
func (x *TransactionMessage) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
type TransactionInput struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PreviousOutpoint *Outpoint `protobuf:"bytes,1,opt,name=previousOutpoint,proto3" json:"previousOutpoint,omitempty"`
SignatureScript []byte `protobuf:"bytes,2,opt,name=signatureScript,proto3" json:"signatureScript,omitempty"`
Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"`
}
func (x *TransactionInput) Reset() {
*x = TransactionInput{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionInput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionInput) ProtoMessage() {}
func (x *TransactionInput) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionInput.ProtoReflect.Descriptor instead.
func (*TransactionInput) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{5}
}
func (x *TransactionInput) GetPreviousOutpoint() *Outpoint {
if x != nil {
return x.PreviousOutpoint
}
return nil
}
func (x *TransactionInput) GetSignatureScript() []byte {
if x != nil {
return x.SignatureScript
}
return nil
}
func (x *TransactionInput) GetSequence() uint64 {
if x != nil {
return x.Sequence
}
return 0
}
type Outpoint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
TransactionId *TransactionId `protobuf:"bytes,1,opt,name=transactionId,proto3" json:"transactionId,omitempty"`
Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"`
}
func (x *Outpoint) Reset() {
*x = Outpoint{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Outpoint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Outpoint) ProtoMessage() {}
func (x *Outpoint) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Outpoint.ProtoReflect.Descriptor instead.
func (*Outpoint) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{6}
}
func (x *Outpoint) GetTransactionId() *TransactionId {
if x != nil {
return x.TransactionId
}
return nil
}
func (x *Outpoint) GetIndex() uint32 {
if x != nil {
return x.Index
}
return 0
}
type TransactionId struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"`
}
func (x *TransactionId) Reset() {
*x = TransactionId{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionId) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionId) ProtoMessage() {}
func (x *TransactionId) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionId.ProtoReflect.Descriptor instead.
func (*TransactionId) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{7}
}
func (x *TransactionId) GetBytes() []byte {
if x != nil {
return x.Bytes
}
return nil
}
type ScriptPublicKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Script []byte `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"`
Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
}
func (x *ScriptPublicKey) Reset() {
*x = ScriptPublicKey{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ScriptPublicKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ScriptPublicKey) ProtoMessage() {}
func (x *ScriptPublicKey) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ScriptPublicKey.ProtoReflect.Descriptor instead.
func (*ScriptPublicKey) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{8}
}
func (x *ScriptPublicKey) GetScript() []byte {
if x != nil {
return x.Script
}
return nil
}
func (x *ScriptPublicKey) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
type TransactionOutput struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
ScriptPublicKey *ScriptPublicKey `protobuf:"bytes,2,opt,name=scriptPublicKey,proto3" json:"scriptPublicKey,omitempty"`
}
func (x *TransactionOutput) Reset() {
*x = TransactionOutput{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionOutput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionOutput) ProtoMessage() {}
func (x *TransactionOutput) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionOutput.ProtoReflect.Descriptor instead.
func (*TransactionOutput) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{9}
}
func (x *TransactionOutput) GetValue() uint64 {
if x != nil {
return x.Value
}
return 0
}
func (x *TransactionOutput) GetScriptPublicKey() *ScriptPublicKey {
if x != nil {
return x.ScriptPublicKey
}
return nil
}
var File_wallet_proto protoreflect.FileDescriptor
var file_wallet_proto_rawDesc = []byte{
0x0a, 0x0c, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x22, 0xb4, 0x01, 0x0a, 0x1a, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79,
0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x36, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x02, 0x74, 0x78, 0x12, 0x5e, 0x0a, 0x15, 0x70, 0x61, 0x72,
0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70, 0x75,
0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x61,
0x72, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x52, 0x15, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x67,
0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x22, 0x8c, 0x02, 0x0a, 0x14, 0x50, 0x61,
0x72, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x53, 0x63, 0x72, 0x69,
0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d,
0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x45, 0x0a, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x4f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x2c, 0x0a,
0x11, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75,
0x6d, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x5b, 0x0a, 0x14, 0x70,
0x75, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x61,
0x69, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50,
0x75, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x61,
0x69, 0x72, 0x52, 0x14, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74,
0x75, 0x72, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x22, 0x4b, 0x0a, 0x13, 0x50, 0x75, 0x62, 0x4b,
0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12,
0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61,
0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e,
0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x24, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77,
0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0xbb, 0x02, 0x0a, 0x12,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x06,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3f, 0x0a, 0x07, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c,
0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6c,
0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x52,
0x0c, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x10, 0x0a,
0x03, 0x67, 0x61, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x67, 0x61, 0x73, 0x12,
0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x10, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x48,
0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x75,
0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73,
0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x69, 0x67, 0x6e,
0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x63, 0x72, 0x69,
0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x69,
0x0a, 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x0d, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69,
0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x49, 0x64, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x25, 0x0a, 0x0d, 0x54, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79,
0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73,
0x22, 0x43, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x12, 0x4d, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53,
0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0f,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42,
0x5c, 0x5a, 0x5a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61,
0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63, 0x6d,
0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x6c, 0x69,
0x62, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72,
0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_wallet_proto_rawDescOnce sync.Once
file_wallet_proto_rawDescData = file_wallet_proto_rawDesc
)
func file_wallet_proto_rawDescGZIP() []byte {
file_wallet_proto_rawDescOnce.Do(func() {
file_wallet_proto_rawDescData = protoimpl.X.CompressGZIP(file_wallet_proto_rawDescData)
})
return file_wallet_proto_rawDescData
}
var file_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_wallet_proto_goTypes = []interface{}{
(*PartiallySignedTransaction)(nil), // 0: protoserialization.PartiallySignedTransaction
(*PartiallySignedInput)(nil), // 1: protoserialization.PartiallySignedInput
(*PubKeySignaturePair)(nil), // 2: protoserialization.PubKeySignaturePair
(*SubnetworkId)(nil), // 3: protoserialization.SubnetworkId
(*TransactionMessage)(nil), // 4: protoserialization.TransactionMessage
(*TransactionInput)(nil), // 5: protoserialization.TransactionInput
(*Outpoint)(nil), // 6: protoserialization.Outpoint
(*TransactionId)(nil), // 7: protoserialization.TransactionId
(*ScriptPublicKey)(nil), // 8: protoserialization.ScriptPublicKey
(*TransactionOutput)(nil), // 9: protoserialization.TransactionOutput
}
var file_wallet_proto_depIdxs = []int32{
4, // 0: protoserialization.PartiallySignedTransaction.tx:type_name -> protoserialization.TransactionMessage
1, // 1: protoserialization.PartiallySignedTransaction.partiallySignedInputs:type_name -> protoserialization.PartiallySignedInput
9, // 2: protoserialization.PartiallySignedInput.prevOutput:type_name -> protoserialization.TransactionOutput
2, // 3: protoserialization.PartiallySignedInput.pubKeySignaturePairs:type_name -> protoserialization.PubKeySignaturePair
5, // 4: protoserialization.TransactionMessage.inputs:type_name -> protoserialization.TransactionInput
9, // 5: protoserialization.TransactionMessage.outputs:type_name -> protoserialization.TransactionOutput
3, // 6: protoserialization.TransactionMessage.subnetworkId:type_name -> protoserialization.SubnetworkId
6, // 7: protoserialization.TransactionInput.previousOutpoint:type_name -> protoserialization.Outpoint
7, // 8: protoserialization.Outpoint.transactionId:type_name -> protoserialization.TransactionId
8, // 9: protoserialization.TransactionOutput.scriptPublicKey:type_name -> protoserialization.ScriptPublicKey
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
}
func init() { file_wallet_proto_init() }
func file_wallet_proto_init() {
if File_wallet_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_wallet_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PartiallySignedTransaction); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PartiallySignedInput); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PubKeySignaturePair); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubnetworkId); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionInput); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Outpoint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionId); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ScriptPublicKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionOutput); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_wallet_proto_rawDesc,
NumEnums: 0,
NumMessages: 10,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_wallet_proto_goTypes,
DependencyIndexes: file_wallet_proto_depIdxs,
MessageInfos: file_wallet_proto_msgTypes,
}.Build()
File_wallet_proto = out.File
file_wallet_proto_rawDesc = nil
file_wallet_proto_goTypes = nil
file_wallet_proto_depIdxs = nil
}

View File

@@ -0,0 +1,59 @@
syntax = "proto3";
package protoserialization;
option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization/protoserialization";
message PartiallySignedTransaction{
TransactionMessage tx = 1;
repeated PartiallySignedInput partiallySignedInputs = 2;
}
message PartiallySignedInput{
bytes redeemScript = 1;
TransactionOutput prevOutput = 2;
uint32 minimumSignatures = 3;
repeated PubKeySignaturePair pubKeySignaturePairs = 4;
}
message PubKeySignaturePair{
bytes pubKey = 1;
bytes signature = 2;
}
message SubnetworkId{
bytes bytes = 1;
}
message TransactionMessage{
uint32 version = 1;
repeated TransactionInput inputs = 2;
repeated TransactionOutput outputs = 3;
uint64 lockTime = 4;
SubnetworkId subnetworkId = 5;
uint64 gas = 6;
bytes payload = 8;
}
message TransactionInput{
Outpoint previousOutpoint = 1;
bytes signatureScript = 2;
uint64 sequence = 3;
}
message Outpoint{
TransactionId transactionId = 1;
uint32 index = 2;
}
message TransactionId{
bytes bytes = 1;
}
message ScriptPublicKey {
bytes script = 1;
uint32 version = 2;
}
message TransactionOutput{
uint64 value = 1;
ScriptPublicKey scriptPublicKey = 2;
}

View File

@@ -0,0 +1,273 @@
package serialization
import (
"github.com/golang/protobuf/proto"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization/protoserialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/pkg/errors"
"math"
)
// PartiallySignedTransaction is a type that is intended
// to be transferred between multiple parties so each
// party will be able to sign the transaction before
// it's fully signed.
type PartiallySignedTransaction struct {
Tx *externalapi.DomainTransaction
PartiallySignedInputs []*PartiallySignedInput
}
// PartiallySignedInput represents an input signed
// only by some of the relevant parties.
type PartiallySignedInput struct {
RedeeemScript []byte
PrevOutput *externalapi.DomainTransactionOutput
MinimumSignatures uint32
PubKeySignaturePairs []*PubKeySignaturePair
}
// PubKeySignaturePair is a pair of public key and (potentially) its associated signature
type PubKeySignaturePair struct {
PubKey []byte
Signature []byte
}
// DeserializePartiallySignedTransaction deserializes a byte slice into PartiallySignedTransaction.
func DeserializePartiallySignedTransaction(serializedPartiallySignedTransaction []byte) (*PartiallySignedTransaction, error) {
protoPartiallySignedTransaction := &protoserialization.PartiallySignedTransaction{}
err := proto.Unmarshal(serializedPartiallySignedTransaction, protoPartiallySignedTransaction)
if err != nil {
return nil, err
}
return partiallySignedTransactionFromProto(protoPartiallySignedTransaction)
}
// SerializePartiallySignedTransaction serializes a PartiallySignedTransaction.
func SerializePartiallySignedTransaction(partiallySignedTransaction *PartiallySignedTransaction) ([]byte, error) {
return proto.Marshal(partiallySignedTransactionToProto(partiallySignedTransaction))
}
func partiallySignedTransactionFromProto(protoPartiallySignedTransaction *protoserialization.PartiallySignedTransaction) (*PartiallySignedTransaction, error) {
tx, err := transactionFromProto(protoPartiallySignedTransaction.Tx)
if err != nil {
return nil, err
}
inputs := make([]*PartiallySignedInput, len(protoPartiallySignedTransaction.PartiallySignedInputs))
for i, protoInput := range protoPartiallySignedTransaction.PartiallySignedInputs {
inputs[i], err = partiallySignedInputFromProto(protoInput)
if err != nil {
return nil, err
}
}
return &PartiallySignedTransaction{
Tx: tx,
PartiallySignedInputs: inputs,
}, nil
}
func partiallySignedTransactionToProto(partiallySignedTransaction *PartiallySignedTransaction) *protoserialization.PartiallySignedTransaction {
protoInputs := make([]*protoserialization.PartiallySignedInput, len(partiallySignedTransaction.PartiallySignedInputs))
for i, input := range partiallySignedTransaction.PartiallySignedInputs {
protoInputs[i] = partiallySignedInputToProto(input)
}
return &protoserialization.PartiallySignedTransaction{
Tx: transactionToProto(partiallySignedTransaction.Tx),
PartiallySignedInputs: protoInputs,
}
}
func partiallySignedInputFromProto(protoPartiallySignedInput *protoserialization.PartiallySignedInput) (*PartiallySignedInput, error) {
output, err := transactionOutputFromProto(protoPartiallySignedInput.PrevOutput)
if err != nil {
return nil, err
}
pubKeySignaturePairs := make([]*PubKeySignaturePair, len(protoPartiallySignedInput.PubKeySignaturePairs))
for i, protoPair := range protoPartiallySignedInput.PubKeySignaturePairs {
pubKeySignaturePairs[i] = pubKeySignaturePairFromProto(protoPair)
}
return &PartiallySignedInput{
RedeeemScript: protoPartiallySignedInput.RedeemScript,
PrevOutput: output,
MinimumSignatures: protoPartiallySignedInput.MinimumSignatures,
PubKeySignaturePairs: pubKeySignaturePairs,
}, nil
}
func partiallySignedInputToProto(partiallySignedInput *PartiallySignedInput) *protoserialization.PartiallySignedInput {
protoPairs := make([]*protoserialization.PubKeySignaturePair, len(partiallySignedInput.PubKeySignaturePairs))
for i, pair := range partiallySignedInput.PubKeySignaturePairs {
protoPairs[i] = pubKeySignaturePairToProto(pair)
}
return &protoserialization.PartiallySignedInput{
RedeemScript: partiallySignedInput.RedeeemScript,
PrevOutput: transactionOutputToProto(partiallySignedInput.PrevOutput),
MinimumSignatures: partiallySignedInput.MinimumSignatures,
PubKeySignaturePairs: protoPairs,
}
}
func pubKeySignaturePairFromProto(protoPubKeySignaturePair *protoserialization.PubKeySignaturePair) *PubKeySignaturePair {
return &PubKeySignaturePair{
PubKey: protoPubKeySignaturePair.PubKey,
Signature: protoPubKeySignaturePair.Signature,
}
}
func pubKeySignaturePairToProto(pubKeySignaturePair *PubKeySignaturePair) *protoserialization.PubKeySignaturePair {
return &protoserialization.PubKeySignaturePair{
PubKey: pubKeySignaturePair.PubKey,
Signature: pubKeySignaturePair.Signature,
}
}
func transactionFromProto(protoTransaction *protoserialization.TransactionMessage) (*externalapi.DomainTransaction, error) {
if protoTransaction.Version > math.MaxUint16 {
return nil, errors.Errorf("protoTransaction.Version is %d and is too big to be a uint16", protoTransaction.Version)
}
inputs := make([]*externalapi.DomainTransactionInput, len(protoTransaction.Inputs))
for i, protoInput := range protoTransaction.Inputs {
var err error
inputs[i], err = transactionInputFromProto(protoInput)
if err != nil {
return nil, err
}
}
outputs := make([]*externalapi.DomainTransactionOutput, len(protoTransaction.Outputs))
for i, protoOutput := range protoTransaction.Outputs {
var err error
outputs[i], err = transactionOutputFromProto(protoOutput)
if err != nil {
return nil, err
}
}
subnetworkID, err := subnetworks.FromBytes(protoTransaction.SubnetworkId.Bytes)
if err != nil {
return nil, err
}
return &externalapi.DomainTransaction{
Version: uint16(protoTransaction.Version),
Inputs: inputs,
Outputs: outputs,
LockTime: protoTransaction.LockTime,
SubnetworkID: *subnetworkID,
Gas: protoTransaction.Gas,
Payload: protoTransaction.Payload,
}, nil
}
func transactionToProto(tx *externalapi.DomainTransaction) *protoserialization.TransactionMessage {
protoInputs := make([]*protoserialization.TransactionInput, len(tx.Inputs))
for i, input := range tx.Inputs {
protoInputs[i] = transactionInputToProto(input)
}
protoOutputs := make([]*protoserialization.TransactionOutput, len(tx.Outputs))
for i, output := range tx.Outputs {
protoOutputs[i] = transactionOutputToProto(output)
}
return &protoserialization.TransactionMessage{
Version: uint32(tx.Version),
Inputs: protoInputs,
Outputs: protoOutputs,
LockTime: tx.LockTime,
SubnetworkId: &protoserialization.SubnetworkId{Bytes: tx.SubnetworkID[:]},
Gas: tx.Gas,
Payload: tx.Payload,
}
}
func transactionInputFromProto(protoInput *protoserialization.TransactionInput) (*externalapi.DomainTransactionInput, error) {
outpoint, err := outpointFromProto(protoInput.PreviousOutpoint)
if err != nil {
return nil, err
}
return &externalapi.DomainTransactionInput{
PreviousOutpoint: *outpoint,
SignatureScript: protoInput.SignatureScript,
Sequence: protoInput.Sequence,
}, nil
}
func transactionInputToProto(input *externalapi.DomainTransactionInput) *protoserialization.TransactionInput {
return &protoserialization.TransactionInput{
PreviousOutpoint: outpointToProto(&input.PreviousOutpoint),
SignatureScript: input.SignatureScript,
Sequence: input.Sequence,
}
}
func outpointFromProto(protoOutpoint *protoserialization.Outpoint) (*externalapi.DomainOutpoint, error) {
txID, err := transactionIDFromProto(protoOutpoint.TransactionId)
if err != nil {
return nil, err
}
return &externalapi.DomainOutpoint{
TransactionID: *txID,
Index: protoOutpoint.Index,
}, nil
}
func outpointToProto(outpoint *externalapi.DomainOutpoint) *protoserialization.Outpoint {
return &protoserialization.Outpoint{
TransactionId: &protoserialization.TransactionId{Bytes: outpoint.TransactionID.ByteSlice()},
Index: outpoint.Index,
}
}
func transactionIDFromProto(protoTxID *protoserialization.TransactionId) (*externalapi.DomainTransactionID, error) {
if protoTxID == nil {
return nil, errors.Errorf("protoTxID is nil")
}
return externalapi.NewDomainTransactionIDFromByteSlice(protoTxID.Bytes)
}
func transactionOutputFromProto(protoOutput *protoserialization.TransactionOutput) (*externalapi.DomainTransactionOutput, error) {
scriptPublicKey, err := scriptPublicKeyFromProto(protoOutput.ScriptPublicKey)
if err != nil {
return nil, err
}
return &externalapi.DomainTransactionOutput{
Value: protoOutput.Value,
ScriptPublicKey: scriptPublicKey,
}, nil
}
func transactionOutputToProto(output *externalapi.DomainTransactionOutput) *protoserialization.TransactionOutput {
return &protoserialization.TransactionOutput{
Value: output.Value,
ScriptPublicKey: scriptPublicKeyToProto(output.ScriptPublicKey),
}
}
func scriptPublicKeyFromProto(protoScriptPublicKey *protoserialization.ScriptPublicKey) (*externalapi.ScriptPublicKey, error) {
if protoScriptPublicKey.Version > math.MaxUint16 {
return nil, errors.Errorf("protoOutput.ScriptPublicKey.Version is %d and is too big to be a uint16", protoScriptPublicKey.Version)
}
return &externalapi.ScriptPublicKey{
Script: protoScriptPublicKey.Script,
Version: uint16(protoScriptPublicKey.Version),
}, nil
}
func scriptPublicKeyToProto(scriptPublicKey *externalapi.ScriptPublicKey) *protoserialization.ScriptPublicKey {
return &protoserialization.ScriptPublicKey{
Script: scriptPublicKey.Script,
Version: uint32(scriptPublicKey.Version),
}
}

View File

@@ -0,0 +1,146 @@
package libkaspawallet
import (
"bytes"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/pkg/errors"
)
type signer interface {
rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error)
serializedPublicKey() ([]byte, error)
}
type schnorrSigner secp256k1.SchnorrKeyPair
func (s *schnorrSigner) rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
return txscript.RawTxInSignature(tx, idx, hashType, (*secp256k1.SchnorrKeyPair)(s), sighashReusedValues)
}
func (s *schnorrSigner) serializedPublicKey() ([]byte, error) {
publicKey, err := (*secp256k1.SchnorrKeyPair)(s).SchnorrPublicKey()
if err != nil {
return nil, err
}
serializedPublicKey, err := publicKey.Serialize()
if err != nil {
return nil, err
}
return serializedPublicKey[:], nil
}
type ecdsaSigner secp256k1.ECDSAPrivateKey
func (e *ecdsaSigner) rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
return txscript.RawTxInSignatureECDSA(tx, idx, hashType, (*secp256k1.ECDSAPrivateKey)(e), sighashReusedValues)
}
func (e *ecdsaSigner) serializedPublicKey() ([]byte, error) {
publicKey, err := (*secp256k1.ECDSAPrivateKey)(e).ECDSAPublicKey()
if err != nil {
return nil, err
}
serializedPublicKey, err := publicKey.Serialize()
if err != nil {
return nil, err
}
return serializedPublicKey[:], nil
}
func deserializeECDSAPrivateKey(privateKey []byte, ecdsa bool) (signer, error) {
if ecdsa {
keyPair, err := secp256k1.DeserializeECDSAPrivateKeyFromSlice(privateKey)
if err != nil {
return nil, errors.Wrap(err, "Error deserializing private key")
}
return (*ecdsaSigner)(keyPair), nil
}
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKey)
if err != nil {
return nil, errors.Wrap(err, "Error deserializing private key")
}
return (*schnorrSigner)(keyPair), nil
}
// Sign signs the transaction with the given private keys
func Sign(privateKeys [][]byte, serializedPSTx []byte, ecdsa bool) ([]byte, error) {
keyPairs := make([]signer, len(privateKeys))
for i, privateKey := range privateKeys {
var err error
keyPairs[i], err = deserializeECDSAPrivateKey(privateKey, ecdsa)
if err != nil {
return nil, errors.Wrap(err, "Error deserializing private key")
}
}
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(serializedPSTx)
if err != nil {
return nil, err
}
for _, keyPair := range keyPairs {
err = sign(keyPair, partiallySignedTransaction)
if err != nil {
return nil, err
}
}
return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
}
func sign(keyPair signer, psTx *serialization.PartiallySignedTransaction) error {
if isTransactionFullySigned(psTx) {
return nil
}
serializedPublicKey, err := keyPair.serializedPublicKey()
if err != nil {
return err
}
sighashReusedValues := &consensushashing.SighashReusedValues{}
for i, partiallySignedInput := range psTx.PartiallySignedInputs {
prevOut := partiallySignedInput.PrevOutput
psTx.Tx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(
prevOut.Value,
prevOut.ScriptPublicKey,
false, // This is a fake value, because it's irrelevant for the signature
0, // This is a fake value, because it's irrelevant for the signature
)
}
signed := false
for i, partiallySignedInput := range psTx.PartiallySignedInputs {
for _, pair := range partiallySignedInput.PubKeySignaturePairs {
if bytes.Equal(pair.PubKey, serializedPublicKey[:]) {
pair.Signature, err = keyPair.rawTxInSignature(psTx.Tx, i, consensushashing.SigHashAll, sighashReusedValues)
if err != nil {
return err
}
signed = true
}
}
}
if !signed {
return errors.Errorf("Public key doesn't match any of the transaction public keys")
}
return nil
}

View File

@@ -0,0 +1,206 @@
package libkaspawallet
import (
"bytes"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"sort"
)
// Payment contains a recipient payment details
type Payment struct {
Address util.Address
Amount uint64
}
func sortPublicKeys(publicKeys [][]byte) {
sort.Slice(publicKeys, func(i, j int) bool {
return bytes.Compare(publicKeys[i], publicKeys[j]) < 0
})
}
// CreateUnsignedTransaction creates an unsigned transaction
func CreateUnsignedTransaction(
pubKeys [][]byte,
minimumSignatures uint32,
ecdsa bool,
payments []*Payment,
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair) ([]byte, error) {
sortPublicKeys(pubKeys)
unsignedTransaction, err := createUnsignedTransaction(pubKeys, minimumSignatures, ecdsa, payments, selectedUTXOs)
if err != nil {
return nil, err
}
return serialization.SerializePartiallySignedTransaction(unsignedTransaction)
}
func multiSigRedeemScript(pubKeys [][]byte, minimumSignatures uint32, ecdsa bool) ([]byte, error) {
scriptBuilder := txscript.NewScriptBuilder()
scriptBuilder.AddInt64(int64(minimumSignatures))
for _, key := range pubKeys {
scriptBuilder.AddData(key)
}
scriptBuilder.AddInt64(int64(len(pubKeys)))
if ecdsa {
scriptBuilder.AddOp(txscript.OpCheckMultiSigECDSA)
} else {
scriptBuilder.AddOp(txscript.OpCheckMultiSig)
}
return scriptBuilder.Script()
}
func createUnsignedTransaction(
pubKeys [][]byte,
minimumSignatures uint32,
ecdsa bool,
payments []*Payment,
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair) (*serialization.PartiallySignedTransaction, error) {
var redeemScript []byte
if len(pubKeys) > 1 {
var err error
redeemScript, err = multiSigRedeemScript(pubKeys, minimumSignatures, ecdsa)
if err != nil {
return nil, err
}
}
inputs := make([]*externalapi.DomainTransactionInput, len(selectedUTXOs))
partiallySignedInputs := make([]*serialization.PartiallySignedInput, len(selectedUTXOs))
for i, utxo := range selectedUTXOs {
emptyPubKeySignaturePairs := make([]*serialization.PubKeySignaturePair, len(pubKeys))
for i, pubKey := range pubKeys {
emptyPubKeySignaturePairs[i] = &serialization.PubKeySignaturePair{
PubKey: pubKey,
}
}
inputs[i] = &externalapi.DomainTransactionInput{PreviousOutpoint: *utxo.Outpoint}
partiallySignedInputs[i] = &serialization.PartiallySignedInput{
RedeeemScript: redeemScript,
PrevOutput: &externalapi.DomainTransactionOutput{
Value: utxo.UTXOEntry.Amount(),
ScriptPublicKey: utxo.UTXOEntry.ScriptPublicKey(),
},
MinimumSignatures: minimumSignatures,
PubKeySignaturePairs: emptyPubKeySignaturePairs,
}
}
outputs := make([]*externalapi.DomainTransactionOutput, len(payments))
for i, payment := range payments {
scriptPublicKey, err := txscript.PayToAddrScript(payment.Address)
if err != nil {
return nil, err
}
outputs[i] = &externalapi.DomainTransactionOutput{
Value: payment.Amount,
ScriptPublicKey: scriptPublicKey,
}
}
domainTransaction := &externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: inputs,
Outputs: outputs,
LockTime: 0,
SubnetworkID: subnetworks.SubnetworkIDNative,
Gas: 0,
Payload: nil,
}
return &serialization.PartiallySignedTransaction{
Tx: domainTransaction,
PartiallySignedInputs: partiallySignedInputs,
}, nil
}
// IsTransactionFullySigned returns whether the transaction is fully signed and ready to broadcast.
func IsTransactionFullySigned(psTxBytes []byte) (bool, error) {
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(psTxBytes)
if err != nil {
return false, err
}
return isTransactionFullySigned(partiallySignedTransaction), nil
}
func isTransactionFullySigned(psTx *serialization.PartiallySignedTransaction) bool {
for _, input := range psTx.PartiallySignedInputs {
numSignatures := 0
for _, pair := range input.PubKeySignaturePairs {
if pair.Signature != nil {
numSignatures++
}
}
if uint32(numSignatures) < input.MinimumSignatures {
return false
}
}
return true
}
// ExtractTransaction extracts a domain transaction from partially signed transaction after all of the
// relevant parties have signed it.
func ExtractTransaction(psTxBytes []byte) (*externalapi.DomainTransaction, error) {
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(psTxBytes)
if err != nil {
return nil, err
}
return extractTransaction(partiallySignedTransaction)
}
func extractTransaction(psTx *serialization.PartiallySignedTransaction) (*externalapi.DomainTransaction, error) {
for i, input := range psTx.PartiallySignedInputs {
isMultisig := input.RedeeemScript != nil
scriptBuilder := txscript.NewScriptBuilder()
if isMultisig {
signatureCount := 0
for _, pair := range input.PubKeySignaturePairs {
if pair.Signature != nil {
scriptBuilder.AddData(pair.Signature)
signatureCount++
}
}
if uint32(signatureCount) < input.MinimumSignatures {
return nil, errors.Errorf("missing %d signatures", input.MinimumSignatures-uint32(signatureCount))
}
scriptBuilder.AddData(input.RedeeemScript)
sigScript, err := scriptBuilder.Script()
if err != nil {
return nil, err
}
psTx.Tx.Inputs[i].SignatureScript = sigScript
} else {
if len(input.PubKeySignaturePairs) > 1 {
return nil, errors.Errorf("Cannot sign on P2PK when len(input.PubKeySignaturePairs) > 1")
}
if input.PubKeySignaturePairs[0].Signature == nil {
return nil, errors.Errorf("missing signature")
}
sigScript, err := txscript.NewScriptBuilder().
AddData(input.PubKeySignaturePairs[0].Signature).
Script()
if err != nil {
return nil, err
}
psTx.Tx.Inputs[i].SignatureScript = sigScript
}
}
return psTx.Tx, nil
}

View File

@@ -0,0 +1,290 @@
package libkaspawallet_test
import (
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"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/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/util"
"strings"
"testing"
)
func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
t.Run("schnorr", func(t *testing.T) {
testFunc(t, false)
})
t.Run("ecdsa", func(t *testing.T) {
testFunc(t, true)
})
}
func TestMultisig(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) {
consensusConfig.BlockCoinbaseMaturity = 0
tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
const numKeys = 3
privateKeys := make([][]byte, numKeys)
publicKeys := make([][]byte, numKeys)
for i := 0; i < numKeys; i++ {
privateKeys[i], publicKeys[i], err = libkaspawallet.CreateKeyPair(ecdsa)
if err != nil {
t.Fatalf("CreateKeyPair: %+v", err)
}
}
const minimumSignatures = 2
address, err := libkaspawallet.Address(&consensusConfig.Params, publicKeys, minimumSignatures, ecdsa)
if err != nil {
t.Fatalf("Address: %+v", err)
}
if _, ok := address.(*util.AddressScriptHash); !ok {
t.Fatalf("The address is of unexpected type")
}
scriptPublicKey, err := txscript.PayToAddrScript(address)
if err != nil {
t.Fatalf("PayToAddrScript: %+v", err)
}
coinbaseData := &externalapi.DomainCoinbaseData{
ScriptPublicKey: scriptPublicKey,
ExtraData: nil,
}
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block1, err := tc.GetBlock(block1Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
block1Tx := block1.Transactions[0]
block1TxOut := block1Tx.Outputs[0]
selectedUTXOs := []*externalapi.OutpointAndUTXOEntryPair{{
Outpoint: &externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
Index: 0,
},
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
}}
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, ecdsa,
[]*libkaspawallet.Payment{{
Address: address,
Amount: 10,
}}, selectedUTXOs)
if err != nil {
t.Fatalf("CreateUnsignedTransaction: %+v", err)
}
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
if err != nil {
t.Fatalf("IsTransactionFullySigned: %+v", err)
}
if isFullySigned {
t.Fatalf("Transaction is not expected to be signed")
}
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction)
if err == nil || !strings.Contains(err.Error(), fmt.Sprintf("missing %d signatures", minimumSignatures)) {
t.Fatal("Unexpectedly succeed to extract a valid transaction out of unsigned transaction")
}
signedTxStep1, err := libkaspawallet.Sign(privateKeys[:1], unsignedTransaction, ecdsa)
if err != nil {
t.Fatalf("IsTransactionFullySigned: %+v", err)
}
isFullySigned, err = libkaspawallet.IsTransactionFullySigned(signedTxStep1)
if err != nil {
t.Fatalf("IsTransactionFullySigned: %+v", err)
}
if isFullySigned {
t.Fatalf("Transaction is not expected to be fully signed")
}
signedTxStep2, err := libkaspawallet.Sign(privateKeys[1:2], signedTxStep1, ecdsa)
if err != nil {
t.Fatalf("Sign: %+v", err)
}
extractedSignedTxStep2, err := libkaspawallet.ExtractTransaction(signedTxStep2)
if err != nil {
t.Fatalf("ExtractTransaction: %+v", err)
}
signedTxOneStep, err := libkaspawallet.Sign(privateKeys[:2], unsignedTransaction, ecdsa)
if err != nil {
t.Fatalf("Sign: %+v", err)
}
extractedSignedTxOneStep, err := libkaspawallet.ExtractTransaction(signedTxOneStep)
if err != nil {
t.Fatalf("ExtractTransaction: %+v", err)
}
// We check IDs instead of comparing the actual transactions because the actual transactions have different
// signature scripts due to non deterministic signature scheme.
if !consensushashing.TransactionID(extractedSignedTxStep2).Equal(consensushashing.TransactionID(extractedSignedTxOneStep)) {
t.Fatalf("Expected extractedSignedTxOneStep and extractedSignedTxStep2 IDs to be equal")
}
_, insertionResult, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{extractedSignedTxStep2})
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
addedUTXO := &externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(extractedSignedTxStep2),
Index: 0,
}
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(addedUTXO) {
t.Fatalf("Transaction wasn't accepted in the DAG")
}
})
})
}
func TestP2PK(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) {
consensusConfig.BlockCoinbaseMaturity = 0
tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
const numKeys = 1
privateKeys := make([][]byte, numKeys)
publicKeys := make([][]byte, numKeys)
for i := 0; i < numKeys; i++ {
privateKeys[i], publicKeys[i], err = libkaspawallet.CreateKeyPair(ecdsa)
if err != nil {
t.Fatalf("CreateKeyPair: %+v", err)
}
}
const minimumSignatures = 1
address, err := libkaspawallet.Address(&consensusConfig.Params, publicKeys, minimumSignatures, ecdsa)
if err != nil {
t.Fatalf("Address: %+v", err)
}
if ecdsa {
if _, ok := address.(*util.AddressPublicKeyECDSA); !ok {
t.Fatalf("The address is of unexpected type")
}
} else {
if _, ok := address.(*util.AddressPublicKey); !ok {
t.Fatalf("The address is of unexpected type")
}
}
scriptPublicKey, err := txscript.PayToAddrScript(address)
if err != nil {
t.Fatalf("PayToAddrScript: %+v", err)
}
coinbaseData := &externalapi.DomainCoinbaseData{
ScriptPublicKey: scriptPublicKey,
ExtraData: nil,
}
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block1, err := tc.GetBlock(block1Hash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
block1Tx := block1.Transactions[0]
block1TxOut := block1Tx.Outputs[0]
selectedUTXOs := []*externalapi.OutpointAndUTXOEntryPair{{
Outpoint: &externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
Index: 0,
},
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
}}
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
ecdsa,
[]*libkaspawallet.Payment{{
Address: address,
Amount: 10,
}}, selectedUTXOs)
if err != nil {
t.Fatalf("CreateUnsignedTransaction: %+v", err)
}
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
if err != nil {
t.Fatalf("IsTransactionFullySigned: %+v", err)
}
if isFullySigned {
t.Fatalf("Transaction is not expected to be signed")
}
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction)
if err == nil || !strings.Contains(err.Error(), "missing signature") {
t.Fatal("Unexpectedly succeed to extract a valid transaction out of unsigned transaction")
}
signedTx, err := libkaspawallet.Sign(privateKeys, unsignedTransaction, ecdsa)
if err != nil {
t.Fatalf("IsTransactionFullySigned: %+v", err)
}
tx, err := libkaspawallet.ExtractTransaction(signedTx)
if err != nil {
t.Fatalf("ExtractTransaction: %+v", err)
}
_, insertionResult, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{tx})
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
addedUTXO := &externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(tx),
Index: 0,
}
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(addedUTXO) {
t.Fatalf("Transaction wasn't accepted in the DAG")
}
})
})
}

33
cmd/kaspawallet/main.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import "github.com/pkg/errors"
func main() {
subCmd, config := parseCommandLine()
var err error
switch subCmd {
case createSubCmd:
err = create(config.(*createConfig))
case balanceSubCmd:
err = balance(config.(*balanceConfig))
case sendSubCmd:
err = send(config.(*sendConfig))
case createUnsignedTransactionSubCmd:
err = createUnsignedTransaction(config.(*createUnsignedTransactionConfig))
case signSubCmd:
err = sign(config.(*signConfig))
case broadcastSubCmd:
err = broadcast(config.(*broadcastConfig))
case showAddressSubCmd:
err = showAddress(config.(*showAddressConfig))
case dumpUnencryptedDataSubCmd:
err = dumpUnencryptedData(config.(*dumpUnencryptedDataConfig))
default:
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
}
if err != nil {
printErrorAndExit(err)
}
}

171
cmd/kaspawallet/send.go Normal file
View File

@@ -0,0 +1,171 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
utxopkg "github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionid"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
func send(conf *sendConfig) error {
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
toAddress, err := util.DecodeAddress(conf.ToAddress, conf.ActiveNetParams.Prefix)
if err != nil {
return err
}
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
if err != nil {
return err
}
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
if err != nil {
return err
}
utxos, err := fetchSpendableUTXOs(conf.NetParams(), client, fromAddress.String())
if err != nil {
return err
}
sendAmountSompi := uint64(conf.SendAmount * util.SompiPerKaspa)
const feePerInput = 1000
selectedUTXOs, changeSompi, err := selectUTXOs(utxos, sendAmountSompi, feePerInput)
if err != nil {
return err
}
psTx, err := libkaspawallet.CreateUnsignedTransaction(keysFile.PublicKeys,
keysFile.MinimumSignatures,
keysFile.ECDSA,
[]*libkaspawallet.Payment{{
Address: toAddress,
Amount: sendAmountSompi,
}, {
Address: fromAddress,
Amount: changeSompi,
}},
selectedUTXOs)
if err != nil {
return err
}
privateKeys, err := keysFile.DecryptPrivateKeys()
if err != nil {
return err
}
updatedPSTx, err := libkaspawallet.Sign(privateKeys, psTx, keysFile.ECDSA)
if err != nil {
return err
}
tx, err := libkaspawallet.ExtractTransaction(updatedPSTx)
if err != nil {
return err
}
transactionID, err := sendTransaction(client, tx)
if err != nil {
return err
}
fmt.Println("Transaction was sent successfully")
fmt.Printf("Transaction ID: \t%s\n", transactionID)
return nil
}
func fetchSpendableUTXOs(params *dagconfig.Params, client *rpcclient.RPCClient, address string) ([]*appmessage.UTXOsByAddressesEntry, error) {
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{address})
if err != nil {
return nil, err
}
blockDAGInfo, err := client.GetBlockDAGInfo()
if err != nil {
return nil, err
}
spendableUTXOs := make([]*appmessage.UTXOsByAddressesEntry, 0)
for _, entry := range getUTXOsByAddressesResponse.Entries {
if !isUTXOSpendable(entry, blockDAGInfo.VirtualDAAScore, params.BlockCoinbaseMaturity) {
continue
}
spendableUTXOs = append(spendableUTXOs, entry)
}
return spendableUTXOs, nil
}
func selectUTXOs(utxos []*appmessage.UTXOsByAddressesEntry, spendAmount uint64, feePerInput uint64) (
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair, changeSompi uint64, err error) {
selectedUTXOs = []*externalapi.OutpointAndUTXOEntryPair{}
totalValue := uint64(0)
for _, utxo := range utxos {
txID, err := transactionid.FromString(utxo.Outpoint.TransactionID)
if err != nil {
return nil, 0, err
}
rpcUTXOEntry := utxo.UTXOEntry
scriptPublicKeyScript, err := hex.DecodeString(rpcUTXOEntry.ScriptPublicKey.Script)
if err != nil {
return nil, 0, err
}
scriptPublicKey := &externalapi.ScriptPublicKey{
Script: scriptPublicKeyScript,
Version: rpcUTXOEntry.ScriptPublicKey.Version,
}
utxoEntry := utxopkg.NewUTXOEntry(rpcUTXOEntry.Amount, scriptPublicKey, rpcUTXOEntry.IsCoinbase, rpcUTXOEntry.BlockDAAScore)
selectedUTXOs = append(selectedUTXOs, &externalapi.OutpointAndUTXOEntryPair{
Outpoint: &externalapi.DomainOutpoint{
TransactionID: *txID,
Index: utxo.Outpoint.Index,
},
UTXOEntry: utxoEntry,
})
totalValue += utxo.UTXOEntry.Amount
fee := feePerInput * uint64(len(selectedUTXOs))
totalSpend := spendAmount + fee
if totalValue >= totalSpend {
break
}
}
fee := feePerInput * uint64(len(selectedUTXOs))
totalSpend := spendAmount + fee
if totalValue < totalSpend {
return nil, 0, errors.Errorf("Insufficient funds for send: %f required, while only %f available",
float64(totalSpend)/util.SompiPerKaspa, float64(totalValue)/util.SompiPerKaspa)
}
return selectedUTXOs, totalValue - totalSpend, nil
}
func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx))
if err != nil {
return "", errors.Wrapf(err, "error submitting transaction")
}
return submitTransactionResponse.TransactionID, nil
}

View File

@@ -0,0 +1,23 @@
package main
import (
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
)
func showAddress(conf *showAddressConfig) error {
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
address, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
if err != nil {
return err
}
fmt.Printf("The wallet address is:\n%s\n", address)
return nil
}

45
cmd/kaspawallet/sign.go Normal file
View File

@@ -0,0 +1,45 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
)
func sign(conf *signConfig) error {
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
psTxBytes, err := hex.DecodeString(conf.Transaction)
if err != nil {
return err
}
privateKeys, err := keysFile.DecryptPrivateKeys()
if err != nil {
return err
}
updatedPSTxBytes, err := libkaspawallet.Sign(privateKeys, psTxBytes, keysFile.ECDSA)
if err != nil {
return err
}
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPSTxBytes)
if err != nil {
return err
}
if isFullySigned {
fmt.Println("The transaction is signed and ready to broadcast")
} else {
fmt.Println("Successfully signed transaction")
}
fmt.Printf("Transaction: %x\n", updatedPSTxBytes)
return nil
}

View File

@@ -1,48 +0,0 @@
WALLET
======
## IMPORTANT:
### This software is for TESTING ONLY. Do NOT use it for handling real money.
`wallet` is a simple, no-frills wallet software operated via the command line.\
It is capable of generating wallet key-pairs, printing a wallet's current balance, and sending simple transactions.
## Requirements
Go 1.16 or later.
## Installation
#### Build from Source
- Install Go according to the installation instructions here:
http://golang.org/doc/install
- Ensure Go was installed properly and is a supported version:
```bash
$ go version
```
- Run the following commands to obtain and install kaspad including all dependencies:
```bash
$ git clone https://github.com/kaspanet/kaspad
$ cd kaspad/cmd/wallet
$ go install .
```
- Wallet should now be installed in `$(go env GOPATH)/bin`. If you did
not already add the bin directory to your system path during Go installation,
you are encouraged to do so now.
Usage
-----
* Create a new wallet key-pair: `wallet create --testnet`
* Print a wallet's current balance:
`wallet balance --testnet --address=kaspatest:000000000000000000000000000000000000000000`
* Send funds to another wallet:
`wallet send --testnet --private-key=0000000000000000000000000000000000000000000000000000000000000000 --send-amount=50 --to-address=kaspatest:000000000000000000000000000000000000000000`

View File

@@ -1,21 +0,0 @@
package main
import (
"fmt"
"github.com/kaspanet/kaspad/app/appmessage"
"os"
)
func isUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualSelectedParentBlueScore uint64, coinbaseMaturity uint64) bool {
if !entry.UTXOEntry.IsCoinbase {
return true
}
blockBlueScore := entry.UTXOEntry.BlockDAAScore
// TODO: Check for a better alternative than virtualSelectedParentBlueScore
return blockBlueScore+coinbaseMaturity < virtualSelectedParentBlueScore
}
func printErrorAndExit(err error) {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}

View File

@@ -1,85 +0,0 @@
package main
import (
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/pkg/errors"
"os"
"github.com/jessevdk/go-flags"
)
const (
createSubCmd = "create"
balanceSubCmd = "balance"
sendSubCmd = "send"
)
type createConfig struct {
config.NetworkFlags
}
type balanceConfig struct {
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Address string `long:"address" short:"d" description:"The public address to check the balance of" required:"true"`
config.NetworkFlags
}
type sendConfig struct {
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
PrivateKey string `long:"private-key" short:"k" description:"The private key of the sender (encoded in hex)" required:"true"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"`
config.NetworkFlags
}
func parseCommandLine() (subCommand string, config interface{}) {
cfg := &struct{}{}
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
createConf := &createConfig{}
parser.AddCommand(createSubCmd, "Creates a new wallet",
"Creates a private key and 3 public addresses, one for each of MainNet, TestNet and DevNet", createConf)
balanceConf := &balanceConfig{}
parser.AddCommand(balanceSubCmd, "Shows the balance of a public address",
"Shows the balance for a public address in Kaspa", balanceConf)
sendConf := &sendConfig{}
parser.AddCommand(sendSubCmd, "Sends a Kaspa transaction to a public address",
"Sends a Kaspa transaction to a public address", sendConf)
_, err := parser.Parse()
if err != nil {
var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
os.Exit(0)
} else {
os.Exit(1)
}
return "", nil
}
switch parser.Command.Active.Name {
case createSubCmd:
err := createConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = createConf
case balanceSubCmd:
err := balanceConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = balanceConf
case sendSubCmd:
err := sendConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = sendConf
}
return parser.Command.Active.Name, config
}

View File

@@ -1,37 +0,0 @@
package main
import (
"fmt"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
func create(conf *createConfig) error {
privateKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return errors.Wrap(err, "Failed to generate private key")
}
fmt.Println("This is your private key, granting access to all wallet funds. Keep it safe. Use it only when sending Kaspa.")
fmt.Printf("Private key (hex):\t%s\n\n", privateKey.SerializePrivateKey())
fmt.Println("This is your public address, where money is to be sent.")
publicKey, err := privateKey.SchnorrPublicKey()
if err != nil {
return errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return errors.Wrap(err, "Failed to serialize public key")
}
addr, err := util.NewAddressPubKeyHashFromPublicKey(publicKeySerialized[:], conf.ActiveNetParams.Prefix)
if err != nil {
return errors.Wrap(err, "Failed to generate p2pkh address")
}
fmt.Printf("Address (%s):\t%s\n", conf.ActiveNetParams.Name, addr)
return nil
}

View File

@@ -1,25 +0,0 @@
package main
import (
"github.com/pkg/errors"
)
func main() {
subCmd, config := parseCommandLine()
var err error
switch subCmd {
case createSubCmd:
err = create(config.(*createConfig))
case balanceSubCmd:
err = balance(config.(*balanceConfig))
case sendSubCmd:
err = send(config.(*sendConfig))
default:
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
}
if err != nil {
printErrorAndExit(err)
}
}

View File

@@ -1,204 +0,0 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionid"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
const feeSompis uint64 = 1000
func send(conf *sendConfig) error {
toAddress, err := util.DecodeAddress(conf.ToAddress, conf.ActiveNetParams.Prefix)
if err != nil {
return err
}
keyPair, publicKey, err := parsePrivateKey(conf.PrivateKey)
if err != nil {
return err
}
serializedPublicKey, err := publicKey.Serialize()
if err != nil {
return err
}
fromAddress, err := util.NewAddressPubKeyHashFromPublicKey(serializedPublicKey[:], conf.ActiveNetParams.Prefix)
if err != nil {
return err
}
client, err := rpcclient.NewRPCClient(conf.RPCServer)
if err != nil {
return err
}
utxos, err := fetchSpendableUTXOs(conf, client, fromAddress.String())
if err != nil {
return err
}
sendAmountSompi := uint64(conf.SendAmount * util.SompiPerKaspa)
totalToSend := sendAmountSompi + feeSompis
selectedUTXOs, changeSompi, err := selectUTXOs(utxos, totalToSend)
if err != nil {
return err
}
rpcTransaction, err := generateTransaction(keyPair, selectedUTXOs, sendAmountSompi, changeSompi, toAddress, fromAddress)
if err != nil {
return err
}
transactionID, err := sendTransaction(client, rpcTransaction)
if err != nil {
return err
}
fmt.Println("Transaction was sent successfully")
fmt.Printf("Transaction ID: \t%s\n", transactionID)
return nil
}
func parsePrivateKey(privateKeyHex string) (*secp256k1.SchnorrKeyPair, *secp256k1.SchnorrPublicKey, error) {
privateKeyBytes, err := hex.DecodeString(privateKeyHex)
if err != nil {
return nil, nil, errors.Wrap(err, "Error parsing private key hex")
}
keyPair, err := secp256k1.DeserializePrivateKeyFromSlice(privateKeyBytes)
if err != nil {
return nil, nil, errors.Wrap(err, "Error deserializing private key")
}
publicKey, err := keyPair.SchnorrPublicKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Error generating public key")
}
return keyPair, publicKey, nil
}
func fetchSpendableUTXOs(conf *sendConfig, client *rpcclient.RPCClient, address string) ([]*appmessage.UTXOsByAddressesEntry, error) {
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{address})
if err != nil {
return nil, err
}
virtualSelectedParentBlueScoreResponse, err := client.GetVirtualSelectedParentBlueScore()
if err != nil {
return nil, err
}
virtualSelectedParentBlueScore := virtualSelectedParentBlueScoreResponse.BlueScore
spendableUTXOs := make([]*appmessage.UTXOsByAddressesEntry, 0)
for _, entry := range getUTXOsByAddressesResponse.Entries {
if !isUTXOSpendable(entry, virtualSelectedParentBlueScore, conf.ActiveNetParams.BlockCoinbaseMaturity) {
continue
}
spendableUTXOs = append(spendableUTXOs, entry)
}
return spendableUTXOs, nil
}
func selectUTXOs(utxos []*appmessage.UTXOsByAddressesEntry, totalToSpend uint64) (
selectedUTXOs []*appmessage.UTXOsByAddressesEntry, changeSompi uint64, err error) {
selectedUTXOs = []*appmessage.UTXOsByAddressesEntry{}
totalValue := uint64(0)
for _, utxo := range utxos {
selectedUTXOs = append(selectedUTXOs, utxo)
totalValue += utxo.UTXOEntry.Amount
if totalValue >= totalToSpend {
break
}
}
if totalValue < totalToSpend {
return nil, 0, errors.Errorf("Insufficient funds for send: %f required, while only %f available",
float64(totalToSpend)/util.SompiPerKaspa, float64(totalValue)/util.SompiPerKaspa)
}
return selectedUTXOs, totalValue - totalToSpend, nil
}
func generateTransaction(keyPair *secp256k1.SchnorrKeyPair, selectedUTXOs []*appmessage.UTXOsByAddressesEntry,
sompisToSend uint64, change uint64, toAddress util.Address,
fromAddress util.Address) (*appmessage.RPCTransaction, error) {
inputs := make([]*externalapi.DomainTransactionInput, len(selectedUTXOs))
for i, utxo := range selectedUTXOs {
outpointTransactionIDBytes, err := hex.DecodeString(utxo.Outpoint.TransactionID)
if err != nil {
return nil, err
}
outpointTransactionID, err := transactionid.FromBytes(outpointTransactionIDBytes)
if err != nil {
return nil, err
}
outpoint := externalapi.DomainOutpoint{
TransactionID: *outpointTransactionID,
Index: utxo.Outpoint.Index,
}
inputs[i] = &externalapi.DomainTransactionInput{PreviousOutpoint: outpoint}
}
toScript, err := txscript.PayToAddrScript(toAddress)
if err != nil {
return nil, err
}
mainOutput := &externalapi.DomainTransactionOutput{
Value: sompisToSend,
ScriptPublicKey: toScript,
}
fromScript, err := txscript.PayToAddrScript(fromAddress)
if err != nil {
return nil, err
}
changeOutput := &externalapi.DomainTransactionOutput{
Value: change,
ScriptPublicKey: fromScript,
}
outputs := []*externalapi.DomainTransactionOutput{mainOutput, changeOutput}
domainTransaction := &externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: inputs,
Outputs: outputs,
LockTime: 0,
SubnetworkID: subnetworks.SubnetworkIDNative,
Gas: 0,
Payload: nil,
}
sighashReusedValues := &consensushashing.SighashReusedValues{}
for i, input := range domainTransaction.Inputs {
signatureScript, err := txscript.SignatureScript(
domainTransaction, i, consensushashing.SigHashAll, keyPair, sighashReusedValues)
if err != nil {
return nil, err
}
input.SignatureScript = signatureScript
}
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(domainTransaction)
return rpcTransaction, nil
}
func sendTransaction(client *rpcclient.RPCClient, rpcTransaction *appmessage.RPCTransaction) (string, error) {
submitTransactionResponse, err := client.SubmitTransaction(rpcTransaction)
if err != nil {
return "", errors.Wrapf(err, "error submitting transaction")
}
return submitTransactionResponse.TransactionID, nil
}

View File

@@ -112,6 +112,30 @@ func (s *consensus) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.Do
return block, nil
}
func (s *consensus) GetBlockEvenIfHeaderOnly(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) {
s.lock.Lock()
defer s.lock.Unlock()
stagingArea := model.NewStagingArea()
block, err := s.blockStore.Block(s.databaseContext, stagingArea, blockHash)
if err == nil {
return block, nil
}
if !errors.Is(err, database.ErrNotFound) {
return nil, err
}
header, err := s.blockHeaderStore.BlockHeader(s.databaseContext, stagingArea, blockHash)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
return nil, errors.Wrapf(err, "block %s does not exist", blockHash)
}
return nil, err
}
return &externalapi.DomainBlock{Header: header}, nil
}
func (s *consensus) GetBlockHeader(blockHash *externalapi.DomainHash) (externalapi.BlockHeader, error) {
s.lock.Lock()
defer s.lock.Unlock()
@@ -166,7 +190,10 @@ func (s *consensus) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalap
return blockInfo, nil
}
func (s *consensus) GetBlockChildren(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
func (s *consensus) GetBlockRelations(blockHash *externalapi.DomainHash) (
parents []*externalapi.DomainHash, selectedParent *externalapi.DomainHash,
children []*externalapi.DomainHash, err error) {
s.lock.Lock()
defer s.lock.Unlock()
@@ -174,10 +201,15 @@ func (s *consensus) GetBlockChildren(blockHash *externalapi.DomainHash) ([]*exte
blockRelation, err := s.blockRelationStore.BlockRelation(s.databaseContext, stagingArea, blockHash)
if err != nil {
return nil, err
return nil, nil, nil, err
}
return blockRelation.Children, nil
blockGHOSTDAGData, err := s.ghostdagDataStore.Get(s.databaseContext, stagingArea, blockHash)
if err != nil {
return nil, nil, nil, err
}
return blockRelation.Parents, blockGHOSTDAGData.SelectedParent(), blockRelation.Children, nil
}
func (s *consensus) GetBlockAcceptanceData(blockHash *externalapi.DomainHash) (externalapi.AcceptanceData, error) {
@@ -355,11 +387,17 @@ func (s *consensus) GetVirtualInfo() (*externalapi.VirtualInfo, error) {
return nil, err
}
daaScore, err := s.daaBlocksStore.DAAScore(s.databaseContext, stagingArea, model.VirtualBlockHash)
if err != nil {
return nil, err
}
return &externalapi.VirtualInfo{
ParentHashes: blockRelations.Parents,
Bits: bits,
PastMedianTime: pastMedianTime,
BlueScore: virtualGHOSTDAGData.BlueScore(),
DAAScore: daaScore,
}, nil
}

View File

@@ -1,26 +1,26 @@
package consensus
package consensus_test
import (
"testing"
"github.com/kaspanet/kaspad/domain/consensus"
"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/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/pkg/errors"
)
func TestConsensus_GetBlockInfo(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
factory := NewFactory()
consensus, teardown, err := factory.NewTestConsensus(params, false, "TestConsensus_GetBlockInfo")
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
consensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestConsensus_GetBlockInfo")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
invalidBlock, _, err := consensus.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
invalidBlock, _, err := consensus.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatal(err)
}

View File

@@ -8,7 +8,8 @@ import (
type pruningStagingShard struct {
store *pruningStore
newPruningPoint *externalapi.DomainHash
currentPruningPoint *externalapi.DomainHash
previousPruningPoint *externalapi.DomainHash
newPruningPointCandidate *externalapi.DomainHash
startUpdatingPruningPointUTXOSet bool
}
@@ -17,7 +18,8 @@ func (ps *pruningStore) stagingShard(stagingArea *model.StagingArea) *pruningSta
return stagingArea.GetOrCreateShard(model.StagingShardIDPruning, func() model.StagingShard {
return &pruningStagingShard{
store: ps,
newPruningPoint: nil,
currentPruningPoint: nil,
previousPruningPoint: nil,
newPruningPointCandidate: nil,
startUpdatingPruningPointUTXOSet: false,
}
@@ -25,8 +27,8 @@ func (ps *pruningStore) stagingShard(stagingArea *model.StagingArea) *pruningSta
}
func (mss *pruningStagingShard) Commit(dbTx model.DBTransaction) error {
if mss.newPruningPoint != nil {
pruningPointBytes, err := mss.store.serializeHash(mss.newPruningPoint)
if mss.currentPruningPoint != nil {
pruningPointBytes, err := mss.store.serializeHash(mss.currentPruningPoint)
if err != nil {
return err
}
@@ -34,7 +36,19 @@ func (mss *pruningStagingShard) Commit(dbTx model.DBTransaction) error {
if err != nil {
return err
}
mss.store.pruningPointCache = mss.newPruningPoint
mss.store.pruningPointCache = mss.currentPruningPoint
}
if mss.previousPruningPoint != nil {
oldPruningPointBytes, err := mss.store.serializeHash(mss.previousPruningPoint)
if err != nil {
return err
}
err = dbTx.Put(previousPruningBlockHashKey, oldPruningPointBytes)
if err != nil {
return err
}
mss.store.oldPruningPointCache = mss.previousPruningPoint
}
if mss.newPruningPointCandidate != nil {
@@ -60,5 +74,5 @@ func (mss *pruningStagingShard) Commit(dbTx model.DBTransaction) error {
}
func (mss *pruningStagingShard) isStaged() bool {
return mss.newPruningPoint != nil || mss.startUpdatingPruningPointUTXOSet
return mss.currentPruningPoint != nil || mss.newPruningPointCandidate != nil || mss.previousPruningPoint != nil || mss.startUpdatingPruningPointUTXOSet
}

View File

@@ -9,6 +9,7 @@ import (
)
var pruningBlockHashKey = database.MakeBucket(nil).Key([]byte("pruning-block-hash"))
var previousPruningBlockHashKey = database.MakeBucket(nil).Key([]byte("previous-pruning-block-hash"))
var candidatePruningPointHashKey = database.MakeBucket(nil).Key([]byte("candidate-pruning-point-hash"))
var pruningPointUTXOSetBucket = database.MakeBucket([]byte("pruning-point-utxo-set"))
var updatingPruningPointUTXOSetKey = database.MakeBucket(nil).Key([]byte("updating-pruning-point-utxo-set"))
@@ -16,6 +17,7 @@ var updatingPruningPointUTXOSetKey = database.MakeBucket(nil).Key([]byte("updati
// pruningStore represents a store for the current pruning state
type pruningStore struct {
pruningPointCache *externalapi.DomainHash
oldPruningPointCache *externalapi.DomainHash
pruningPointCandidateCache *externalapi.DomainHash
}
@@ -72,54 +74,56 @@ func (ps *pruningStore) HasPruningPointCandidate(dbContext model.DBReader, stagi
func (ps *pruningStore) StagePruningPoint(stagingArea *model.StagingArea, pruningPointBlockHash *externalapi.DomainHash) {
stagingShard := ps.stagingShard(stagingArea)
stagingShard.newPruningPoint = pruningPointBlockHash
stagingShard.currentPruningPoint = pruningPointBlockHash
}
func (ps *pruningStore) StagePreviousPruningPoint(stagingArea *model.StagingArea, oldPruningPointBlockHash *externalapi.DomainHash) {
stagingShard := ps.stagingShard(stagingArea)
stagingShard.previousPruningPoint = oldPruningPointBlockHash
}
func (ps *pruningStore) IsStaged(stagingArea *model.StagingArea) bool {
return ps.stagingShard(stagingArea).isStaged()
}
func (ps *pruningStore) UpdatePruningPointUTXOSet(dbContext model.DBWriter,
utxoSetIterator externalapi.ReadOnlyUTXOSetIterator) error {
// Delete all the old UTXOs from the database
deleteCursor, err := dbContext.Cursor(pruningPointUTXOSetBucket)
if err != nil {
return err
}
defer deleteCursor.Close()
for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
key, err := deleteCursor.Key()
func (ps *pruningStore) UpdatePruningPointUTXOSet(dbContext model.DBWriter, diff externalapi.UTXODiff) error {
toRemoveIterator := diff.ToRemove().Iterator()
defer toRemoveIterator.Close()
for ok := toRemoveIterator.First(); ok; ok = toRemoveIterator.Next() {
toRemoveOutpoint, _, err := toRemoveIterator.Get()
if err != nil {
return err
}
err = dbContext.Delete(key)
serializedOutpoint, err := serializeOutpoint(toRemoveOutpoint)
if err != nil {
return err
}
err = dbContext.Delete(pruningPointUTXOSetBucket.Key(serializedOutpoint))
if err != nil {
return err
}
}
// Insert all the new UTXOs into the database
for ok := utxoSetIterator.First(); ok; ok = utxoSetIterator.Next() {
outpoint, entry, err := utxoSetIterator.Get()
toAddIterator := diff.ToAdd().Iterator()
defer toAddIterator.Close()
for ok := toAddIterator.First(); ok; ok = toAddIterator.Next() {
toAddOutpoint, entry, err := toAddIterator.Get()
if err != nil {
return err
}
serializedOutpoint, err := serializeOutpoint(outpoint)
serializedOutpoint, err := serializeOutpoint(toAddOutpoint)
if err != nil {
return err
}
key := pruningPointUTXOSetBucket.Key(serializedOutpoint)
serializedUTXOEntry, err := serializeUTXOEntry(entry)
if err != nil {
return err
}
err = dbContext.Put(key, serializedUTXOEntry)
err = dbContext.Put(pruningPointUTXOSetBucket.Key(serializedOutpoint), serializedUTXOEntry)
if err != nil {
return err
}
}
return nil
}
@@ -127,8 +131,8 @@ func (ps *pruningStore) UpdatePruningPointUTXOSet(dbContext model.DBWriter,
func (ps *pruningStore) PruningPoint(dbContext model.DBReader, stagingArea *model.StagingArea) (*externalapi.DomainHash, error) {
stagingShard := ps.stagingShard(stagingArea)
if stagingShard.newPruningPoint != nil {
return stagingShard.newPruningPoint, nil
if stagingShard.currentPruningPoint != nil {
return stagingShard.currentPruningPoint, nil
}
if ps.pruningPointCache != nil {
@@ -148,6 +152,30 @@ func (ps *pruningStore) PruningPoint(dbContext model.DBReader, stagingArea *mode
return pruningPoint, nil
}
// OldPruningPoint returns the pruning point *before* the current one
func (ps *pruningStore) PreviousPruningPoint(dbContext model.DBReader, stagingArea *model.StagingArea) (*externalapi.DomainHash, error) {
stagingShard := ps.stagingShard(stagingArea)
if stagingShard.previousPruningPoint != nil {
return stagingShard.previousPruningPoint, nil
}
if ps.oldPruningPointCache != nil {
return ps.oldPruningPointCache, nil
}
oldPruningPointBytes, err := dbContext.Get(previousPruningBlockHashKey)
if err != nil {
return nil, err
}
oldPruningPoint, err := ps.deserializePruningPoint(oldPruningPointBytes)
if err != nil {
return nil, err
}
ps.oldPruningPointCache = oldPruningPoint
return oldPruningPoint, nil
}
func (ps *pruningStore) serializeHash(hash *externalapi.DomainHash) ([]byte, error) {
return proto.Marshal(serialization.DomainHashToDbHash(hash))
}
@@ -165,7 +193,7 @@ func (ps *pruningStore) deserializePruningPoint(pruningPointBytes []byte) (*exte
func (ps *pruningStore) HasPruningPoint(dbContext model.DBReader, stagingArea *model.StagingArea) (bool, error) {
stagingShard := ps.stagingShard(stagingArea)
if stagingShard.newPruningPoint != nil {
if stagingShard.currentPruningPoint != nil {
return true, nil
}
@@ -176,6 +204,14 @@ func (ps *pruningStore) HasPruningPoint(dbContext model.DBReader, stagingArea *m
return dbContext.Has(pruningBlockHashKey)
}
func (ps *pruningStore) PruningPointUTXOIterator(dbContext model.DBReader) (externalapi.ReadOnlyUTXOSetIterator, error) {
cursor, err := dbContext.Cursor(pruningPointUTXOSetBucket)
if err != nil {
return nil, err
}
return ps.newCursorUTXOSetIterator(cursor), nil
}
func (ps *pruningStore) PruningPointUTXOs(dbContext model.DBReader,
fromOutpoint *externalapi.DomainOutpoint, limit int) ([]*externalapi.OutpointAndUTXOEntryPair, error) {

View File

@@ -28,7 +28,9 @@ func New(cacheSize int, preallocate bool) model.UTXODiffStore {
}
// Stage stages the given utxoDiff for the given blockHash
func (uds *utxoDiffStore) Stage(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, utxoDiff externalapi.UTXODiff, utxoDiffChild *externalapi.DomainHash) {
func (uds *utxoDiffStore) Stage(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
utxoDiff externalapi.UTXODiff, utxoDiffChild *externalapi.DomainHash) {
stagingShard := uds.stagingShard(stagingArea)
stagingShard.utxoDiffToAdd[*blockHash] = utxoDiff

View File

@@ -5,13 +5,6 @@ import (
"os"
"sync"
daablocksstore "github.com/kaspanet/kaspad/domain/consensus/datastructures/daablocksstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/headersselectedchainstore"
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtraversalmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/finalitymanager"
consensusdatabase "github.com/kaspanet/kaspad/domain/consensus/database"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/acceptancedatastore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockheaderstore"
@@ -19,8 +12,10 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockstatusstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/consensusstatestore"
daablocksstore "github.com/kaspanet/kaspad/domain/consensus/datastructures/daablocksstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/finalitystore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/ghostdagdatastore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/headersselectedchainstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/headersselectedtipstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/multisetstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/pruningstore"
@@ -34,7 +29,9 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/processes/coinbasemanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/consensusstatemanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtopologymanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtraversalmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/difficultymanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/finalitymanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/headersselectedtipmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/mergedepthmanager"
@@ -54,11 +51,20 @@ const (
defaultTestPreallocateCaches = false
)
// Config is the full config required to run consensus
type Config struct {
dagconfig.Params
// IsArchival tells the consensus if it should not prune old blocks
IsArchival bool
// EnableSanityCheckPruningUTXOSet checks the full pruning point utxo set against the commitment at every pruning movement
EnableSanityCheckPruningUTXOSet bool
}
// Factory instantiates new Consensuses
type Factory interface {
NewConsensus(dagParams *dagconfig.Params, db infrastructuredatabase.Database, isArchivalNode bool) (
NewConsensus(config *Config, db infrastructuredatabase.Database) (
externalapi.Consensus, error)
NewTestConsensus(dagParams *dagconfig.Params, isArchivalNode bool, testName string) (
NewTestConsensus(config *Config, testName string) (
tc testapi.TestConsensus, teardown func(keepDataDir bool), err error)
SetTestDataDir(dataDir string)
@@ -88,12 +94,12 @@ func NewFactory() Factory {
}
// NewConsensus instantiates a new Consensus
func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredatabase.Database, isArchivalNode bool) (
func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Database) (
externalapi.Consensus, error) {
dbManager := consensusdatabase.New(db)
pruningWindowSizeForCaches := int(dagParams.PruningDepth())
pruningWindowSizeForCaches := int(config.PruningDepth())
var preallocateCaches bool
if f.preallocateCaches != nil {
@@ -104,7 +110,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
// This is used for caches that are used as part of deletePastBlocks that need to traverse until
// the previous pruning point.
pruningWindowSizePlusFinalityDepthForCache := int(dagParams.PruningDepth() + dagParams.FinalityDepth())
pruningWindowSizePlusFinalityDepthForCache := int(config.PruningDepth() + config.FinalityDepth())
// Data Structures
acceptanceDataStore := acceptancedatastore.New(200, preallocateCaches)
@@ -129,15 +135,15 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
// a single DifficultyAdjustmentWindow. To alleviate this problem we make sure that the cache size is at least
// dagParams.DifficultyAdjustmentWindowSize
ghostdagDataCacheSize := pruningWindowSizeForCaches
if ghostdagDataCacheSize < dagParams.DifficultyAdjustmentWindowSize {
ghostdagDataCacheSize = dagParams.DifficultyAdjustmentWindowSize
if ghostdagDataCacheSize < config.DifficultyAdjustmentWindowSize {
ghostdagDataCacheSize = config.DifficultyAdjustmentWindowSize
}
ghostdagDataStore := ghostdagdatastore.New(ghostdagDataCacheSize, preallocateCaches)
headersSelectedTipStore := headersselectedtipstore.New()
finalityStore := finalitystore.New(200, preallocateCaches)
headersSelectedChainStore := headersselectedchainstore.New(pruningWindowSizeForCaches, preallocateCaches)
daaBlocksStore := daablocksstore.New(pruningWindowSizeForCaches, int(dagParams.FinalityDepth()), preallocateCaches)
daaBlocksStore := daablocksstore.New(pruningWindowSizeForCaches, int(config.FinalityDepth()), preallocateCaches)
// Processes
reachabilityManager := reachabilitymanager.New(
@@ -154,7 +160,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
dagTopologyManager,
ghostdagDataStore,
blockHeaderStore,
dagParams.K)
config.K)
dagTraversalManager := dagtraversalmanager.New(
dbManager,
dagTopologyManager,
@@ -162,20 +168,20 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
reachabilityDataStore,
ghostdagManager,
consensusStateStore,
dagParams.GenesisHash)
config.GenesisHash)
pastMedianTimeManager := f.pastMedianTimeConsructor(
dagParams.TimestampDeviationTolerance,
config.TimestampDeviationTolerance,
dbManager,
dagTraversalManager,
blockHeaderStore,
ghostdagDataStore,
dagParams.GenesisHash)
transactionValidator := transactionvalidator.New(dagParams.BlockCoinbaseMaturity,
dagParams.EnableNonNativeSubnetworks,
dagParams.MassPerTxByte,
dagParams.MassPerScriptPubKeyByte,
dagParams.MassPerSigOp,
dagParams.MaxCoinbasePayloadLength,
config.GenesisHash)
transactionValidator := transactionvalidator.New(config.BlockCoinbaseMaturity,
config.EnableNonNativeSubnetworks,
config.MassPerTxByte,
config.MassPerScriptPubKeyByte,
config.MassPerSigOp,
config.MaxCoinbasePayloadLength,
dbManager,
pastMedianTimeManager,
ghostdagDataStore,
@@ -188,29 +194,29 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
daaBlocksStore,
dagTopologyManager,
dagTraversalManager,
dagParams.PowMax,
dagParams.DifficultyAdjustmentWindowSize,
dagParams.DisableDifficultyAdjustment,
dagParams.TargetTimePerBlock,
dagParams.GenesisHash)
config.PowMax,
config.DifficultyAdjustmentWindowSize,
config.DisableDifficultyAdjustment,
config.TargetTimePerBlock,
config.GenesisHash)
coinbaseManager := coinbasemanager.New(
dbManager,
dagParams.SubsidyReductionInterval,
dagParams.BaseSubsidy,
dagParams.CoinbasePayloadScriptPublicKeyMaxLength,
config.SubsidyReductionInterval,
config.BaseSubsidy,
config.CoinbasePayloadScriptPublicKeyMaxLength,
ghostdagDataStore,
acceptanceDataStore,
daaBlocksStore)
headerTipsManager := headersselectedtipmanager.New(dbManager, dagTopologyManager, dagTraversalManager,
ghostdagManager, headersSelectedTipStore, headersSelectedChainStore)
genesisHash := dagParams.GenesisHash
genesisHash := config.GenesisHash
finalityManager := finalitymanager.New(
dbManager,
dagTopologyManager,
finalityStore,
ghostdagDataStore,
genesisHash,
dagParams.FinalityDepth())
config.FinalityDepth())
mergeDepthManager := mergedepthmanager.New(
dbManager,
dagTopologyManager,
@@ -218,15 +224,15 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
finalityManager,
ghostdagDataStore)
blockValidator := blockvalidator.New(
dagParams.PowMax,
dagParams.SkipProofOfWork,
config.PowMax,
config.SkipProofOfWork,
genesisHash,
dagParams.EnableNonNativeSubnetworks,
dagParams.MaxBlockSize,
dagParams.MergeSetSizeLimit,
dagParams.MaxBlockParents,
dagParams.TimestampDeviationTolerance,
dagParams.TargetTimePerBlock,
config.EnableNonNativeSubnetworks,
config.MaxBlockSize,
config.MergeSetSizeLimit,
config.MaxBlockParents,
config.TimestampDeviationTolerance,
config.TargetTimePerBlock,
dbManager,
difficultyManager,
@@ -249,10 +255,10 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
)
consensusStateManager, err := consensusstatemanager.New(
dbManager,
dagParams.PruningDepth(),
dagParams.MaxMassAcceptedByBlock,
dagParams.MaxBlockParents,
dagParams.MergeSetSizeLimit,
config.PruningDepth(),
config.MaxMassAcceptedByBlock,
config.MaxBlockParents,
config.MergeSetSizeLimit,
genesisHash,
ghostdagManager,
@@ -299,10 +305,11 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
blockHeaderStore,
utxoDiffStore,
daaBlocksStore,
isArchivalNode,
config.IsArchival,
genesisHash,
dagParams.FinalityDepth(),
dagParams.PruningDepth())
config.FinalityDepth(),
config.PruningDepth(),
config.EnableSanityCheckPruningUTXOSet)
syncManager := syncmanager.New(
dbManager,
@@ -336,7 +343,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
blockProcessor := blockprocessor.New(
genesisHash,
dagParams.TargetTimePerBlock,
config.TargetTimePerBlock,
dbManager,
consensusStateManager,
pruningManager,
@@ -411,7 +418,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
}
if !genesisInfo.Exists {
_, err = c.ValidateAndInsertBlock(dagParams.GenesisBlock)
_, err = c.ValidateAndInsertBlock(config.GenesisBlock)
if err != nil {
return nil, err
}
@@ -425,7 +432,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
if err != nil {
return nil, err
}
err = pruningManager.UpdatePruningPointUTXOSetIfRequired()
err = pruningManager.UpdatePruningPointIfRequired()
if err != nil {
return nil, err
}
@@ -433,7 +440,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
return c, nil
}
func (f *factory) NewTestConsensus(dagParams *dagconfig.Params, isArchivalNode bool, testName string) (
func (f *factory) NewTestConsensus(config *Config, testName string) (
tc testapi.TestConsensus, teardown func(keepDataDir bool), err error) {
datadir := f.dataDir
if datadir == "" {
@@ -455,7 +462,7 @@ func (f *factory) NewTestConsensus(dagParams *dagconfig.Params, isArchivalNode b
if err != nil {
return nil, nil, err
}
consensusAsInterface, err := f.NewConsensus(dagParams, db, isArchivalNode)
consensusAsInterface, err := f.NewConsensus(config, db)
if err != nil {
return nil, nil, err
}
@@ -465,7 +472,7 @@ func (f *factory) NewTestConsensus(dagParams *dagconfig.Params, isArchivalNode b
testTransactionValidator := transactionvalidator.NewTestTransactionValidator(consensusAsImplementation.transactionValidator)
tstConsensus := &testConsensus{
dagParams: dagParams,
dagParams: &config.Params,
consensus: consensusAsImplementation,
database: db,
testConsensusStateManager: testConsensusStateManager,

View File

@@ -11,7 +11,7 @@ import (
func TestNewConsensus(t *testing.T) {
f := NewFactory()
dagParams := &dagconfig.DevnetParams
config := &Config{Params: dagconfig.DevnetParams}
tmpDir, err := ioutil.TempDir("", "TestNewConsensus")
if err != nil {
@@ -23,7 +23,7 @@ func TestNewConsensus(t *testing.T) {
t.Fatalf("error in NewLevelDB: %s", err)
}
_, err = f.NewConsensus(dagParams, db, false)
_, err = f.NewConsensus(config, db)
if err != nil {
t.Fatalf("error in NewConsensus: %+v", err)
}

View File

@@ -1,26 +1,26 @@
package consensus
package consensus_test
import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus"
"testing"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/pkg/errors"
"fmt"
"testing"
)
func TestFinality(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// Set finalityInterval to 50 blocks, so that test runs quickly
params.FinalityDuration = 50 * params.TargetTimePerBlock
consensusConfig.FinalityDuration = 50 * consensusConfig.TargetTimePerBlock
factory := NewFactory()
consensus, teardown, err := factory.NewTestConsensus(params, false, "TestFinality")
factory := consensus.NewFactory()
consensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestFinality")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
@@ -40,9 +40,9 @@ func TestFinality(t *testing.T) {
}
// Build a chain of `finalityInterval - 1` blocks
finalityInterval := params.FinalityDepth()
finalityInterval := consensusConfig.FinalityDepth()
var mainChainTip *externalapi.DomainBlock
mainChainTipHash := params.GenesisHash
mainChainTipHash := consensusConfig.GenesisHash
for i := uint64(0); i < finalityInterval-1; i++ {
mainChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{mainChainTipHash})
@@ -63,11 +63,11 @@ func TestFinality(t *testing.T) {
// Mine another chain of `finality-Interval - 2` blocks
var sideChainTip *externalapi.DomainBlock
sideChainTipHash := params.GenesisHash
sideChainTipHash := consensusConfig.GenesisHash
for i := uint64(0); i < finalityInterval-2; i++ {
sideChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{sideChainTipHash})
if err != nil {
t.Fatalf("TestFinality: Failed to process sidechain Block #%d: %v", i, err)
t.Fatalf("TestFinality: Failed to process sidechain Block #%d: %+v", i, err)
}
sideChainTipHash = consensushashing.BlockHash(sideChainTip)
@@ -127,7 +127,7 @@ func TestFinality(t *testing.T) {
t.Fatalf("TestFinality: Failed getting the virtual's finality point: %v", err)
}
if virtualFinality.Equal(params.GenesisHash) {
if virtualFinality.Equal(consensusConfig.GenesisHash) {
t.Fatalf("virtual's finalityPoint is still genesis after adding finalityInterval + 1 blocks to the main chain")
}
@@ -178,12 +178,12 @@ func TestFinality(t *testing.T) {
}
func TestBoundedMergeDepth(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// Set finalityInterval to 50 blocks, so that test runs quickly
params.FinalityDuration = 50 * params.TargetTimePerBlock
finalityInterval := int(params.FinalityDepth())
consensusConfig.FinalityDuration = 50 * consensusConfig.TargetTimePerBlock
finalityInterval := int(consensusConfig.FinalityDepth())
if int(params.K) >= finalityInterval {
if int(consensusConfig.K) >= finalityInterval {
t.Fatal("K must be smaller than finality duration for this test to run")
}
@@ -235,20 +235,20 @@ func TestBoundedMergeDepth(t *testing.T) {
return blockInfo.BlockStatus
}
factory := NewFactory()
consensusBuild, teardownFunc1, err := factory.NewTestConsensus(params, false, "TestBoundedMergeTestBuild")
factory := consensus.NewFactory()
consensusBuild, teardownFunc1, err := factory.NewTestConsensus(consensusConfig, "TestBoundedMergeTestBuild")
if err != nil {
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
}
consensusReal, teardownFunc2, err := factory.NewTestConsensus(params, false, "TestBoundedMergeTestReal")
consensusReal, teardownFunc2, err := factory.NewTestConsensus(consensusConfig, "TestBoundedMergeTestReal")
if err != nil {
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
}
defer teardownFunc2(false)
// Create a block on top on genesis
block1 := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{params.GenesisHash})
block1 := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{consensusConfig.GenesisHash})
// Create a chain
selectedChain := make([]*externalapi.DomainBlock, 0, finalityInterval+1)
@@ -342,7 +342,7 @@ func TestBoundedMergeDepth(t *testing.T) {
// Now let's make the kosherizing block red and try to merge again
tip := consensushashing.BlockHash(selectedChain[len(selectedChain)-1])
// we use k-1 because `kosherizingBlock` points at tip-2, so 2+k-1 = k+1 anticone.
for i := 0; i < int(params.K)-1; i++ {
for i := 0; i < int(consensusConfig.K)-1; i++ {
block := buildAndInsertBlock(consensusReal, []*externalapi.DomainHash{tip})
tip = consensushashing.BlockHash(block)
}

View File

@@ -7,9 +7,10 @@ type Consensus interface {
ValidateTransactionAndPopulateWithConsensusData(transaction *DomainTransaction) error
GetBlock(blockHash *DomainHash) (*DomainBlock, error)
GetBlockEvenIfHeaderOnly(blockHash *DomainHash) (*DomainBlock, error)
GetBlockHeader(blockHash *DomainHash) (BlockHeader, error)
GetBlockInfo(blockHash *DomainHash) (*BlockInfo, error)
GetBlockChildren(blockHash *DomainHash) ([]*DomainHash, error)
GetBlockRelations(blockHash *DomainHash) (parents []*DomainHash, selectedParent *DomainHash, children []*DomainHash, err error)
GetBlockAcceptanceData(blockHash *DomainHash) (AcceptanceData, error)
GetHashesBetween(lowHash, highHash *DomainHash, maxBlueScoreDifference uint64) (hashes []*DomainHash, actualHighHash *DomainHash, err error)

View File

@@ -14,6 +14,7 @@ type UTXODiff interface {
ToRemove() UTXOCollection
WithDiff(other UTXODiff) (UTXODiff, error)
DiffFrom(other UTXODiff) (UTXODiff, error)
Reversed() UTXODiff
CloneMutable() MutableUTXODiff
}

View File

@@ -6,4 +6,5 @@ type VirtualInfo struct {
Bits uint32
PastMedianTime int64
BlueScore uint64
DAAScore uint64
}

View File

@@ -6,17 +6,19 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
type PruningStore interface {
Store
StagePruningPoint(stagingArea *StagingArea, pruningPointBlockHash *externalapi.DomainHash)
StagePreviousPruningPoint(stagingArea *StagingArea, oldPruningPointBlockHash *externalapi.DomainHash)
StagePruningPointCandidate(stagingArea *StagingArea, candidate *externalapi.DomainHash)
IsStaged(stagingArea *StagingArea) bool
PruningPointCandidate(dbContext DBReader, stagingArea *StagingArea) (*externalapi.DomainHash, error)
HasPruningPointCandidate(dbContext DBReader, stagingArea *StagingArea) (bool, error)
PreviousPruningPoint(dbContext DBReader, stagingArea *StagingArea) (*externalapi.DomainHash, error)
PruningPoint(dbContext DBReader, stagingArea *StagingArea) (*externalapi.DomainHash, error)
HasPruningPoint(dbContext DBReader, stagingArea *StagingArea) (bool, error)
StageStartUpdatingPruningPointUTXOSet(stagingArea *StagingArea)
HadStartedUpdatingPruningPointUTXOSet(dbContext DBWriter) (bool, error)
FinishUpdatingPruningPointUTXOSet(dbContext DBWriter) error
UpdatePruningPointUTXOSet(dbContext DBWriter, utxoSetIterator externalapi.ReadOnlyUTXOSetIterator) error
UpdatePruningPointUTXOSet(dbContext DBWriter, diff externalapi.UTXODiff) error
ClearImportedPruningPointUTXOs(dbContext DBWriter) error
AppendImportedPruningPointUTXOs(dbTx DBTransaction, outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error
@@ -26,4 +28,5 @@ type PruningStore interface {
UpdateImportedPruningPointMultiset(dbTx DBTransaction, multiset Multiset) error
CommitImportedPruningPointUTXOSet(dbContext DBWriter) error
PruningPointUTXOs(dbContext DBReader, fromOutpoint *externalapi.DomainOutpoint, limit int) ([]*externalapi.OutpointAndUTXOEntryPair, error)
PruningPointUTXOIterator(dbContext DBReader) (externalapi.ReadOnlyUTXOSetIterator, error)
}

View File

@@ -4,11 +4,12 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// ConsensusStateManager manages the node's consensus state
type ConsensusStateManager interface {
AddBlock(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, externalapi.UTXODiff, error)
AddBlock(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, externalapi.UTXODiff, *UTXODiffReversalData, error)
PopulateTransactionWithUTXOEntries(stagingArea *StagingArea, transaction *externalapi.DomainTransaction) error
ImportPruningPoint(stagingArea *StagingArea, newPruningPoint *externalapi.DomainBlock) error
RestorePastUTXOSetIterator(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (externalapi.ReadOnlyUTXOSetIterator, error)
CalculatePastUTXOAndAcceptanceData(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (externalapi.UTXODiff, externalapi.AcceptanceData, Multiset, error)
GetVirtualSelectedParentChainFromBlock(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error)
RecoverUTXOIfRequired() error
ReverseUTXODiffs(tipHash *externalapi.DomainHash, reversalData *UTXODiffReversalData) error
}

View File

@@ -8,6 +8,6 @@ type PruningManager interface {
IsValidPruningPoint(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (bool, error)
ClearImportedPruningPointData() error
AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error
UpdatePruningPointUTXOSetIfRequired() error
UpdatePruningPointIfRequired() error
PruneAllBlocksBelow(stagingArea *StagingArea, pruningPointHash *externalapi.DomainHash) error
}

View File

@@ -10,5 +10,6 @@ type TestConsensusStateManager interface {
model.ConsensusStateManager
AddUTXOToMultiset(multiset model.Multiset, entry externalapi.UTXOEntry,
outpoint *externalapi.DomainOutpoint) error
ResolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error)
ResolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
useSeparateStagingAreaPerBlock bool) (externalapi.BlockStatus, error)
}

View File

@@ -0,0 +1,9 @@
package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// UTXODiffReversalData is used by ConsensusStateManager to reverse the UTXODiffs during a re-org
type UTXODiffReversalData struct {
SelectedParentHash *externalapi.DomainHash
SelectedParentUTXODiff externalapi.UTXODiff
}

View File

@@ -1,24 +1,21 @@
package blockbuilder_test
import (
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/pkg/errors"
"testing"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/pkg/errors"
)
func TestBuildBlockErrorCases(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
testConsensus, teardown, err := factory.NewTestConsensus(
params, false, "TestBlockBuilderErrorCases")
testConsensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestBlockBuilderErrorCases")
if err != nil {
t.Fatalf("Error initializing consensus for: %+v", err)
}
@@ -36,7 +33,7 @@ func TestBuildBlockErrorCases(t *testing.T) {
"scriptPublicKey too long",
&externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: make([]byte, params.CoinbasePayloadScriptPublicKeyMaxLength+1),
Script: make([]byte, consensusConfig.CoinbasePayloadScriptPublicKeyMaxLength+1),
Version: 0,
},
ExtraData: nil,

View File

@@ -6,7 +6,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/pkg/errors"
)
@@ -119,7 +119,11 @@ func (bb *testBlockBuilder) buildBlockWithParents(stagingArea *model.StagingArea
*externalapi.DomainBlock, externalapi.UTXODiff, error) {
if coinbaseData == nil {
scriptPublicKey, _ := testutils.OpTrueScript()
scriptPublicKeyScript, err := txscript.PayToScriptHashScript([]byte{txscript.OpTrue})
if err != nil {
panic(errors.Wrapf(err, "Couldn't parse opTrueScript. This should never happen"))
}
scriptPublicKey := &externalapi.ScriptPublicKey{Script: scriptPublicKeyScript, Version: constants.MaxScriptPublicKeyVersion}
coinbaseData = &externalapi.DomainCoinbaseData{
ScriptPublicKey: scriptPublicKey,
ExtraData: []byte{},
@@ -143,7 +147,8 @@ func (bb *testBlockBuilder) buildBlockWithParents(stagingArea *model.StagingArea
return nil, nil, err
}
selectedParentStatus, err := bb.testConsensus.ConsensusStateManager().ResolveBlockStatus(stagingArea, ghostdagData.SelectedParent())
selectedParentStatus, err := bb.testConsensus.ConsensusStateManager().ResolveBlockStatus(
stagingArea, ghostdagData.SelectedParent(), false)
if err != nil {
return nil, nil, err
}

View File

@@ -3,6 +3,8 @@ package blockprocessor
import (
"fmt"
"github.com/kaspanet/kaspad/util/staging"
"github.com/kaspanet/kaspad/util/difficulty"
"github.com/kaspanet/kaspad/domain/consensus/model"
@@ -57,6 +59,22 @@ func (bp *blockProcessor) setBlockStatusAfterBlockValidation(
return nil
}
func (bp *blockProcessor) updateVirtualAcceptanceDataAfterImportingPruningPoint(stagingArea *model.StagingArea) error {
_, virtualAcceptanceData, virtualMultiset, err :=
bp.consensusStateManager.CalculatePastUTXOAndAcceptanceData(stagingArea, model.VirtualBlockHash)
if err != nil {
return err
}
log.Debugf("Staging virtual acceptance data after importing the pruning point")
bp.acceptanceDataStore.Stage(stagingArea, model.VirtualBlockHash, virtualAcceptanceData)
log.Debugf("Staging virtual multiset after importing the pruning point")
bp.multisetStore.Stage(stagingArea, model.VirtualBlockHash, virtualMultiset)
return nil
}
func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea, block *externalapi.DomainBlock,
isPruningPoint bool) (*externalapi.BlockInsertionResult, error) {
@@ -88,6 +106,7 @@ func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea,
var selectedParentChainChanges *externalapi.SelectedChainPath
var virtualUTXODiff externalapi.UTXODiff
var reversalData *model.UTXODiffReversalData
isHeaderOnlyBlock := isHeaderOnlyBlock(block)
if !isHeaderOnlyBlock {
// There's no need to update the consensus state manager when
@@ -95,7 +114,12 @@ func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea,
// in consensusStateManager.ImportPruningPoint
if !isPruningPoint {
// Attempt to add the block to the virtual
selectedParentChainChanges, virtualUTXODiff, err = bp.consensusStateManager.AddBlock(stagingArea, blockHash)
selectedParentChainChanges, virtualUTXODiff, reversalData, err = bp.consensusStateManager.AddBlock(stagingArea, blockHash)
if err != nil {
return nil, err
}
} else {
err := bp.updateVirtualAcceptanceDataAfterImportingPruningPoint(stagingArea)
if err != nil {
return nil, err
}
@@ -117,12 +141,19 @@ func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea,
}
}
err = bp.commitAllChanges(stagingArea)
err = staging.CommitAllChanges(bp.databaseContext, stagingArea)
if err != nil {
return nil, err
}
err = bp.pruningManager.UpdatePruningPointUTXOSetIfRequired()
if reversalData != nil {
err = bp.consensusStateManager.ReverseUTXODiffs(blockHash, reversalData)
if err != nil {
return nil, err
}
}
err = bp.pruningManager.UpdatePruningPointIfRequired()
if err != nil {
return nil, err
}
@@ -298,20 +329,3 @@ func (bp *blockProcessor) hasValidatedHeader(stagingArea *model.StagingArea, blo
return status != externalapi.StatusInvalid, nil
}
func (bp *blockProcessor) commitAllChanges(stagingArea *model.StagingArea) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "commitAllChanges")
defer onEnd()
dbTx, err := bp.databaseContext.Begin()
if err != nil {
return err
}
err = stagingArea.Commit(dbTx)
if err != nil {
return err
}
return dbTx.Commit()
}

View File

@@ -4,25 +4,22 @@ import (
"strings"
"testing"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/pkg/errors"
)
func TestBlockStatus(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestBlockStatus")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestBlockStatus")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
@@ -39,7 +36,7 @@ func TestBlockStatus(t *testing.T) {
}
}
tipHash := params.GenesisHash
tipHash := consensusConfig.GenesisHash
for i := 0; i < 2; i++ {
tipHash, _, err = tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
if err != nil {
@@ -56,7 +53,7 @@ func TestBlockStatus(t *testing.T) {
checkStatus(headerHash, externalapi.StatusHeaderOnly)
nonChainBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
nonChainBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
@@ -119,17 +116,17 @@ func TestValidateAndInsertErrors(t *testing.T) {
// Each test is covering the error cases in a sub-function in "validateAndInsertBlock" function.
// Currently, implemented only for some of the errors.
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestBlockStatus")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestBlockStatus")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
tipHash, emptyCoinbase, tx1 := initData(params)
tipHash, emptyCoinbase, tx1 := initData(consensusConfig)
// Tests all the error case on the function: "checkBlockStatus"(sub-function in function validateBlock)
blockWithStatusInvalid, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash},
blockWithStatusInvalid, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash},
&emptyCoinbase, []*externalapi.DomainTransaction{tx1, tx1})
if err != nil {
t.Fatalf("AddBlock: %+v", err)
@@ -184,8 +181,8 @@ func TestValidateAndInsertErrors(t *testing.T) {
})
}
func initData(params *dagconfig.Params) (*externalapi.DomainHash, externalapi.DomainCoinbaseData, *externalapi.DomainTransaction) {
return params.GenesisHash,
func initData(consensusConfig *consensus.Config) (*externalapi.DomainHash, externalapi.DomainCoinbaseData, *externalapi.DomainTransaction) {
return consensusConfig.GenesisHash,
externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,

View File

@@ -42,27 +42,27 @@ func addBlock(tcSyncer, tcSyncee testapi.TestConsensus, parentHashes []*external
}
func TestValidateAndInsertImportedPruningPoint(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// This is done to reduce the pruning depth to 6 blocks
finalityDepth := 3
params.FinalityDuration = time.Duration(finalityDepth) * params.TargetTimePerBlock
params.K = 0
consensusConfig.FinalityDuration = time.Duration(finalityDepth) * consensusConfig.TargetTimePerBlock
consensusConfig.K = 0
factory := consensus.NewFactory()
tcSyncer, teardownSyncer, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertPruningPointSyncer")
tcSyncer, teardownSyncer, err := factory.NewTestConsensus(consensusConfig, "TestValidateAndInsertPruningPointSyncer")
if err != nil {
t.Fatalf("Error setting up tcSyncer: %+v", err)
}
defer teardownSyncer(false)
tcSyncee, teardownSyncee, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertPruningPointSyncee")
tcSyncee, teardownSyncee, err := factory.NewTestConsensus(consensusConfig, "TestValidateAndInsertPruningPointSyncee")
if err != nil {
t.Fatalf("Error setting up tcSyncee: %+v", err)
}
defer teardownSyncee(false)
tipHash := params.GenesisHash
tipHash := consensusConfig.GenesisHash
for i := 0; i < finalityDepth-2; i++ {
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
}
@@ -83,7 +83,7 @@ func TestValidateAndInsertImportedPruningPoint(t *testing.T) {
t.Fatalf("PruningPoint: %+v", err)
}
if !pruningPoint.Equal(params.GenesisHash) {
if !pruningPoint.Equal(consensusConfig.GenesisHash) {
break
}
}
@@ -212,28 +212,28 @@ func TestValidateAndInsertImportedPruningPoint(t *testing.T) {
// IBD, while it already has a non-empty UTXO-Set originating from blocks mined on top of genesis - the resulting
// UTXO set is correct
func TestValidateAndInsertPruningPointWithSideBlocks(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// This is done to reduce the pruning depth to 6 blocks
finalityDepth := 3
params.FinalityDuration = time.Duration(finalityDepth) * params.TargetTimePerBlock
params.K = 0
consensusConfig.FinalityDuration = time.Duration(finalityDepth) * consensusConfig.TargetTimePerBlock
consensusConfig.K = 0
factory := consensus.NewFactory()
tcSyncer, teardownSyncer, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertPruningPointSyncer")
tcSyncer, teardownSyncer, err := factory.NewTestConsensus(consensusConfig, "TestValidateAndInsertPruningPointSyncer")
if err != nil {
t.Fatalf("Error setting up tcSyncer: %+v", err)
}
defer teardownSyncer(false)
tcSyncee, teardownSyncee, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertPruningPointSyncee")
tcSyncee, teardownSyncee, err := factory.NewTestConsensus(consensusConfig, "TestValidateAndInsertPruningPointSyncee")
if err != nil {
t.Fatalf("Error setting up tcSyncee: %+v", err)
}
defer teardownSyncee(false)
// Mine 2 block in the syncee on top of genesis
side, _, err := tcSyncee.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, &externalapi.DomainCoinbaseData{ScriptPublicKey: &externalapi.ScriptPublicKey{}, ExtraData: []byte{1, 2}}, nil)
side, _, err := tcSyncee.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, &externalapi.DomainCoinbaseData{ScriptPublicKey: &externalapi.ScriptPublicKey{}, ExtraData: []byte{1, 2}}, nil)
if err != nil {
t.Fatal(err)
}
@@ -242,7 +242,7 @@ func TestValidateAndInsertPruningPointWithSideBlocks(t *testing.T) {
t.Fatal(err)
}
tipHash := params.GenesisHash
tipHash := consensusConfig.GenesisHash
for i := 0; i < finalityDepth-2; i++ {
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
}
@@ -261,7 +261,7 @@ func TestValidateAndInsertPruningPointWithSideBlocks(t *testing.T) {
t.Fatalf("PruningPoint: %+v", err)
}
if !pruningPoint.Equal(params.GenesisHash) {
if !pruningPoint.Equal(consensusConfig.GenesisHash) {
break
}
}
@@ -421,16 +421,16 @@ func makeFakeUTXOs() []*externalapi.OutpointAndUTXOEntryPair {
}
func TestGetPruningPointUTXOs(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// This is done to reduce the pruning depth to 8 blocks
finalityDepth := 4
params.FinalityDuration = time.Duration(finalityDepth) * params.TargetTimePerBlock
params.K = 0
consensusConfig.FinalityDuration = time.Duration(finalityDepth) * consensusConfig.TargetTimePerBlock
consensusConfig.K = 0
params.BlockCoinbaseMaturity = 0
consensusConfig.BlockCoinbaseMaturity = 0
factory := consensus.NewFactory()
testConsensus, teardown, err := factory.NewTestConsensus(params, false, "TestGetPruningPointUTXOs")
testConsensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestGetPruningPointUTXOs")
if err != nil {
t.Fatalf("Error setting up testConsensus: %+v", err)
}
@@ -518,7 +518,7 @@ func TestGetPruningPointUTXOs(t *testing.T) {
if err != nil {
t.Fatalf("Error getting the pruning point: %+v", err)
}
if !pruningPoint.Equal(params.GenesisHash) {
if !pruningPoint.Equal(consensusConfig.GenesisHash) {
break
}
}
@@ -580,18 +580,18 @@ func TestGetPruningPointUTXOs(t *testing.T) {
}
func BenchmarkGetPruningPointUTXOs(b *testing.B) {
params := dagconfig.DevnetParams
consensusConfig := consensus.Config{Params: dagconfig.DevnetParams}
// This is done to reduce the pruning depth to 200 blocks
finalityDepth := 100
params.FinalityDuration = time.Duration(finalityDepth) * params.TargetTimePerBlock
params.K = 0
consensusConfig.FinalityDuration = time.Duration(finalityDepth) * consensusConfig.TargetTimePerBlock
consensusConfig.K = 0
params.SkipProofOfWork = true
params.BlockCoinbaseMaturity = 0
consensusConfig.SkipProofOfWork = true
consensusConfig.BlockCoinbaseMaturity = 0
factory := consensus.NewFactory()
testConsensus, teardown, err := factory.NewTestConsensus(&params, false, "TestGetPruningPointUTXOs")
testConsensus, teardown, err := factory.NewTestConsensus(&consensusConfig, "TestGetPruningPointUTXOs")
if err != nil {
b.Fatalf("Error setting up testConsensus: %+v", err)
}
@@ -671,7 +671,7 @@ func BenchmarkGetPruningPointUTXOs(b *testing.B) {
if err != nil {
b.Fatalf("Error getting the pruning point: %+v", err)
}
if !pruningPoint.Equal(params.GenesisHash) {
if !pruningPoint.Equal(consensusConfig.GenesisHash) {
break
}
}

View File

@@ -5,6 +5,7 @@ import (
"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/util/staging"
"github.com/pkg/errors"
)
@@ -70,7 +71,7 @@ func (bp *blockProcessor) validateBlock(stagingArea *model.StagingArea, block *e
stagingArea := model.NewStagingArea()
hash := consensushashing.BlockHash(block)
bp.blockStatusStore.Stage(stagingArea, hash, externalapi.StatusInvalid)
commitErr := bp.commitAllChanges(stagingArea)
commitErr := staging.CommitAllChanges(bp.databaseContext, stagingArea)
if commitErr != nil {
return commitErr
}

View File

@@ -11,26 +11,25 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/pkg/errors"
)
func TestCheckBlockIsNotPruned(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// This is done to reduce the pruning depth to 6 blocks
params.FinalityDuration = 2 * params.TargetTimePerBlock
params.K = 0
consensusConfig.FinalityDuration = 2 * consensusConfig.TargetTimePerBlock
consensusConfig.K = 0
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockIsNotPruned")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckBlockIsNotPruned")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
// Add blocks until the pruning point changes
tipHash := params.GenesisHash
tipHash := consensusConfig.GenesisHash
tipHash, _, err = tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
@@ -52,7 +51,7 @@ func TestCheckBlockIsNotPruned(t *testing.T) {
t.Fatalf("PruningPoint: %+v", err)
}
if !pruningPoint.Equal(params.GenesisHash) {
if !pruningPoint.Equal(consensusConfig.GenesisHash) {
break
}
}
@@ -76,20 +75,20 @@ func TestCheckBlockIsNotPruned(t *testing.T) {
}
func TestCheckParentBlockBodiesExist(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// This is done to reduce the pruning depth to 6 blocks
params.FinalityDuration = 2 * params.TargetTimePerBlock
params.K = 0
consensusConfig.FinalityDuration = 2 * consensusConfig.TargetTimePerBlock
consensusConfig.K = 0
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckParentBlockBodiesExist")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckParentBlockBodiesExist")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
headerHash, _, err := tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{params.GenesisHash})
headerHash, _, err := tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{consensusConfig.GenesisHash})
if err != nil {
t.Fatalf("AddUTXOInvalidHeader: %+v", err)
}
@@ -105,7 +104,7 @@ func TestCheckParentBlockBodiesExist(t *testing.T) {
}
// Add blocks until the pruning point changes
tipHash := params.GenesisHash
tipHash := consensusConfig.GenesisHash
anticonePruningBlock, err := tc.BuildUTXOInvalidBlock([]*externalapi.DomainHash{tipHash})
if err != nil {
t.Fatalf("BuildUTXOInvalidBlock: %+v", err)
@@ -131,7 +130,7 @@ func TestCheckParentBlockBodiesExist(t *testing.T) {
t.Fatalf("PruningPoint: %+v", err)
}
if !pruningPoint.Equal(params.GenesisHash) {
if !pruningPoint.Equal(consensusConfig.GenesisHash) {
break
}
}
@@ -146,20 +145,20 @@ func TestCheckParentBlockBodiesExist(t *testing.T) {
}
func TestIsFinalizedTransaction(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
stagingArea := model.NewStagingArea()
params.BlockCoinbaseMaturity = 0
consensusConfig.BlockCoinbaseMaturity = 0
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestIsFinalizedTransaction")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestIsFinalizedTransaction")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
// Build a small DAG
outerParents := []*externalapi.DomainHash{params.GenesisHash}
outerParents := []*externalapi.DomainHash{consensusConfig.GenesisHash}
for i := 0; i < 5; i++ {
var innerParents []*externalapi.DomainHash
for i := 0; i < 4; i++ {
@@ -199,7 +198,7 @@ func TestIsFinalizedTransaction(t *testing.T) {
}
checkForLockTimeAndSequence := func(lockTime, sequence uint64, shouldPass bool) {
tx, err := testutils.CreateTransaction(parentToSpend.Transactions[0])
tx, err := testutils.CreateTransaction(parentToSpend.Transactions[0], 1)
if err != nil {
t.Fatalf("Error creating tx: %+v", err)
}

View File

@@ -2,39 +2,37 @@ package blockvalidator_test
import (
"bytes"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"math"
"testing"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/pkg/errors"
)
func TestChainedTransactions(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
params.BlockCoinbaseMaturity = 0
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
consensusConfig.BlockCoinbaseMaturity = 0
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestChainedTransactions")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestChainedTransactions")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
@@ -49,12 +47,12 @@ func TestChainedTransactions(t *testing.T) {
t.Fatalf("Error getting block1: %+v", err)
}
tx1, err := testutils.CreateTransaction(block1.Transactions[0])
tx1, err := testutils.CreateTransaction(block1.Transactions[0], 1)
if err != nil {
t.Fatalf("Error creating tx1: %+v", err)
}
chainedTx, err := testutils.CreateTransaction(tx1)
chainedTx, err := testutils.CreateTransaction(tx1, 1)
if err != nil {
t.Fatalf("Error creating chainedTx: %+v", err)
}
@@ -76,7 +74,7 @@ func TestChainedTransactions(t *testing.T) {
t.Fatalf("Error getting block2: %+v", err)
}
tx2, err := testutils.CreateTransaction(block2.Transactions[0])
tx2, err := testutils.CreateTransaction(block2.Transactions[0], 1)
if err != nil {
t.Fatalf("Error creating tx2: %+v", err)
}
@@ -93,9 +91,9 @@ func TestChainedTransactions(t *testing.T) {
// TestCheckBlockSanity tests the CheckBlockSanity function to ensure it works
// as expected.
func TestCheckBlockSanity(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockSanity")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckBlockSanity")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
@@ -1000,15 +998,15 @@ var blockWithWrongTxOrder = externalapi.DomainBlock{
}
func TestCheckBlockHashMerkleRoot(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockHashMerkleRoot")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckBlockHashMerkleRoot")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
block, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
block, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("BuildBlockWithParents: %+v", err)
}
@@ -1030,16 +1028,16 @@ func TestCheckBlockHashMerkleRoot(t *testing.T) {
}
func TestBlockSize(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestBlockSize")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestBlockSize")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
block, _, err := initBlockWithInvalidBlockSize(params, tc)
block, _, err := initBlockWithInvalidBlockSize(consensusConfig, tc)
if err != nil {
t.Fatalf("Error BuildBlockWithParents : %+v", err)
}
@@ -1055,7 +1053,7 @@ func TestBlockSize(t *testing.T) {
})
}
func initBlockWithInvalidBlockSize(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainBlock, externalapi.UTXODiff, error) {
func initBlockWithInvalidBlockSize(consensusConfig *consensus.Config, tc testapi.TestConsensus) (*externalapi.DomainBlock, externalapi.UTXODiff, error) {
emptyCoinbase := externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,
@@ -1084,20 +1082,20 @@ func initBlockWithInvalidBlockSize(params *dagconfig.Params, tc testapi.TestCons
Payload: []byte{0x01},
}
return tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx})
return tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx})
}
func TestCheckBlockDuplicateTransactions(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockDuplicateTransactions")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckBlockDuplicateTransactions")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
block, _, err := initBlockWithDuplicateTransaction(params, tc)
block, _, err := initBlockWithDuplicateTransaction(consensusConfig, tc)
if err != nil {
t.Fatalf("Error BuildBlockWithParents : %+v", err)
}
@@ -1113,7 +1111,7 @@ func TestCheckBlockDuplicateTransactions(t *testing.T) {
})
}
func initBlockWithDuplicateTransaction(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainBlock, externalapi.UTXODiff, error) {
func initBlockWithDuplicateTransaction(consensusConfig *consensus.Config, tc testapi.TestConsensus) (*externalapi.DomainBlock, externalapi.UTXODiff, error) {
emptyCoinbase := externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,
@@ -1141,20 +1139,20 @@ func initBlockWithDuplicateTransaction(params *dagconfig.Params, tc testapi.Test
SubnetworkID: subnetworks.SubnetworkIDNative,
}
return tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx, tx})
return tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx, tx})
}
func TestCheckBlockContainsOnlyOneCoinbase(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockContainsOnlyOneCoinbase")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckBlockContainsOnlyOneCoinbase")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
block, _, err := initBlockWithMoreThanOneCoinbase(params, tc)
block, _, err := initBlockWithMoreThanOneCoinbase(consensusConfig, tc)
if err != nil {
t.Fatalf("Error BuildBlockWithParents : %+v", err)
}
@@ -1170,7 +1168,7 @@ func TestCheckBlockContainsOnlyOneCoinbase(t *testing.T) {
})
}
func initBlockWithMoreThanOneCoinbase(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainBlock, externalapi.UTXODiff, error) {
func initBlockWithMoreThanOneCoinbase(consensusConfig *consensus.Config, tc testapi.TestConsensus) (*externalapi.DomainBlock, externalapi.UTXODiff, error) {
emptyCoinbase := externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,
@@ -1198,20 +1196,20 @@ func initBlockWithMoreThanOneCoinbase(params *dagconfig.Params, tc testapi.TestC
SubnetworkID: subnetworks.SubnetworkIDCoinbase,
}
return tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx})
return tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx})
}
func TestCheckBlockDoubleSpends(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockDoubleSpends")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckBlockDoubleSpends")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
block, _, err := initBlockWithDoubleSpends(params, tc)
block, _, err := initBlockWithDoubleSpends(consensusConfig, tc)
if err != nil {
t.Fatalf("Error BuildBlockWithParents : %+v", err)
}
@@ -1227,7 +1225,7 @@ func TestCheckBlockDoubleSpends(t *testing.T) {
})
}
func initBlockWithDoubleSpends(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainBlock, externalapi.UTXODiff, error) {
func initBlockWithDoubleSpends(consensusConfig *consensus.Config, tc testapi.TestConsensus) (*externalapi.DomainBlock, externalapi.UTXODiff, error) {
emptyCoinbase := externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,
@@ -1273,21 +1271,21 @@ func initBlockWithDoubleSpends(params *dagconfig.Params, tc testapi.TestConsensu
SubnetworkID: subnetworks.SubnetworkIDNative,
}
return tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash},
return tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash},
&emptyCoinbase, []*externalapi.DomainTransaction{tx, txSameOutpoint})
}
func TestCheckFirstBlockTransactionIsCoinbase(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckFirstBlockTransactionIsCoinbase")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckFirstBlockTransactionIsCoinbase")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
block := initBlockWithFirstTransactionDifferentThanCoinbase(params)
block := initBlockWithFirstTransactionDifferentThanCoinbase(consensusConfig)
blockHash := consensushashing.BlockHash(block)
stagingArea := model.NewStagingArea()
tc.BlockStore().Stage(stagingArea, blockHash, block)
@@ -1300,7 +1298,7 @@ func TestCheckFirstBlockTransactionIsCoinbase(t *testing.T) {
})
}
func initBlockWithFirstTransactionDifferentThanCoinbase(params *dagconfig.Params) *externalapi.DomainBlock {
func initBlockWithFirstTransactionDifferentThanCoinbase(consensusConfig *consensus.Config) *externalapi.DomainBlock {
prevOutTxID := &externalapi.DomainTransactionID{}
prevOutPoint := externalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1}
txInput := externalapi.DomainTransactionInput{
@@ -1320,7 +1318,7 @@ func initBlockWithFirstTransactionDifferentThanCoinbase(params *dagconfig.Params
return &externalapi.DomainBlock{
Header: blockheader.NewImmutableBlockHeader(
constants.MaxBlockVersion,
[]*externalapi.DomainHash{params.GenesisHash},
[]*externalapi.DomainHash{consensusConfig.GenesisHash},
merkle.CalculateHashMerkleRoot([]*externalapi.DomainTransaction{tx}),
&externalapi.DomainHash{},
externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{

View File

@@ -12,13 +12,12 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
)
func TestValidateMedianTime(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestValidateMedianTime")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestValidateMedianTime")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
@@ -62,8 +61,8 @@ func TestValidateMedianTime(t *testing.T) {
return pastMedianTime
}
tip := params.GenesisBlock
tipHash := params.GenesisHash
tip := consensusConfig.GenesisBlock
tipHash := consensusConfig.GenesisHash
blockTime := tip.Header.TimeInMilliseconds()
@@ -84,15 +83,15 @@ func TestValidateMedianTime(t *testing.T) {
}
func TestCheckParentsIncest(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckParentsIncest")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckParentsIncest")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
a, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
a, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
@@ -102,7 +101,7 @@ func TestCheckParentsIncest(t *testing.T) {
t.Fatalf("AddBlock: %+v", err)
}
c, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
c, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
@@ -129,7 +128,7 @@ func TestCheckParentsIncest(t *testing.T) {
indirectParentsRelationBlock := &externalapi.DomainBlock{
Header: blockheader.NewImmutableBlockHeader(
0,
[]*externalapi.DomainHash{params.GenesisHash, b},
[]*externalapi.DomainHash{consensusConfig.GenesisHash, b},
&externalapi.DomainHash{},
&externalapi.DomainHash{},
&externalapi.DomainHash{},
@@ -154,25 +153,25 @@ func TestCheckParentsIncest(t *testing.T) {
}
func TestCheckMergeSizeLimit(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
params.MergeSetSizeLimit = 2 * uint64(params.K)
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
consensusConfig.MergeSetSizeLimit = 2 * uint64(consensusConfig.K)
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckParentsIncest")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckParentsIncest")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
chain1TipHash := params.GenesisHash
for i := uint64(0); i < params.MergeSetSizeLimit+2; i++ {
chain1TipHash := consensusConfig.GenesisHash
for i := uint64(0); i < consensusConfig.MergeSetSizeLimit+2; i++ {
chain1TipHash, _, err = tc.AddBlock([]*externalapi.DomainHash{chain1TipHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
}
chain2TipHash := params.GenesisHash
for i := uint64(0); i < params.MergeSetSizeLimit+1; i++ {
chain2TipHash := consensusConfig.GenesisHash
for i := uint64(0); i < consensusConfig.MergeSetSizeLimit+1; i++ {
chain2TipHash, _, err = tc.AddBlock([]*externalapi.DomainHash{chain2TipHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)

View File

@@ -1,6 +1,8 @@
package blockvalidator_test
import (
"testing"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
@@ -8,24 +10,22 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"testing"
)
func TestCheckParentsLimit(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckParentsLimit")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckParentsLimit")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
for i := model.KType(0); i < params.MaxBlockParents+1; i++ {
_, _, err = tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
for i := model.KType(0); i < consensusConfig.MaxBlockParents+1; i++ {
_, _, err = tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
@@ -44,16 +44,16 @@ func TestCheckParentsLimit(t *testing.T) {
}
func TestCheckBlockVersion(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockVersion")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckBlockVersion")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
block, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
block, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("BuildBlockWithParents: %+v", err)
}
@@ -77,23 +77,23 @@ func TestCheckBlockVersion(t *testing.T) {
}
func TestCheckBlockTimestampInIsolation(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockTimestampInIsolation")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckBlockTimestampInIsolation")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
block, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
block, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("BuildBlockWithParents: %+v", err)
}
// Give 10 seconds slack to take care of the test duration
timestamp := mstime.Now().UnixMilliseconds() +
int64(params.TimestampDeviationTolerance)*params.TargetTimePerBlock.Milliseconds() + 10_000
int64(consensusConfig.TimestampDeviationTolerance)*consensusConfig.TargetTimePerBlock.Milliseconds() + 10_000
block.Header = blockheader.NewImmutableBlockHeader(
block.Header.Version(),

View File

@@ -3,9 +3,9 @@ package blockvalidator
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/model/pow"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/pow"
"github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/difficulty"

View File

@@ -7,36 +7,34 @@ import (
"testing"
"time"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/pow"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
"github.com/kaspanet/kaspad/domain/consensus/utils/mining"
"github.com/kaspanet/kaspad/util/difficulty"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/pow"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util/difficulty"
"github.com/pkg/errors"
)
// TestPOW tests the validation of the block's POW.
func TestPOW(t *testing.T) {
// We set the flag "skip pow" to be false (second argument in the function) for not skipping the check of POW and validate its correctness.
testutils.ForAllNets(t, false, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, false, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestPOW")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestPOW")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
// Builds and checks block with invalid POW.
invalidBlockWrongPOW, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
invalidBlockWrongPOW, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -46,12 +44,12 @@ func TestPOW(t *testing.T) {
t.Fatalf("Expected block to be invalid with err: %v, instead found: %v", ruleerrors.ErrInvalidPoW, err)
}
abovePowMaxBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
abovePowMaxBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatal(err)
}
abovePowMaxTarget := big.NewInt(0).Add(big.NewInt(1), params.PowMax)
abovePowMaxTarget := big.NewInt(0).Add(big.NewInt(1), consensusConfig.PowMax)
abovePowMaxBlock.Header = blockheader.NewImmutableBlockHeader(
abovePowMaxBlock.Header.Version(),
abovePowMaxBlock.Header.ParentHashes(),
@@ -68,7 +66,7 @@ func TestPOW(t *testing.T) {
t.Fatalf("Unexpected error: %+v", err)
}
negativeTargetBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
negativeTargetBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -90,7 +88,7 @@ func TestPOW(t *testing.T) {
}
// test on a valid block.
validBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
validBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -120,16 +118,16 @@ func solveBlockWithWrongPOW(block *externalapi.DomainBlock) *externalapi.DomainB
}
func TestCheckParentHeadersExist(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckParentHeadersExist")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckParentHeadersExist")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
orphanBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
orphanBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -159,7 +157,7 @@ func TestCheckParentHeadersExist(t *testing.T) {
}
invalidBlock, _, err := tc.BuildBlockWithParents(
[]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
[]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -183,7 +181,7 @@ func TestCheckParentHeadersExist(t *testing.T) {
invalidBlockHash := consensushashing.BlockHash(invalidBlock)
invalidBlockChild, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
invalidBlockChild, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -207,21 +205,21 @@ func TestCheckParentHeadersExist(t *testing.T) {
}
func TestCheckPruningPointViolation(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// This is done to reduce the pruning depth to 6 blocks
params.FinalityDuration = 2 * params.TargetTimePerBlock
params.K = 0
consensusConfig.FinalityDuration = 2 * consensusConfig.TargetTimePerBlock
consensusConfig.K = 0
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckPruningPointViolation")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckPruningPointViolation")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
// Add blocks until the pruning point changes
tipHash := params.GenesisHash
tipHash := consensusConfig.GenesisHash
for {
tipHash, _, err = tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
if err != nil {
@@ -233,12 +231,12 @@ func TestCheckPruningPointViolation(t *testing.T) {
t.Fatalf("PruningPoint: %+v", err)
}
if !pruningPoint.Equal(params.GenesisHash) {
if !pruningPoint.Equal(consensusConfig.GenesisHash) {
break
}
}
_, _, err = tc.AddUTXOInvalidBlock([]*externalapi.DomainHash{params.GenesisHash})
_, _, err = tc.AddUTXOInvalidBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash})
if !errors.Is(err, ruleerrors.ErrPruningPointViolation) {
t.Fatalf("Unexpected error: %+v", err)
}
@@ -251,7 +249,7 @@ func TestCheckPruningPointViolation(t *testing.T) {
// "calculated" by the mocDifficultyManager, where mocDifficultyManager is special implementation
// of the type DifficultyManager for this test (defined below).
func TestValidateDifficulty(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
mocDifficulty := &mocDifficultyManager{}
factory.SetTestDifficultyManager(func(_ model.DBReader, _ model.GHOSTDAGManager, _ model.GHOSTDAGDataStore,
@@ -262,10 +260,10 @@ func TestValidateDifficulty(t *testing.T) {
mocDifficulty.daaBlocksStore = daaBlocksStore
return mocDifficulty
})
genesisDifficulty := params.GenesisBlock.Header.Bits()
genesisDifficulty := consensusConfig.GenesisBlock.Header.Bits()
mocDifficulty.testDifficulty = genesisDifficulty
mocDifficulty.testGenesisBits = genesisDifficulty
tc, teardown, err := factory.NewTestConsensus(params, false, "TestValidateDifficulty")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestValidateDifficulty")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
@@ -277,7 +275,7 @@ func TestValidateDifficulty(t *testing.T) {
Version: 0,
},
}
block, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, &emptyCoinbase, nil)
block, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{consensusConfig.GenesisHash}, &emptyCoinbase, nil)
if err != nil {
t.Fatalf("TestValidateDifficulty: Failed build block with parents: %v.", err)
}

View File

@@ -9,16 +9,19 @@ import (
// AddBlock submits the given block to be added to the
// current virtual. This process may result in a new virtual block
// getting created
func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, externalapi.UTXODiff, error) {
func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (
*externalapi.SelectedChainPath, externalapi.UTXODiff, *model.UTXODiffReversalData, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "csm.AddBlock")
defer onEnd()
log.Debugf("Resolving whether the block %s is the next virtual selected parent", blockHash)
isCandidateToBeNextVirtualSelectedParent, err := csm.isCandidateToBeNextVirtualSelectedParent(stagingArea, blockHash)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
var reversalData *model.UTXODiffReversalData
if isCandidateToBeNextVirtualSelectedParent {
// It's important to check for finality violation before resolving the block status, because the status of
// blocks with a selected chain that doesn't contain the pruning point cannot be resolved because they will
@@ -27,7 +30,7 @@ func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, block
"finality", blockHash)
isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(stagingArea, blockHash)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
if shouldNotify {
@@ -37,9 +40,10 @@ func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, block
if !isViolatingFinality {
log.Debugf("Block %s doesn't violate finality. Resolving its block status", blockHash)
blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash)
var blockStatus externalapi.BlockStatus
blockStatus, reversalData, err = csm.resolveBlockStatus(stagingArea, blockHash, true)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
log.Debugf("Block %s resolved to status `%s`", blockHash, blockStatus)
@@ -52,17 +56,17 @@ func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, block
log.Debugf("Adding block %s to the DAG tips", blockHash)
newTips, err := csm.addTip(stagingArea, blockHash)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
log.Debugf("After adding %s, the amount of new tips are %d", blockHash, len(newTips))
log.Debugf("Updating the virtual with the new tips")
selectedParentChainChanges, virtualUTXODiff, err := csm.updateVirtual(stagingArea, blockHash, newTips)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
return selectedParentChainChanges, virtualUTXODiff, nil
return selectedParentChainChanges, virtualUTXODiff, reversalData, nil
}
func (csm *consensusStateManager) isCandidateToBeNextVirtualSelectedParent(

View File

@@ -10,20 +10,19 @@ import (
"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/dagconfig"
)
func TestVirtualDiff(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestVirtualDiff")
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestVirtualDiff")
if err != nil {
t.Fatalf("Error setting up tc: %+v", err)
}
defer teardown(false)
// Add block A over the genesis
blockAHash, blockInsertionResult, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
blockAHash, blockInsertionResult, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
if err != nil {
t.Fatalf("Error adding block A: %+v", err)
}

View File

@@ -39,14 +39,26 @@ func (csm *consensusStateManager) CalculatePastUTXOAndAcceptanceData(stagingArea
return nil, nil, nil, err
}
daaScore, err := csm.daaBlocksStore.DAAScore(csm.databaseContext, stagingArea, blockHash)
log.Debugf("Restored the past UTXO of block %s with selectedParent %s. "+
"Diff toAdd length: %d, toRemove length: %d", blockHash, blockGHOSTDAGData.SelectedParent(),
selectedParentPastUTXO.ToAdd().Len(), selectedParentPastUTXO.ToRemove().Len())
return csm.calculatePastUTXOAndAcceptanceDataWithSelectedParentUTXO(stagingArea, blockHash, selectedParentPastUTXO)
}
func (csm *consensusStateManager) calculatePastUTXOAndAcceptanceDataWithSelectedParentUTXO(stagingArea *model.StagingArea,
blockHash *externalapi.DomainHash, selectedParentPastUTXO externalapi.UTXODiff) (
externalapi.UTXODiff, externalapi.AcceptanceData, model.Multiset, error) {
blockGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, blockHash)
if err != nil {
return nil, nil, nil, err
}
log.Debugf("Restored the past UTXO of block %s with selectedParent %s. "+
"Diff toAdd length: %d, toRemove length: %d", blockHash, blockGHOSTDAGData.SelectedParent(),
selectedParentPastUTXO.ToAdd().Len(), selectedParentPastUTXO.ToRemove().Len())
daaScore, err := csm.daaBlocksStore.DAAScore(csm.databaseContext, stagingArea, blockHash)
if err != nil {
return nil, nil, nil, err
}
log.Debugf("Applying blue blocks to the selected parent past UTXO of block %s", blockHash)
acceptanceData, utxoDiff, err := csm.applyMergeSetBlocks(
@@ -66,7 +78,7 @@ func (csm *consensusStateManager) CalculatePastUTXOAndAcceptanceData(stagingArea
}
func (csm *consensusStateManager) restorePastUTXO(
stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.MutableUTXODiff, error) {
stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.UTXODiff, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "restorePastUTXO")
defer onEnd()
@@ -120,11 +132,11 @@ func (csm *consensusStateManager) restorePastUTXO(
}
log.Tracef("The accumulated diff for block %s is: %s", blockHash, accumulatedDiff)
return accumulatedDiff, nil
return accumulatedDiff.ToImmutable(), nil
}
func (csm *consensusStateManager) applyMergeSetBlocks(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
selectedParentPastUTXODiff externalapi.MutableUTXODiff, ghostdagData *model.BlockGHOSTDAGData, daaScore uint64) (
selectedParentPastUTXODiff externalapi.UTXODiff, ghostdagData *model.BlockGHOSTDAGData, daaScore uint64) (
externalapi.AcceptanceData, externalapi.MutableUTXODiff, error) {
log.Debugf("applyMergeSetBlocks start for block %s", blockHash)
@@ -144,7 +156,7 @@ func (csm *consensusStateManager) applyMergeSetBlocks(stagingArea *model.Staging
log.Tracef("The past median time for block %s is: %d", blockHash, selectedParentMedianTime)
multiblockAcceptanceData := make(externalapi.AcceptanceData, len(mergeSetBlocks))
accumulatedUTXODiff := selectedParentPastUTXODiff
accumulatedUTXODiff := selectedParentPastUTXODiff.CloneMutable()
accumulatedMass := uint64(0)
for i, mergeSetBlock := range mergeSetBlocks {
@@ -277,12 +289,13 @@ func (csm *consensusStateManager) checkTransactionMass(
}
// RestorePastUTXOSetIterator restores the given block's UTXOSet iterator, and returns it as a externalapi.ReadOnlyUTXOSetIterator
func (csm *consensusStateManager) RestorePastUTXOSetIterator(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.ReadOnlyUTXOSetIterator, error) {
func (csm *consensusStateManager) RestorePastUTXOSetIterator(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (
externalapi.ReadOnlyUTXOSetIterator, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "RestorePastUTXOSetIterator")
defer onEnd()
blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash)
blockStatus, _, err := csm.resolveBlockStatus(stagingArea, blockHash, true)
if err != nil {
return nil, err
}
@@ -306,5 +319,5 @@ func (csm *consensusStateManager) RestorePastUTXOSetIterator(stagingArea *model.
return nil, err
}
return utxo.IteratorWithDiff(virtualUTXOSetIterator, blockDiff.ToImmutable())
return utxo.IteratorWithDiff(virtualUTXOSetIterator, blockDiff)
}

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