mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-22 19:45:36 +00:00
Compare commits
24 Commits
v0.10.0-al
...
ghostdagRe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16a83f9bac | ||
|
|
7d20ee6b58 | ||
|
|
1ab05c3fbc | ||
|
|
347dd8fc4b | ||
|
|
d2cccd2829 | ||
|
|
7186f83095 | ||
|
|
5c394c2951 | ||
|
|
a786cdc15e | ||
|
|
6dd3d4a9e7 | ||
|
|
73b36f12f0 | ||
|
|
a795a9e619 | ||
|
|
0be1bba408 | ||
|
|
6afc06ce58 | ||
|
|
d01a213f3d | ||
|
|
7ad8ce521c | ||
|
|
86ba80a091 | ||
|
|
088e2114c2 | ||
|
|
2854d91688 | ||
|
|
af10b59181 | ||
|
|
c5b0394bbc | ||
|
|
9266d179a9 | ||
|
|
321792778e | ||
|
|
70f3fa9893 | ||
|
|
4e18031483 |
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ type fakeRelayInvsContext struct {
|
||||
rwLock sync.RWMutex
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetBlockChildren(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
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))
|
||||
}
|
||||
|
||||
|
||||
@@ -69,12 +69,12 @@ func (m *Manager) NotifyBlockAddedToDAG(block *externalapi.DomainBlock, blockIns
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,10 @@ package rpccontext
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
difficultyPackage "github.com/kaspanet/kaspad/util/difficulty"
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
|
||||
@@ -26,79 +21,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 +28,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 +39,125 @@ 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),
|
||||
SelectedParentHash: selectedParentHash.String(),
|
||||
IsHeaderOnly: blockInfo.BlockStatus == externalapi.StatusHeaderOnly,
|
||||
BlueScore: blockInfo.BlueScore,
|
||||
}
|
||||
|
||||
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().GetBlock(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
|
||||
}
|
||||
|
||||
@@ -26,10 +26,12 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
|
||||
errorMessage.Error = appmessage.RPCErrorf("Block %s not found", hash)
|
||||
return errorMessage, nil
|
||||
}
|
||||
block := &externalapi.DomainBlock{Header: header}
|
||||
|
||||
response := appmessage.NewGetBlockResponseMessage()
|
||||
response.Block = appmessage.DomainBlockToRPCBlock(block)
|
||||
|
||||
blockVerboseData, err := context.BuildBlockVerboseData(header, nil, getBlockRequest.IncludeTransactionVerboseData)
|
||||
err = context.PopulateBlockWithVerboseData(response.Block, header, nil, getBlockRequest.IncludeTransactionVerboseData)
|
||||
if err != nil {
|
||||
if errors.Is(err, rpccontext.ErrBuildBlockVerboseDataInvalidBlock) {
|
||||
errorMessage := &appmessage.GetBlockResponseMessage{}
|
||||
@@ -39,7 +41,5 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response.BlockVerboseData = blockVerboseData
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,23 @@ 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 {
|
||||
blocks := 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 := &externalapi.DomainBlock{Header: blockHeader}
|
||||
blocks[i] = appmessage.DomainBlockToRPCBlock(block)
|
||||
err = context.PopulateBlockWithVerboseData(blocks[i], blockHeader, nil, getBlocksRequest.IncludeTransactionVerboseData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response.BlockVerboseData[i] = blockVerboseData
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
9
cmd/genkeypair/README.md
Normal file
9
cmd/genkeypair/README.md
Normal 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
26
cmd/genkeypair/config.go
Normal 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
27
cmd/genkeypair/main.go
Normal 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)
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -114,13 +114,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 +133,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 +192,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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -2,17 +2,28 @@ 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.KeysFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
34
cmd/kaspawallet/broadcast.go
Normal file
34
cmd/kaspawallet/broadcast.go
Normal 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
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -19,3 +21,12 @@ 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
198
cmd/kaspawallet/config.go
Normal 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
|
||||
}
|
||||
}
|
||||
69
cmd/kaspawallet/create.go
Normal file
69
cmd/kaspawallet/create.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
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.KeysFile, encryptedPrivateKeys, publicKeys, conf.MinimumSignatures, conf.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keysFile, err := keys.ReadKeysFile(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
|
||||
}
|
||||
61
cmd/kaspawallet/create_unsigned_tx.go
Normal file
61
cmd/kaspawallet/create_unsigned_tx.go
Normal file
@@ -0,0 +1,61 @@
|
||||
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.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
|
||||
}
|
||||
69
cmd/kaspawallet/dump_unencrypted_data.go
Normal file
69
cmd/kaspawallet/dump_unencrypted_data.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
func dumpUnencryptedData(conf *dumpUnencryptedDataConfig) error {
|
||||
err := confirmDump()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keysFile, err := keys.ReadKeysFile(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
|
||||
}
|
||||
110
cmd/kaspawallet/keys/create.go
Normal file
110
cmd/kaspawallet/keys/create.go
Normal 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
|
||||
}
|
||||
41
cmd/kaspawallet/keys/get_password.go
Normal file
41
cmd/kaspawallet/keys/get_password.go
Normal 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(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(syscall.Stdin, initialTermState)
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
// Now get the password.
|
||||
fmt.Print(prompt)
|
||||
p, err := term.ReadPassword(syscall.Stdin)
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Stop looking for ^C on the channel.
|
||||
signal.Stop(c)
|
||||
|
||||
return p
|
||||
}
|
||||
254
cmd/kaspawallet/keys/keys.go
Normal file
254
cmd/kaspawallet/keys/keys.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/cipher"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultAppDir = util.AppDir("kaspawallet", false)
|
||||
defaultKeysFile = filepath.Join(defaultAppDir, "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(path string) (*Data, error) {
|
||||
if path == "" {
|
||||
path = defaultKeysFile
|
||||
}
|
||||
|
||||
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, 0600)
|
||||
}
|
||||
|
||||
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(path string,
|
||||
encryptedPrivateKeys []*EncryptedPrivateKey,
|
||||
publicKeys [][]byte,
|
||||
minimumSignatures uint32,
|
||||
ecdsa bool) error {
|
||||
if path == "" {
|
||||
path = defaultKeysFile
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
93
cmd/kaspawallet/libkaspawallet/keypair.go
Normal file
93
cmd/kaspawallet/libkaspawallet/keypair.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
273
cmd/kaspawallet/libkaspawallet/serialization/serialization.go
Normal file
273
cmd/kaspawallet/libkaspawallet/serialization/serialization.go
Normal 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),
|
||||
}
|
||||
}
|
||||
146
cmd/kaspawallet/libkaspawallet/sign.go
Normal file
146
cmd/kaspawallet/libkaspawallet/sign.go
Normal 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
|
||||
}
|
||||
206
cmd/kaspawallet/libkaspawallet/transaction.go
Normal file
206
cmd/kaspawallet/libkaspawallet/transaction.go
Normal 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
|
||||
}
|
||||
291
cmd/kaspawallet/libkaspawallet/transaction_test.go
Normal file
291
cmd/kaspawallet/libkaspawallet/transaction_test.go
Normal file
@@ -0,0 +1,291 @@
|
||||
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/domain/dagconfig"
|
||||
"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, params *dagconfig.Params) {
|
||||
forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
tc, teardown, err := consensus.NewFactory().NewTestConsensus(params, false, "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(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{params.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, params *dagconfig.Params) {
|
||||
forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
tc, teardown, err := consensus.NewFactory().NewTestConsensus(params, false, "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(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{params.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
33
cmd/kaspawallet/main.go
Normal 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)
|
||||
}
|
||||
}
|
||||
170
cmd/kaspawallet/send.go
Normal file
170
cmd/kaspawallet/send.go
Normal file
@@ -0,0 +1,170 @@
|
||||
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.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
|
||||
}
|
||||
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, 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
|
||||
}
|
||||
22
cmd/kaspawallet/show_address.go
Normal file
22
cmd/kaspawallet/show_address.go
Normal file
@@ -0,0 +1,22 @@
|
||||
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.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
|
||||
}
|
||||
44
cmd/kaspawallet/sign.go
Normal file
44
cmd/kaspawallet/sign.go
Normal file
@@ -0,0 +1,44 @@
|
||||
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.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
|
||||
}
|
||||
@@ -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`
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -166,7 +166,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 +177,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) {
|
||||
|
||||
@@ -9,7 +9,7 @@ type Consensus interface {
|
||||
GetBlock(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)
|
||||
|
||||
@@ -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,
|
||||
useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error)
|
||||
}
|
||||
|
||||
@@ -143,7 +143,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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -117,7 +119,7 @@ func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea,
|
||||
}
|
||||
}
|
||||
|
||||
err = bp.commitAllChanges(stagingArea)
|
||||
err = staging.CommitAllChanges(bp.databaseContext, stagingArea)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -298,20 +300,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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -199,7 +199,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)
|
||||
}
|
||||
|
||||
@@ -49,12 +49,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 +76,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)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ 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)
|
||||
blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ func (csm *consensusStateManager) RestorePastUTXOSetIterator(stagingArea *model.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestUTXOCommitment(t *testing.T) {
|
||||
checkBlockUTXOCommitment(t, consensus, blockCHash, "C")
|
||||
// Block D:
|
||||
blockDTransaction, err := testutils.CreateTransaction(
|
||||
blockB.Transactions[transactionhelper.CoinbaseTransactionIndex])
|
||||
blockB.Transactions[transactionhelper.CoinbaseTransactionIndex], 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating transaction: %+v", err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package consensusstatemanager
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/staging"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
@@ -10,8 +12,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (csm *consensusStateManager) resolveBlockStatus(
|
||||
stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error) {
|
||||
func (csm *consensusStateManager) resolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
|
||||
useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error) {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, fmt.Sprintf("resolveBlockStatus for %s", blockHash))
|
||||
defer onEnd()
|
||||
@@ -50,19 +52,32 @@ func (csm *consensusStateManager) resolveBlockStatus(
|
||||
for i := len(unverifiedBlocks) - 1; i >= 0; i-- {
|
||||
unverifiedBlockHash := unverifiedBlocks[i]
|
||||
|
||||
stagingAreaForCurrentBlock := stagingArea
|
||||
useSeparateStagingArea := useSeparateStagingAreasPerBlock && (i != 0)
|
||||
if useSeparateStagingArea {
|
||||
stagingAreaForCurrentBlock = model.NewStagingArea()
|
||||
}
|
||||
|
||||
if selectedParentStatus == externalapi.StatusDisqualifiedFromChain {
|
||||
blockStatus = externalapi.StatusDisqualifiedFromChain
|
||||
} else {
|
||||
blockStatus, err = csm.resolveSingleBlockStatus(stagingArea, unverifiedBlockHash)
|
||||
blockStatus, err = csm.resolveSingleBlockStatus(stagingAreaForCurrentBlock, unverifiedBlockHash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
csm.blockStatusStore.Stage(stagingArea, unverifiedBlockHash, blockStatus)
|
||||
csm.blockStatusStore.Stage(stagingAreaForCurrentBlock, unverifiedBlockHash, blockStatus)
|
||||
selectedParentStatus = blockStatus
|
||||
log.Debugf("Block %s status resolved to `%s`, finished %d/%d of unverified blocks",
|
||||
unverifiedBlockHash, blockStatus, len(unverifiedBlocks)-i, len(unverifiedBlocks))
|
||||
|
||||
if useSeparateStagingArea {
|
||||
err := staging.CommitAllChanges(csm.databaseContext, stagingAreaForCurrentBlock)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blockStatus, nil
|
||||
|
||||
@@ -51,11 +51,11 @@ func TestDoubleSpends(t *testing.T) {
|
||||
fundingTransaction := fundingBlock.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
|
||||
// Create two transactions that spends the same output, but with different IDs
|
||||
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction)
|
||||
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating spendingTransaction1: %+v", err)
|
||||
}
|
||||
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction)
|
||||
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating spendingTransaction2: %+v", err)
|
||||
}
|
||||
@@ -147,7 +147,8 @@ func TestDoubleSpends(t *testing.T) {
|
||||
t.Fatalf("Error adding goodBlock: %+v", err)
|
||||
}
|
||||
//use ResolveBlockStatus, since goodBlock2 might not be the selectedTip
|
||||
goodBlock2Status, err := consensus.ConsensusStateManager().ResolveBlockStatus(stagingArea, goodBlock2Hash)
|
||||
goodBlock2Status, err := consensus.ConsensusStateManager().ResolveBlockStatus(
|
||||
stagingArea, goodBlock2Hash, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting status of goodBlock: %+v", err)
|
||||
}
|
||||
@@ -213,7 +214,7 @@ func TestTransactionAcceptance(t *testing.T) {
|
||||
|
||||
fundingTransaction2 := fundingBlock3.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
|
||||
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction1)
|
||||
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction1, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating spendingTransaction1: %+v", err)
|
||||
}
|
||||
@@ -223,7 +224,7 @@ func TestTransactionAcceptance(t *testing.T) {
|
||||
t.Fatalf("Error getting UTXOEntry for spendingTransaction1: %s", err)
|
||||
}
|
||||
|
||||
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction2)
|
||||
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction2, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating spendingTransaction1: %+v", err)
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ func (csm *testConsensusStateManager) AddUTXOToMultiset(
|
||||
return addUTXOToMultiset(multiset, entry, outpoint)
|
||||
}
|
||||
|
||||
func (csm *testConsensusStateManager) ResolveBlockStatus(stagingArea *model.StagingArea,
|
||||
blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error) {
|
||||
func (csm *testConsensusStateManager) ResolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
|
||||
useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error) {
|
||||
|
||||
return csm.resolveBlockStatus(stagingArea, blockHash)
|
||||
return csm.resolveBlockStatus(stagingArea, blockHash, useSeparateStagingAreasPerBlock)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package ghostdag2
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"sort"
|
||||
|
||||
"math/big"
|
||||
|
||||
@@ -19,7 +18,7 @@ type ghostdagHelper struct {
|
||||
headerStore model.BlockHeaderStore
|
||||
}
|
||||
|
||||
// New creates a new instance of this alternative ghostdag impl
|
||||
// New creates a new instance of this alternative GHOSTDAG impl
|
||||
func New(
|
||||
databaseContext model.DBReader,
|
||||
dagTopologyManager model.DAGTopologyManager,
|
||||
@@ -36,53 +35,60 @@ func New(
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------- */
|
||||
|
||||
func (gh *ghostdagHelper) GHOSTDAG(stagingArea *model.StagingArea, blockCandidate *externalapi.DomainHash) error {
|
||||
myWork := new(big.Int)
|
||||
maxWork := new(big.Int)
|
||||
var myScore uint64
|
||||
var spScore uint64
|
||||
/* find the selectedParent */
|
||||
blockParents, err := gh.dagTopologyManager.Parents(stagingArea, blockCandidate)
|
||||
var blueScore uint64 = 0
|
||||
var blueWork = new(big.Int)
|
||||
blueWork.SetUint64(0)
|
||||
var selectedParent *externalapi.DomainHash = nil
|
||||
var mergeSetBlues = make([]*externalapi.DomainHash, 0)
|
||||
var mergeSetReds = make([]*externalapi.DomainHash, 0)
|
||||
|
||||
parents, err := gh.dagTopologyManager.Parents(stagingArea, blockCandidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var selectedParent = blockParents[0]
|
||||
for _, parent := range blockParents {
|
||||
blockData, err := gh.dataStore.Get(gh.dbAccess, stagingArea, parent)
|
||||
// If genesis:
|
||||
if len(parents) == 0 {
|
||||
blockGHOSTDAGData := model.NewBlockGHOSTDAGData(blueScore, blueWork, selectedParent, mergeSetBlues, mergeSetReds, nil)
|
||||
gh.dataStore.Stage(stagingArea, blockCandidate, blockGHOSTDAGData)
|
||||
return nil
|
||||
}
|
||||
|
||||
maxBlueWork := new(big.Int)
|
||||
maxBlueWork.SetUint64(0)
|
||||
maxBlueScore := uint64(0)
|
||||
selectedParent = parents[0]
|
||||
// Find the selected parent.
|
||||
for _, parent := range parents {
|
||||
parentBlockData, err := gh.dataStore.Get(gh.dbAccess, stagingArea, parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockWork := blockData.BlueWork()
|
||||
blockScore := blockData.BlueScore()
|
||||
if blockWork.Cmp(maxWork) == 1 {
|
||||
parentBlueWork := parentBlockData.BlueWork()
|
||||
switch parentBlueWork.Cmp(maxBlueWork) {
|
||||
case 0:
|
||||
if isMoreHash(parent, selectedParent) {
|
||||
selectedParent = parent
|
||||
maxBlueWork = parentBlueWork
|
||||
maxBlueScore = parentBlockData.BlueScore()
|
||||
}
|
||||
case 1:
|
||||
selectedParent = parent
|
||||
maxWork = blockWork
|
||||
spScore = blockScore
|
||||
}
|
||||
if blockWork.Cmp(maxWork) == 0 && ismoreHash(parent, selectedParent) {
|
||||
selectedParent = parent
|
||||
maxWork = blockWork
|
||||
spScore = blockScore
|
||||
maxBlueWork = parentBlueWork
|
||||
maxBlueScore = parentBlockData.BlueScore()
|
||||
}
|
||||
}
|
||||
myWork.Set(maxWork)
|
||||
myScore = spScore
|
||||
|
||||
/* Goal: iterate blockCandidate's mergeSet and divide it to : blue, blues, reds. */
|
||||
var mergeSetBlues = make([]*externalapi.DomainHash, 0)
|
||||
var mergeSetReds = make([]*externalapi.DomainHash, 0)
|
||||
var blueSet = make([]*externalapi.DomainHash, 0)
|
||||
blueWork.Set(maxBlueWork)
|
||||
blueScore = maxBlueScore
|
||||
|
||||
blueSet := make([]*externalapi.DomainHash, 0)
|
||||
blueSet = append(blueSet, selectedParent)
|
||||
mergeSetBlues = append(mergeSetBlues, selectedParent)
|
||||
|
||||
mergeSetArr, err := gh.findMergeSet(stagingArea, blockParents, selectedParent)
|
||||
mergeSet, err := gh.findMergeSet(stagingArea, parents, selectedParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gh.sortByBlueWork(stagingArea, mergeSetArr)
|
||||
err = gh.sortByBlueWork(stagingArea, mergeSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,41 +96,36 @@ func (gh *ghostdagHelper) GHOSTDAG(stagingArea *model.StagingArea, blockCandidat
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mergeSetBlock := range mergeSetArr {
|
||||
// Iterate on mergeSet and divide it to mergeSetBlues and mergeSetReds.
|
||||
for _, mergeSetBlock := range mergeSet {
|
||||
if mergeSetBlock.Equal(selectedParent) {
|
||||
if !contains(selectedParent, mergeSetBlues) {
|
||||
mergeSetBlues = append(mergeSetBlues, selectedParent)
|
||||
blueSet = append(blueSet, selectedParent)
|
||||
}
|
||||
continue
|
||||
}
|
||||
err := gh.divideBlueRed(stagingArea, selectedParent, mergeSetBlock, &mergeSetBlues, &mergeSetReds, &blueSet)
|
||||
err := gh.divideToBlueAndRed(stagingArea, mergeSetBlock, &mergeSetBlues, &mergeSetReds, &blueSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
myScore += uint64(len(mergeSetBlues))
|
||||
blueScore += uint64(len(mergeSetBlues))
|
||||
|
||||
// We add up all the *work*(not blueWork) that all our blues and selected parent did
|
||||
// Calculation of blue work
|
||||
for _, blue := range mergeSetBlues {
|
||||
header, err := gh.headerStore.BlockHeader(gh.dbAccess, stagingArea, blue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
myWork.Add(myWork, difficulty.CalcWork(header.Bits()))
|
||||
blueWork.Add(blueWork, difficulty.CalcWork(header.Bits()))
|
||||
}
|
||||
|
||||
e := model.NewBlockGHOSTDAGData(myScore, myWork, selectedParent, mergeSetBlues, mergeSetReds, nil)
|
||||
gh.dataStore.Stage(stagingArea, blockCandidate, e)
|
||||
blockGHOSTDAGData := model.NewBlockGHOSTDAGData(blueScore, blueWork, selectedParent, mergeSetBlues, mergeSetReds, nil)
|
||||
gh.dataStore.Stage(stagingArea, blockCandidate, blockGHOSTDAGData)
|
||||
return nil
|
||||
}
|
||||
|
||||
/* --------isMoreHash(w, selectedParent)----------------*/
|
||||
func ismoreHash(parent *externalapi.DomainHash, selectedParent *externalapi.DomainHash) bool {
|
||||
func isMoreHash(parent *externalapi.DomainHash, selectedParent *externalapi.DomainHash) bool {
|
||||
parentByteArray := parent.ByteArray()
|
||||
selectedParentByteArray := selectedParent.ByteArray()
|
||||
//Check if parentHash is more then selectedParentHash
|
||||
|
||||
for i := 0; i < len(parentByteArray); i++ {
|
||||
switch {
|
||||
case parentByteArray[i] < selectedParentByteArray[i]:
|
||||
@@ -136,46 +137,39 @@ func ismoreHash(parent *externalapi.DomainHash, selectedParent *externalapi.Doma
|
||||
return false
|
||||
}
|
||||
|
||||
/* 1. blue = selectedParent.blue + blues
|
||||
2. not connected to at most K blocks (from the blue group)
|
||||
3. for each block at blue , check if not destroy
|
||||
*/
|
||||
|
||||
/* ---------------divideBluesReds--------------------- */
|
||||
func (gh *ghostdagHelper) divideBlueRed(stagingArea *model.StagingArea,
|
||||
selectedParent *externalapi.DomainHash, desiredBlock *externalapi.DomainHash,
|
||||
func (gh *ghostdagHelper) divideToBlueAndRed(stagingArea *model.StagingArea, blockToCheck *externalapi.DomainHash,
|
||||
blues *[]*externalapi.DomainHash, reds *[]*externalapi.DomainHash, blueSet *[]*externalapi.DomainHash) error {
|
||||
|
||||
var k = int(gh.k)
|
||||
counter := 0
|
||||
|
||||
var suspectsBlues = make([]*externalapi.DomainHash, 0)
|
||||
anticoneBlocksCounter := 0
|
||||
anticoneBlues := make([]*externalapi.DomainHash, 0)
|
||||
isMergeBlue := true
|
||||
|
||||
//check that not-connected to at most k.
|
||||
for _, block := range *blueSet {
|
||||
isAnticone, err := gh.isAnticone(stagingArea, block, desiredBlock)
|
||||
for _, blueblock := range *blueSet {
|
||||
isAnticone, err := gh.isAnticone(stagingArea, blueblock, blockToCheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isAnticone {
|
||||
counter++
|
||||
suspectsBlues = append(suspectsBlues, block)
|
||||
anticoneBlocksCounter++
|
||||
anticoneBlues = append(anticoneBlues, blueblock)
|
||||
}
|
||||
if counter > k {
|
||||
if anticoneBlocksCounter > k {
|
||||
isMergeBlue = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isMergeBlue {
|
||||
if !contains(desiredBlock, *reds) {
|
||||
*reds = append(*reds, desiredBlock)
|
||||
if !contains(blockToCheck, *reds) {
|
||||
*reds = append(*reds, blockToCheck)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// check that the k-cluster of each blue is still valid.
|
||||
for _, blue := range suspectsBlues {
|
||||
isDestroyed, err := gh.checkIfDestroy(stagingArea, blue, blueSet)
|
||||
// check that the k-cluster of each anticone blue block is still valid.
|
||||
for _, blueBlock := range anticoneBlues {
|
||||
isDestroyed, err := gh.checkIfDestroy(stagingArea, blueBlock, blueSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,39 +179,37 @@ func (gh *ghostdagHelper) divideBlueRed(stagingArea *model.StagingArea,
|
||||
}
|
||||
}
|
||||
if !isMergeBlue {
|
||||
if !contains(desiredBlock, *reds) {
|
||||
*reds = append(*reds, desiredBlock)
|
||||
if !contains(blockToCheck, *reds) {
|
||||
*reds = append(*reds, blockToCheck)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !contains(desiredBlock, *blues) {
|
||||
*blues = append(*blues, desiredBlock)
|
||||
if !contains(blockToCheck, *blues) {
|
||||
*blues = append(*blues, blockToCheck)
|
||||
}
|
||||
if !contains(desiredBlock, *blueSet) {
|
||||
*blueSet = append(*blueSet, desiredBlock)
|
||||
if !contains(blockToCheck, *blueSet) {
|
||||
*blueSet = append(*blueSet, blockToCheck)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* ---------------isAnticone-------------------------- */
|
||||
func (gh *ghostdagHelper) isAnticone(stagingArea *model.StagingArea, blockA, blockB *externalapi.DomainHash) (bool, error) {
|
||||
isAAncestorOfAB, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, blockA, blockB)
|
||||
isBlockAAncestorOfBlockB, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, blockA, blockB)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isBAncestorOfA, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, blockB, blockA)
|
||||
isBlockBAncestorOfBlockA, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, blockB, blockA)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !isAAncestorOfAB && !isBAncestorOfA, nil
|
||||
|
||||
return !isBlockAAncestorOfBlockB && !isBlockBAncestorOfBlockA, nil
|
||||
}
|
||||
|
||||
/* ----------------validateKCluster------------------- */
|
||||
func (gh *ghostdagHelper) validateKCluster(stagingArea *model.StagingArea, chain *externalapi.DomainHash,
|
||||
checkedBlock *externalapi.DomainHash, counter *int, blueSet *[]*externalapi.DomainHash) (bool, error) {
|
||||
func (gh *ghostdagHelper) validateKCluster(stagingArea *model.StagingArea, chain *externalapi.DomainHash, checkedBlock *externalapi.DomainHash,
|
||||
counter *int, blueSet *[]*externalapi.DomainHash) (bool, error) {
|
||||
|
||||
var k = int(gh.k)
|
||||
k := int(gh.k)
|
||||
isAnticone, err := gh.isAnticone(stagingArea, chain, checkedBlock)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -236,6 +228,7 @@ func (gh *ghostdagHelper) validateKCluster(stagingArea *model.StagingArea, chain
|
||||
*counter++
|
||||
return true, nil
|
||||
}
|
||||
|
||||
isAncestorOf, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, checkedBlock, chain)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -251,27 +244,24 @@ func (gh *ghostdagHelper) validateKCluster(stagingArea *model.StagingArea, chain
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
/*----------------contains-------------------------- */
|
||||
func contains(item *externalapi.DomainHash, items []*externalapi.DomainHash) bool {
|
||||
for _, r := range items {
|
||||
if r.Equal(item) {
|
||||
func contains(desiredItem *externalapi.DomainHash, items []*externalapi.DomainHash) bool {
|
||||
for _, item := range items {
|
||||
if item.Equal(desiredItem) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/* ----------------checkIfDestroy------------------- */
|
||||
/* find number of not-connected in his blue*/
|
||||
// find number of not-connected in blueSet
|
||||
func (gh *ghostdagHelper) checkIfDestroy(stagingArea *model.StagingArea, blockBlue *externalapi.DomainHash,
|
||||
blueSet *[]*externalapi.DomainHash) (bool, error) {
|
||||
|
||||
// Goal: check that the K-cluster of each block in the blueSet is not destroyed when adding the block to the mergeSet.
|
||||
var k = int(gh.k)
|
||||
// check that the K-cluster of each block in the blueSet is not destroyed when adding the block to the mergeSet.
|
||||
k := int(gh.k)
|
||||
counter := 0
|
||||
for _, blue := range *blueSet {
|
||||
isAnticone, err := gh.isAnticone(stagingArea, blue, blockBlue)
|
||||
@@ -288,51 +278,45 @@ func (gh *ghostdagHelper) checkIfDestroy(stagingArea *model.StagingArea, blockBl
|
||||
return false, nil
|
||||
}
|
||||
|
||||
/* ----------------findMergeSet------------------- */
|
||||
func (gh *ghostdagHelper) findMergeSet(stagingArea *model.StagingArea, parents []*externalapi.DomainHash,
|
||||
selectedParent *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
|
||||
allMergeSet := make([]*externalapi.DomainHash, 0)
|
||||
blockQueue := make([]*externalapi.DomainHash, 0)
|
||||
mergeSet := make([]*externalapi.DomainHash, 0)
|
||||
blocksQueue := make([]*externalapi.DomainHash, 0)
|
||||
for _, parent := range parents {
|
||||
if !contains(parent, blockQueue) {
|
||||
blockQueue = append(blockQueue, parent)
|
||||
if !contains(parent, blocksQueue) {
|
||||
blocksQueue = append(blocksQueue, parent)
|
||||
}
|
||||
|
||||
}
|
||||
for len(blockQueue) > 0 {
|
||||
block := blockQueue[0]
|
||||
blockQueue = blockQueue[1:]
|
||||
for len(blocksQueue) > 0 {
|
||||
block := blocksQueue[0]
|
||||
blocksQueue = blocksQueue[1:]
|
||||
if selectedParent.Equal(block) {
|
||||
if !contains(block, allMergeSet) {
|
||||
allMergeSet = append(allMergeSet, block)
|
||||
if !contains(block, mergeSet) {
|
||||
mergeSet = append(mergeSet, block)
|
||||
}
|
||||
continue
|
||||
}
|
||||
isancestorOf, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, block, selectedParent)
|
||||
isAncestorOf, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, block, selectedParent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isancestorOf {
|
||||
if isAncestorOf {
|
||||
continue
|
||||
}
|
||||
if !contains(block, allMergeSet) {
|
||||
allMergeSet = append(allMergeSet, block)
|
||||
if !contains(block, mergeSet) {
|
||||
mergeSet = append(mergeSet, block)
|
||||
}
|
||||
err = gh.insertParent(stagingArea, block, &blockQueue)
|
||||
err = gh.insertParent(stagingArea, block, &blocksQueue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return allMergeSet, nil
|
||||
return mergeSet, nil
|
||||
}
|
||||
|
||||
/* ----------------insertParent------------------- */
|
||||
/* Insert all parents to the queue*/
|
||||
func (gh *ghostdagHelper) insertParent(stagingArea *model.StagingArea, child *externalapi.DomainHash,
|
||||
queue *[]*externalapi.DomainHash) error {
|
||||
|
||||
// Insert all parents to the queue
|
||||
func (gh *ghostdagHelper) insertParent(stagingArea *model.StagingArea, child *externalapi.DomainHash, queue *[]*externalapi.DomainHash) error {
|
||||
parents, err := gh.dagTopologyManager.Parents(stagingArea, child)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -346,8 +330,9 @@ func (gh *ghostdagHelper) insertParent(stagingArea *model.StagingArea, child *ex
|
||||
return nil
|
||||
}
|
||||
|
||||
/* ----------------findBlueSet------------------- */
|
||||
func (gh *ghostdagHelper) findBlueSet(stagingArea *model.StagingArea, blueSet *[]*externalapi.DomainHash, selectedParent *externalapi.DomainHash) error {
|
||||
func (gh *ghostdagHelper) findBlueSet(stagingArea *model.StagingArea, blueSet *[]*externalapi.DomainHash,
|
||||
selectedParent *externalapi.DomainHash) error {
|
||||
|
||||
for selectedParent != nil {
|
||||
if !contains(selectedParent, *blueSet) {
|
||||
*blueSet = append(*blueSet, selectedParent)
|
||||
@@ -368,44 +353,41 @@ func (gh *ghostdagHelper) findBlueSet(stagingArea *model.StagingArea, blueSet *[
|
||||
return nil
|
||||
}
|
||||
|
||||
/* ----------------sortByBlueScore------------------- */
|
||||
func (gh *ghostdagHelper) sortByBlueWork(stagingArea *model.StagingArea, arr []*externalapi.DomainHash) error {
|
||||
|
||||
var err error = nil
|
||||
var err error
|
||||
sort.Slice(arr, func(i, j int) bool {
|
||||
|
||||
blockLeft, error := gh.dataStore.Get(gh.dbAccess, stagingArea, arr[i])
|
||||
if error != nil {
|
||||
err = error
|
||||
blockLeft, err := gh.dataStore.Get(gh.dbAccess, stagingArea, arr[i])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
blockRight, error := gh.dataStore.Get(gh.dbAccess, stagingArea, arr[j])
|
||||
if error != nil {
|
||||
err = error
|
||||
blockRight, err := gh.dataStore.Get(gh.dbAccess, stagingArea, arr[j])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if blockLeft.BlueWork().Cmp(blockRight.BlueWork()) == -1 {
|
||||
return true
|
||||
}
|
||||
if blockLeft.BlueWork().Cmp(blockRight.BlueWork()) == 0 {
|
||||
return ismoreHash(arr[j], arr[i])
|
||||
return isMoreHash(arr[j], arr[i])
|
||||
}
|
||||
return false
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
/* --------------------------------------------- */
|
||||
|
||||
func (gh *ghostdagHelper) BlockData(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*model.BlockGHOSTDAGData, error) {
|
||||
return gh.dataStore.Get(gh.dbAccess, stagingArea, blockHash)
|
||||
}
|
||||
func (gh *ghostdagHelper) ChooseSelectedParent(stagingArea *model.StagingArea, blockHashes ...*externalapi.DomainHash) (*externalapi.DomainHash, error) {
|
||||
|
||||
func (gh *ghostdagHelper) ChooseSelectedParent(*model.StagingArea, ...*externalapi.DomainHash) (*externalapi.DomainHash, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (gh *ghostdagHelper) Less(blockHashA *externalapi.DomainHash, ghostdagDataA *model.BlockGHOSTDAGData, blockHashB *externalapi.DomainHash, ghostdagDataB *model.BlockGHOSTDAGData) bool {
|
||||
func (gh *ghostdagHelper) Less(*externalapi.DomainHash, *model.BlockGHOSTDAGData, *externalapi.DomainHash, *model.BlockGHOSTDAGData) bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (gh *ghostdagHelper) GetSortedMergeSet(*model.StagingArea, *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ func (v *transactionValidator) validateTransactionScripts(tx *externalapi.Domain
|
||||
}
|
||||
|
||||
scriptPubKey := utxoEntry.ScriptPublicKey()
|
||||
vm, err := txscript.NewEngine(scriptPubKey, tx, i, txscript.ScriptNoFlags, v.sigCache, sighashReusedValues)
|
||||
vm, err := txscript.NewEngine(scriptPubKey, tx, i, txscript.ScriptNoFlags, v.sigCache, v.sigCacheECDSA, sighashReusedValues)
|
||||
if err != nil {
|
||||
return errors.Wrapf(ruleerrors.ErrScriptMalformed, "failed to parse input "+
|
||||
"%d which references output %s - "+
|
||||
|
||||
@@ -21,6 +21,7 @@ type transactionValidator struct {
|
||||
massPerSigOp uint64
|
||||
maxCoinbasePayloadLength uint64
|
||||
sigCache *txscript.SigCache
|
||||
sigCacheECDSA *txscript.SigCacheECDSA
|
||||
}
|
||||
|
||||
// New instantiates a new TransactionValidator
|
||||
@@ -47,5 +48,6 @@ func New(blockCoinbaseMaturity uint64,
|
||||
ghostdagDataStore: ghostdagDataStore,
|
||||
daaBlocksStore: daaBlocksStore,
|
||||
sigCache: txscript.NewSigCache(sigCacheSize),
|
||||
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestValidateTransactionInContextAndPopulateMassAndFee(t *testing.T) {
|
||||
defer tearDown(false)
|
||||
|
||||
pastMedianManager.pastMedianTimeForTest = 1
|
||||
privateKey, err := secp256k1.GeneratePrivateKey()
|
||||
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a private key: %v", err)
|
||||
}
|
||||
@@ -58,9 +58,9 @@ func TestValidateTransactionInContextAndPopulateMassAndFee(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to serialize public key: %v", err)
|
||||
}
|
||||
addr, err := util.NewAddressPubKeyHashFromPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
addr, err := util.NewAddressPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate p2pkh address: %v", err)
|
||||
t.Fatalf("Failed to generate p2pk address: %v", err)
|
||||
}
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
@@ -238,3 +238,251 @@ func TestValidateTransactionInContextAndPopulateMassAndFee(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSigningTwoInputs(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestSigningTwoInputs")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a private key: %v", err)
|
||||
}
|
||||
publicKey, err := privateKey.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a public key: %v", err)
|
||||
}
|
||||
publicKeySerialized, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to serialize public key: %v", err)
|
||||
}
|
||||
addr, err := util.NewAddressPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate p2pk address: %v", err)
|
||||
}
|
||||
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToAddrScript: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
|
||||
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block2Hash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block2, err := tc.GetBlock(block2Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting block2: %+v", err)
|
||||
}
|
||||
|
||||
block3, err := tc.GetBlock(block3Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting block3: %+v", err)
|
||||
}
|
||||
|
||||
block2Tx := block2.Transactions[0]
|
||||
block2TxOut := block2Tx.Outputs[0]
|
||||
|
||||
block3Tx := block3.Transactions[0]
|
||||
block3TxOut := block3Tx.Outputs[0]
|
||||
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{
|
||||
{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block2.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(block2TxOut.Value, block2TxOut.ScriptPublicKey, true, 0),
|
||||
},
|
||||
{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block3.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(block3TxOut.Value, block3TxOut.ScriptPublicKey, true, 0),
|
||||
},
|
||||
},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{
|
||||
Value: 1,
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
Gas: 0,
|
||||
LockTime: 0,
|
||||
}
|
||||
|
||||
sighashReusedValues := &consensushashing.SighashReusedValues{}
|
||||
for i, input := range tx.Inputs {
|
||||
signatureScript, err := txscript.SignatureScript(tx, i, consensushashing.SigHashAll, privateKey,
|
||||
sighashReusedValues)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a sigScript: %v", err)
|
||||
}
|
||||
input.SignatureScript = signatureScript
|
||||
}
|
||||
|
||||
_, insertionResult, err := tc.AddBlock([]*externalapi.DomainHash{block3Hash}, nil, []*externalapi.DomainTransaction{tx})
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
txOutpoint := &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(tx),
|
||||
Index: 0,
|
||||
}
|
||||
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(txOutpoint) {
|
||||
t.Fatalf("tx was not accepted by the DAG")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSigningTwoInputsECDSA(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestSigningTwoInputsECDSA")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
privateKey, err := secp256k1.GenerateECDSAPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a private key: %v", err)
|
||||
}
|
||||
publicKey, err := privateKey.ECDSAPublicKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a public key: %v", err)
|
||||
}
|
||||
publicKeySerialized, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to serialize public key: %v", err)
|
||||
}
|
||||
addr, err := util.NewAddressPublicKeyECDSA(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate p2pk address: %v", err)
|
||||
}
|
||||
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToAddrScript: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
|
||||
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block2Hash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block2, err := tc.GetBlock(block2Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting block2: %+v", err)
|
||||
}
|
||||
|
||||
block3, err := tc.GetBlock(block3Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting block3: %+v", err)
|
||||
}
|
||||
|
||||
block2Tx := block2.Transactions[0]
|
||||
block2TxOut := block2Tx.Outputs[0]
|
||||
|
||||
block3Tx := block3.Transactions[0]
|
||||
block3TxOut := block3Tx.Outputs[0]
|
||||
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{
|
||||
{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block2.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(block2TxOut.Value, block2TxOut.ScriptPublicKey, true, 0),
|
||||
},
|
||||
{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block3.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(block3TxOut.Value, block3TxOut.ScriptPublicKey, true, 0),
|
||||
},
|
||||
},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{
|
||||
Value: 1,
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
Gas: 0,
|
||||
LockTime: 0,
|
||||
}
|
||||
|
||||
sighashReusedValues := &consensushashing.SighashReusedValues{}
|
||||
for i, input := range tx.Inputs {
|
||||
signatureScript, err := txscript.SignatureScriptECDSA(tx, i, consensushashing.SigHashAll, privateKey,
|
||||
sighashReusedValues)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a sigScript: %v", err)
|
||||
}
|
||||
input.SignatureScript = signatureScript
|
||||
}
|
||||
|
||||
_, insertionResult, err := tc.AddBlock([]*externalapi.DomainHash{block3Hash}, nil, []*externalapi.DomainTransaction{tx})
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
txOutpoint := &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(tx),
|
||||
Index: 0,
|
||||
}
|
||||
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(txOutpoint) {
|
||||
t.Fatalf("tx was not accepted by the DAG")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ type testConsensus struct {
|
||||
testReachabilityManager testapi.TestReachabilityManager
|
||||
testConsensusStateManager testapi.TestConsensusStateManager
|
||||
testTransactionValidator testapi.TestTransactionValidator
|
||||
|
||||
buildBlockConsensus *consensus
|
||||
}
|
||||
|
||||
func (tc *testConsensus) DAGParams() *dagconfig.Params {
|
||||
|
||||
@@ -2,7 +2,6 @@ package consensushashing
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
@@ -58,10 +57,10 @@ type SighashReusedValues struct {
|
||||
payloadHash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// CalculateSignatureHash will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification.
|
||||
// CalculateSignatureHashSchnorr will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification for Schnorr.
|
||||
// This returns error only if one of the provided parameters are consensus-invalid.
|
||||
func CalculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
func CalculateSignatureHashSchnorr(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
reusedValues *SighashReusedValues) (*externalapi.DomainHash, error) {
|
||||
|
||||
if !hashType.IsStandardSigHashType() {
|
||||
@@ -70,18 +69,26 @@ func CalculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, h
|
||||
|
||||
txIn := tx.Inputs[inputIndex]
|
||||
prevScriptPublicKey := txIn.UTXOEntry.ScriptPublicKey()
|
||||
|
||||
if tx.Version > constants.MaxTransactionVersion {
|
||||
return nil, errors.Errorf("Transaction version is unknown.")
|
||||
}
|
||||
|
||||
if prevScriptPublicKey.Version > constants.MaxScriptPublicKeyVersion {
|
||||
return nil, errors.Errorf("Script version is unknown.")
|
||||
}
|
||||
|
||||
return calculateSignatureHash(tx, inputIndex, txIn, prevScriptPublicKey, hashType, reusedValues)
|
||||
}
|
||||
|
||||
// CalculateSignatureHashECDSA will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification for ECDSA.
|
||||
// This returns error only if one of the provided parameters are consensus-invalid.
|
||||
func CalculateSignatureHashECDSA(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
reusedValues *SighashReusedValues) (*externalapi.DomainHash, error) {
|
||||
|
||||
hash, err := CalculateSignatureHashSchnorr(tx, inputIndex, hashType, reusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hashWriter := hashes.NewTransactionSigningHashECDSAWriter()
|
||||
hashWriter.InfallibleWrite(hash.ByteSlice())
|
||||
|
||||
return hashWriter.Finalize(), nil
|
||||
}
|
||||
|
||||
func calculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, txIn *externalapi.DomainTransactionInput,
|
||||
prevScriptPublicKey *externalapi.ScriptPublicKey, hashType SigHashType, reusedValues *SighashReusedValues) (
|
||||
*externalapi.DomainHash, error) {
|
||||
|
||||
@@ -88,7 +88,7 @@ func modifySubnetworkID(tx *externalapi.DomainTransaction) *externalapi.DomainTr
|
||||
return clone
|
||||
}
|
||||
|
||||
func TestCalculateSignatureHash(t *testing.T) {
|
||||
func TestCalculateSignatureHashSchnorr(t *testing.T) {
|
||||
nativeTx, subnetworkTx, err := generateTxs()
|
||||
if err != nil {
|
||||
t.Fatalf("Error from generateTxs: %+v", err)
|
||||
@@ -108,86 +108,86 @@ func TestCalculateSignatureHash(t *testing.T) {
|
||||
|
||||
// sigHashAll
|
||||
{name: "native-all-0", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "c899a5ea7414f0bbfd77e50674f46da34ce8722b928d4362a4b4b727c69c6499"},
|
||||
expectedSignatureHash: "3e4a8effa6903dea5f762754ec410d732114ec2907e5bcbae7b6dd8d3f309a10"},
|
||||
{name: "native-all-0-modify-input-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // should change the hash
|
||||
expectedSignatureHash: "faf3b9db2e07b1c14b2df02002d3e40f1e430f177ac5cd3354c84dad8fbe72ce"},
|
||||
expectedSignatureHash: "0bd2947383101f9708d94d5799626c69c1b8472d2de66523c90c4a674e99cc51"},
|
||||
{name: "native-all-0-modify-output-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // should change the hash
|
||||
expectedSignatureHash: "3a557c5b873aab72dcb81649642e1d7a63b75dcdcc74e19d340964a9e0eac76c"},
|
||||
expectedSignatureHash: "ae69aa1372e958f069bf52d2628ead7ee789f195340b615ff0bcb6f01a995810"},
|
||||
{name: "native-all-0-modify-sequence-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // should change the hash
|
||||
expectedSignatureHash: "2dd5fe8f9fa4bf551ea2f080a26e07b2462083e12d3b2ed01cb9369a61920665"},
|
||||
expectedSignatureHash: "51295575e7efc1fe1c2e741ade49089e3d780d15d26e9a0b18b3d90e35caf795"},
|
||||
{name: "native-all-anyonecanpay-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "19fe2e0db681017f318fda705a39bbbad9c1085514cfbcff6fac01e1725f758b"},
|
||||
expectedSignatureHash: "0a13d3cab42a6cf3a7ef96f7b28c9bb0d98c209a3032ff85a6bfa7dac520f2c2"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(0), // should change the hash
|
||||
expectedSignatureHash: "5b21d492560a1c794595f769b3ae3c151775b9cfc4029d17c53f1856e1005da4"},
|
||||
expectedSignatureHash: "aeaa56f5bc99daea0be5ed4b047808ce0123a81ac89c6089b72fa7199f5cd1c6"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-1", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "19fe2e0db681017f318fda705a39bbbad9c1085514cfbcff6fac01e1725f758b"},
|
||||
expectedSignatureHash: "0a13d3cab42a6cf3a7ef96f7b28c9bb0d98c209a3032ff85a6bfa7dac520f2c2"},
|
||||
{name: "native-all-anyonecanpay-0-modify-sequence", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "19fe2e0db681017f318fda705a39bbbad9c1085514cfbcff6fac01e1725f758b"},
|
||||
expectedSignatureHash: "0a13d3cab42a6cf3a7ef96f7b28c9bb0d98c209a3032ff85a6bfa7dac520f2c2"},
|
||||
|
||||
// sigHashNone
|
||||
{name: "native-none-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
expectedSignatureHash: "fafabaabf6349fee4e18626b4eff015472f2317576a8f4bf7b0eea1df6f3e32b"},
|
||||
expectedSignatureHash: "da53f7c726b55357adb1b644265fe7b9b7897cadde91a942d6127195c2ce99dc"},
|
||||
{name: "native-none-0-modify-output-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "fafabaabf6349fee4e18626b4eff015472f2317576a8f4bf7b0eea1df6f3e32b"},
|
||||
expectedSignatureHash: "da53f7c726b55357adb1b644265fe7b9b7897cadde91a942d6127195c2ce99dc"},
|
||||
{name: "native-none-0-modify-sequence-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "daee0700e0ed4ab9f50de24d83e0bfce62999474ec8ceeb537ea35980662b601"},
|
||||
expectedSignatureHash: "72c07c152a3792fb863d2de219ab4e863fe6779dc970a5c3958e26b3a3b9f139"},
|
||||
{name: "native-none-0-modify-sequence-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "fafabaabf6349fee4e18626b4eff015472f2317576a8f4bf7b0eea1df6f3e32b"},
|
||||
expectedSignatureHash: "da53f7c726b55357adb1b644265fe7b9b7897cadde91a942d6127195c2ce99dc"},
|
||||
{name: "native-none-anyonecanpay-0", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "4e5c2d895f9711dc89c19d49ba478e9c8f4be0d82c9bd6b60d0361eb9b5296bc"},
|
||||
expectedSignatureHash: "18eb79df328a516708f792cadfc4bf2be21b6add35fb6637a20347d532b1dce1"},
|
||||
{name: "native-none-anyonecanpay-0-modify-amount-spent", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyAmountSpent(0), // should change the hash
|
||||
expectedSignatureHash: "9ce2f75eafc85b8e19133942c3143d14b61f2e7cc479fbc6d2fca026e50897f1"},
|
||||
expectedSignatureHash: "674a8e6effa0bbfdb3df3ca45a7b17ab695cc0f9b3e0519e5bff165de13604e2"},
|
||||
{name: "native-none-anyonecanpay-0-modify-script-public-key", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyScriptPublicKey(0), // should change the hash
|
||||
expectedSignatureHash: "c6c364190520fe6c0419c2f45e25bf084356333b03ac7aaec28251126398bda3"},
|
||||
expectedSignatureHash: "7a879d339c9b948c5264f1c0b8463f551221b2dcd575623f178cbbfcf7e01987"},
|
||||
|
||||
// sigHashSingle
|
||||
{name: "native-single-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
expectedSignatureHash: "6ff01d5d7cd82e24bc9ca0edec8bd6931ffb5aa1d303f07ca05dc89757343a92"},
|
||||
expectedSignatureHash: "99c0a4a381fed9dbd651450d6ca969a6e8cdd941e9d73be086a1ba6c90fb630f"},
|
||||
{name: "native-single-0-modify-output-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(0), // should change the hash
|
||||
expectedSignatureHash: "d62af956aea369365bacc7e7f1aac106836994f1648311e82dd38da822c8771e"},
|
||||
expectedSignatureHash: "92189a32391ca50a454d1853efed55acb1c49993911a1201df9891c866c483ee"},
|
||||
{name: "native-single-0-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6ff01d5d7cd82e24bc9ca0edec8bd6931ffb5aa1d303f07ca05dc89757343a92"},
|
||||
expectedSignatureHash: "99c0a4a381fed9dbd651450d6ca969a6e8cdd941e9d73be086a1ba6c90fb630f"},
|
||||
{name: "native-single-0-modify-sequence-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "46692229d45bf2ceacb18960faba29753e325c0ade26ecf94495b91daacb828d"},
|
||||
expectedSignatureHash: "765e2289df98b3a5269f112d4b36d57fe32c74d42e04a3046c6a3c8dd78a4121"},
|
||||
{name: "native-single-0-modify-sequence-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6ff01d5d7cd82e24bc9ca0edec8bd6931ffb5aa1d303f07ca05dc89757343a92"},
|
||||
expectedSignatureHash: "99c0a4a381fed9dbd651450d6ca969a6e8cdd941e9d73be086a1ba6c90fb630f"},
|
||||
{name: "native-single-2-no-corresponding-output", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
expectedSignatureHash: "d3cc385082a7f272ec2c8aae7f3a96ab2f49a4a4e1ed44d61af34058a7721281"},
|
||||
expectedSignatureHash: "43de18c04d7fde81f49a40228d8730b4ceb0c66c77841c22622f59554769dd13"},
|
||||
{name: "native-single-2-no-corresponding-output-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "d3cc385082a7f272ec2c8aae7f3a96ab2f49a4a4e1ed44d61af34058a7721281"},
|
||||
expectedSignatureHash: "43de18c04d7fde81f49a40228d8730b4ceb0c66c77841c22622f59554769dd13"},
|
||||
{name: "native-single-anyonecanpay-0", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "408fcfd8ceca135c0f54569ccf8ac727e1aa6b5a15f87ccca765f1d5808aa4ea"},
|
||||
expectedSignatureHash: "2e270f86fd68f780a5aa8d250364ab6786d990040268e5d561d09dde365dfab7"},
|
||||
{name: "native-single-anyonecanpay-2-no-corresponding-output", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 2,
|
||||
expectedSignatureHash: "685fac0d0b9dd3c5556f266714c4f7f93475d49fa12befb18e8297bc062aeaba"},
|
||||
expectedSignatureHash: "afc05dd6b4a530cc3a4d7d23126548bd1f31c793e9282cbbfa27ff5566219501"},
|
||||
|
||||
// subnetwork transaction
|
||||
{name: "subnetwork-all-0", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "0e8b1433b761a220a61c0dc1f0fda909d49cef120d98d9f87344fef52dac0d8b"},
|
||||
expectedSignatureHash: "5a67423ee048e067b94e2d4fc2960f62eceff6aa8b6c5ad71e3b7b7e5ff6bad7"},
|
||||
{name: "subnetwork-all-modify-payload", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyPayload, // should change the hash
|
||||
expectedSignatureHash: "087315acb9193eaa14929dbe3d0ace80238aebe13eab3bf8db6c0a0d7ddb782e"},
|
||||
expectedSignatureHash: "0d3bc5914da8dc8df081945fea1255359f380ca9baa8b31dfb3657c1e3c6038a"},
|
||||
{name: "subnetwork-all-modify-gas", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyGas, // should change the hash
|
||||
expectedSignatureHash: "07a90408ef45864ae8354b07a74cf826a4621391425ba417470a6e680af4ce70"},
|
||||
expectedSignatureHash: "70abe9f947d0a0f5d735f9a063db8af41fe5902940f2693a1782119063097094"},
|
||||
{name: "subnetwork-all-subnetwork-id", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySubnetworkID, // should change the hash
|
||||
expectedSignatureHash: "4ca44c2e35729ae5efe831a77027f1a58a41dbdd853459c26cbfe7d6c88783fb"},
|
||||
expectedSignatureHash: "571a0b7ea905b7a6ff7ab825b72d23f911bac0bfaa7c4c97a4887a3d090925d4"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -196,10 +196,132 @@ func TestCalculateSignatureHash(t *testing.T) {
|
||||
tx = test.modificationFunction(tx)
|
||||
}
|
||||
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHash(
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHashSchnorr(
|
||||
tx, test.inputIndex, test.hashType, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("%s: Error from CalculateSignatureHash: %+v", test.name, err)
|
||||
t.Errorf("%s: Error from CalculateSignatureHashSchnorr: %+v", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if actualSignatureHash.String() != test.expectedSignatureHash {
|
||||
t.Errorf("%s: expected signature hash: '%s'; but got: '%s'",
|
||||
test.name, test.expectedSignatureHash, actualSignatureHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateSignatureHashECDSA(t *testing.T) {
|
||||
nativeTx, subnetworkTx, err := generateTxs()
|
||||
if err != nil {
|
||||
t.Fatalf("Error from generateTxs: %+v", err)
|
||||
}
|
||||
|
||||
// Note: Expected values were generated by the same code that they test,
|
||||
// As long as those were not verified using 3rd-party code they only check for regression, not correctness
|
||||
tests := []struct {
|
||||
name string
|
||||
tx *externalapi.DomainTransaction
|
||||
hashType consensushashing.SigHashType
|
||||
inputIndex int
|
||||
modificationFunction func(*externalapi.DomainTransaction) *externalapi.DomainTransaction
|
||||
expectedSignatureHash string
|
||||
}{
|
||||
// native transactions
|
||||
|
||||
// sigHashAll
|
||||
{name: "native-all-0", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "150a2bcd0296f76a3395a4a9982df46bf24ce93f955bc39c10ffc95b6c524eb3"},
|
||||
{name: "native-all-0-modify-input-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // should change the hash
|
||||
expectedSignatureHash: "8fb5304e181b003e0c123ea6f6677abc3704feec47054e8a1c218b827bf57ca0"},
|
||||
{name: "native-all-0-modify-output-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // should change the hash
|
||||
expectedSignatureHash: "180cb36454aa80822694decde4fc711104e35a4bddf92286a83877f2b8d0aabb"},
|
||||
{name: "native-all-0-modify-sequence-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // should change the hash
|
||||
expectedSignatureHash: "5b5f1c42a3c3c16bb4922777e2963c00e6a2cce39afa1980d2288053378f9632"},
|
||||
{name: "native-all-anyonecanpay-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(0), // should change the hash
|
||||
expectedSignatureHash: "1208491d564c138d613f51b997394dbad23feca7c0ca88c7f36cdf6b9173327d"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-1", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
{name: "native-all-anyonecanpay-0-modify-sequence", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
|
||||
// sigHashNone
|
||||
{name: "native-none-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-0-modify-output-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-0-modify-sequence-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "57d76e2568cd3fc3426b4f8836fe900a2d20e740fad744949126651fd549f75e"},
|
||||
{name: "native-none-0-modify-sequence-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-anyonecanpay-0", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "ef97a0f89d623302619f9aa2a00fce1522e72d4d255e6c6d3ed225ffc02f38ff"},
|
||||
{name: "native-none-anyonecanpay-0-modify-amount-spent", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyAmountSpent(0), // should change the hash
|
||||
expectedSignatureHash: "043a2a943f02607be126ac6609ab2324aae389d784a4147f27101e7da379311a"},
|
||||
{name: "native-none-anyonecanpay-0-modify-script-public-key", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyScriptPublicKey(0), // should change the hash
|
||||
expectedSignatureHash: "f2cd43d0d047cdcfbf8b6e12a86cfbf250f1e2c34dc5e631675a5f5b867bd9e6"},
|
||||
|
||||
// sigHashSingle
|
||||
{name: "native-single-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-0-modify-output-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(0), // should change the hash
|
||||
expectedSignatureHash: "c2c7e77516a15f0f47f886b14cc47af2045eea15f176a9a560a9d47d8866958f"},
|
||||
{name: "native-single-0-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-0-modify-sequence-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "2034eec2acc08c49d3896cc1bda214904ca850fc5989518885465b5a3154ee7f"},
|
||||
{name: "native-single-0-modify-sequence-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-2-no-corresponding-output", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
expectedSignatureHash: "84ae3bb03202efc587d97e5aea7b80581b82242b969e6dea13b8daa32d24c0c1"},
|
||||
{name: "native-single-2-no-corresponding-output-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "84ae3bb03202efc587d97e5aea7b80581b82242b969e6dea13b8daa32d24c0c1"},
|
||||
{name: "native-single-anyonecanpay-0", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "b2ccf259a65c3231d741a03420967b95563c3928cc15d3d15e8e795f383ab48b"},
|
||||
{name: "native-single-anyonecanpay-2-no-corresponding-output", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 2,
|
||||
expectedSignatureHash: "652c8cd0ac050e41aad347ea09ee788360eec70908ba22fe5bba5bdde49b8ae1"},
|
||||
|
||||
// subnetwork transaction
|
||||
{name: "subnetwork-all-0", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "2e828c04f5f03e4ce4b3de1fa5303400da5fa504291b760f5f6d4e98fc24597f"},
|
||||
{name: "subnetwork-all-modify-payload", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyPayload, // should change the hash
|
||||
expectedSignatureHash: "d5f3993aa8b7f47df52f78f2be9965f928c9cca9ac9e9542f1190b9d5ed6c17d"},
|
||||
{name: "subnetwork-all-modify-gas", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyGas, // should change the hash
|
||||
expectedSignatureHash: "e74d4a9fa5cdf476299ebdfa03f3c8021a157f814731ea11f6a6d606dc5cd439"},
|
||||
{name: "subnetwork-all-subnetwork-id", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySubnetworkID, // should change the hash
|
||||
expectedSignatureHash: "ca8bf9bc42cda2ec3ce8bee090011072e56ff4d0d8616d5c20cefe5f84d7fb37"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
tx := test.tx
|
||||
if test.modificationFunction != nil {
|
||||
tx = test.modificationFunction(tx)
|
||||
}
|
||||
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHashECDSA(
|
||||
tx, test.inputIndex, test.hashType, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("%s: Error from CalculateSignatureHashECDSA: %+v", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -283,7 +405,7 @@ func generateTxs() (nativeTx, subnetworkTx *externalapi.DomainTransaction, err e
|
||||
return nativeTx, subnetworkTx, nil
|
||||
}
|
||||
|
||||
func BenchmarkCalculateSignatureHash(b *testing.B) {
|
||||
func BenchmarkCalculateSignatureHashSchnorr(b *testing.B) {
|
||||
sigHashTypes := []consensushashing.SigHashType{
|
||||
consensushashing.SigHashAll,
|
||||
consensushashing.SigHashNone,
|
||||
@@ -300,9 +422,9 @@ func BenchmarkCalculateSignatureHash(b *testing.B) {
|
||||
reusedValues := &consensushashing.SighashReusedValues{}
|
||||
for inputIndex := range tx.Inputs {
|
||||
sigHashType := sigHashTypes[inputIndex%len(sigHashTypes)]
|
||||
_, err := consensushashing.CalculateSignatureHash(tx, inputIndex, sigHashType, reusedValues)
|
||||
_, err := consensushashing.CalculateSignatureHashSchnorr(tx, inputIndex, sigHashType, reusedValues)
|
||||
if err != nil {
|
||||
b.Fatalf("Error from CalculateSignatureHash: %+v", err)
|
||||
b.Fatalf("Error from CalculateSignatureHashSchnorr: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,7 +456,7 @@ func signTx(b *testing.B, tx *externalapi.DomainTransaction, sigHashTypes []cons
|
||||
if err != nil {
|
||||
b.Fatalf("Error parsing private key hex: %+v", err)
|
||||
}
|
||||
keyPair, err := secp256k1.DeserializePrivateKeyFromSlice(privateKeyBytes)
|
||||
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKeyBytes)
|
||||
if err != nil {
|
||||
b.Fatalf("Error deserializing private key: %+v", err)
|
||||
}
|
||||
|
||||
@@ -22,28 +22,6 @@ const (
|
||||
txEncodingExcludeSignatureScript = 1 << iota
|
||||
)
|
||||
|
||||
// TransactionHashForSigning hashes the transaction and the given hash type in a way that is intended for
|
||||
// signatures.
|
||||
func TransactionHashForSigning(tx *externalapi.DomainTransaction, hashType uint32) *externalapi.DomainHash {
|
||||
// Encode the header and hash everything prior to the number of
|
||||
// transactions.
|
||||
writer := hashes.NewTransactionSigningHashWriter()
|
||||
err := serializeTransaction(writer, tx, txEncodingFull)
|
||||
if err != nil {
|
||||
// It seems like this could only happen if the writer returned an error.
|
||||
// and this writer should never return an error (no allocations or possible failures)
|
||||
// the only non-writer error path here is unknown types in `WriteElement`
|
||||
panic(errors.Wrap(err, "TransactionHashForSigning() failed. this should never fail for structurally-valid transactions"))
|
||||
}
|
||||
|
||||
err = serialization.WriteElement(writer, hashType)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "this should never happen. Hash digest should never return an error"))
|
||||
}
|
||||
|
||||
return writer.Finalize()
|
||||
}
|
||||
|
||||
// TransactionHash returns the transaction hash.
|
||||
func TransactionHash(tx *externalapi.DomainTransaction) *externalapi.DomainHash {
|
||||
// Encode the header and hash everything prior to the number of
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
package hashes
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
transcationHashDomain = "TransactionHash"
|
||||
transcationIDDomain = "TransactionID"
|
||||
transcationSigningDomain = "TransactionSigningHash"
|
||||
blockDomain = "BlockHash"
|
||||
proofOfWorkDomain = "ProofOfWorkHash"
|
||||
merkleBranchDomain = "MerkleBranchHash"
|
||||
transcationHashDomain = "TransactionHash"
|
||||
transcationIDDomain = "TransactionID"
|
||||
transcationSigningDomain = "TransactionSigningHash"
|
||||
transcationSigningECDSADomain = "TransactionSigningHashECDSA"
|
||||
blockDomain = "BlockHash"
|
||||
proofOfWorkDomain = "ProofOfWorkHash"
|
||||
merkleBranchDomain = "MerkleBranchHash"
|
||||
)
|
||||
|
||||
// transactionSigningECDSADomainHash is a hashed version of transcationSigningECDSADomain that is used
|
||||
// to make it a constant size. This is needed because this domain is used by sha256 hash writer, and
|
||||
// sha256 doesn't support variable size domain separation.
|
||||
var transactionSigningECDSADomainHash = sha256.Sum256([]byte(transcationSigningECDSADomain))
|
||||
|
||||
// NewTransactionHashWriter Returns a new HashWriter used for transaction hashes
|
||||
func NewTransactionHashWriter() HashWriter {
|
||||
blake, err := blake2b.New256([]byte(transcationHashDomain))
|
||||
@@ -41,6 +48,13 @@ func NewTransactionSigningHashWriter() HashWriter {
|
||||
return HashWriter{blake}
|
||||
}
|
||||
|
||||
// NewTransactionSigningHashECDSAWriter Returns a new HashWriter used for signing on a transaction with ECDSA
|
||||
func NewTransactionSigningHashECDSAWriter() HashWriter {
|
||||
hashWriter := HashWriter{sha256.New()}
|
||||
hashWriter.InfallibleWrite(transactionSigningECDSADomainHash[:])
|
||||
return hashWriter
|
||||
}
|
||||
|
||||
// NewBlockHashWriter Returns a new HashWriter used for hashing blocks
|
||||
func NewBlockHashWriter() HashWriter {
|
||||
blake, err := blake2b.New256([]byte(blockDomain))
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package hashes
|
||||
|
||||
import (
|
||||
"hash"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/pkg/errors"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// HashWriter is used to incrementally hash data without concatenating all of the data to a single buffer
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
// CreateTransaction create a transaction that spends the first output of provided transaction.
|
||||
// Assumes that the output being spent has opTrueScript as it's scriptPublicKey
|
||||
// Creates the value of the spent output minus 1 sompi
|
||||
func CreateTransaction(txToSpend *externalapi.DomainTransaction) (*externalapi.DomainTransaction, error) {
|
||||
func CreateTransaction(txToSpend *externalapi.DomainTransaction, fee uint64) (*externalapi.DomainTransaction, error) {
|
||||
scriptPublicKey, redeemScript := OpTrueScript()
|
||||
|
||||
signatureScript, err := txscript.PayToScriptHashSignatureScript(redeemScript, nil)
|
||||
@@ -27,7 +27,7 @@ func CreateTransaction(txToSpend *externalapi.DomainTransaction) (*externalapi.D
|
||||
}
|
||||
output := &externalapi.DomainTransactionOutput{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
Value: txToSpend.Outputs[0].Value - 1,
|
||||
Value: txToSpend.Outputs[0].Value - fee,
|
||||
}
|
||||
return &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
|
||||
@@ -15,7 +15,7 @@ although it is still fairly powerful.
|
||||
|
||||
## Examples
|
||||
|
||||
* [Standard Pay-to-pubkey-hash Script](http://godoc.org/github.com/kaspanet/kaspad/txscript#example-PayToAddrScript)
|
||||
* [Standard Pay-to-pubkey Script](http://godoc.org/github.com/kaspanet/kaspad/txscript#example-PayToAddrScript)
|
||||
Demonstrates creating a script which pays to a kaspa address. It also
|
||||
prints the created script hex and uses the DisasmString function to display
|
||||
the disassembled script.
|
||||
|
||||
@@ -53,6 +53,7 @@ type Engine struct {
|
||||
numOps int
|
||||
flags ScriptFlags
|
||||
sigCache *SigCache
|
||||
sigCacheECDSA *SigCacheECDSA
|
||||
sigHashReusedValues *consensushashing.SighashReusedValues
|
||||
isP2SH bool // treat execution as pay-to-script-hash
|
||||
savedFirstStack [][]byte // stack from first script for ps2h scripts
|
||||
@@ -369,6 +370,14 @@ func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
||||
return scriptError(ErrPubKeyFormat, "unsupported public key type")
|
||||
}
|
||||
|
||||
func (vm *Engine) checkPubKeyEncodingECDSA(pubKey []byte) error {
|
||||
if len(pubKey) == 33 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scriptError(ErrPubKeyFormat, "unsupported public key type")
|
||||
}
|
||||
|
||||
// checkSignatureLength returns whether or not the passed signature is
|
||||
// in the correct Schnorr format.
|
||||
func (vm *Engine) checkSignatureLength(sig []byte) error {
|
||||
@@ -379,6 +388,14 @@ func (vm *Engine) checkSignatureLength(sig []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *Engine) checkSignatureLengthECDSA(sig []byte) error {
|
||||
if len(sig) != 64 {
|
||||
message := fmt.Sprintf("invalid signature length %d", len(sig))
|
||||
return scriptError(ErrSigLength, message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStack returns the contents of stack as a byte array bottom up
|
||||
func getStack(stack *stack) [][]byte {
|
||||
array := make([][]byte, stack.Depth())
|
||||
@@ -428,7 +445,7 @@ func (vm *Engine) SetAltStack(data [][]byte) {
|
||||
// transaction, and input index. The flags modify the behavior of the script
|
||||
// engine according to the description provided by each flag.
|
||||
func NewEngine(scriptPubKey *externalapi.ScriptPublicKey, tx *externalapi.DomainTransaction, txIdx int, flags ScriptFlags,
|
||||
sigCache *SigCache, sighashReusedValues *consensushashing.SighashReusedValues) (*Engine, error) {
|
||||
sigCache *SigCache, sigCacheECDSA *SigCacheECDSA, sighashReusedValues *consensushashing.SighashReusedValues) (*Engine, error) {
|
||||
|
||||
// The provided transaction input index must refer to a valid input.
|
||||
if txIdx < 0 || txIdx >= len(tx.Inputs) {
|
||||
@@ -446,7 +463,7 @@ func NewEngine(scriptPubKey *externalapi.ScriptPublicKey, tx *externalapi.Domain
|
||||
return nil, scriptError(ErrEvalFalse,
|
||||
"false stack entry at end of script execution")
|
||||
}
|
||||
vm := Engine{scriptVersion: scriptPubKey.Version, flags: flags, sigCache: sigCache}
|
||||
vm := Engine{scriptVersion: scriptPubKey.Version, flags: flags, sigCache: sigCache, sigCacheECDSA: sigCacheECDSA}
|
||||
|
||||
if vm.scriptVersion > constants.MaxScriptPublicKeyVersion {
|
||||
return &vm, nil
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestBadPC(t *testing.T) {
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("NOP", 0), Version: 0}
|
||||
|
||||
for _, test := range tests {
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create script: %v", err)
|
||||
}
|
||||
@@ -124,7 +124,7 @@ func TestCheckErrorCondition(t *testing.T) {
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm(test.script, 0), Version: 0}
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("TestCheckErrorCondition: %d: failed to create script: %v", i, err)
|
||||
}
|
||||
@@ -252,7 +252,7 @@ func TestDisasmPC(t *testing.T) {
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("OP_DROP NOP TRUE", 0), Version: 0}
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create script: %v", err)
|
||||
}
|
||||
@@ -315,7 +315,7 @@ func TestDisasmScript(t *testing.T) {
|
||||
}
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("OP_DROP NOP TRUE", 0), Version: 0}
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create script: %v", err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func ExamplePayToAddrScript() {
|
||||
// which is useful to ensure the accuracy of the address and determine
|
||||
// the address type. It is also required for the upcoming call to
|
||||
// PayToAddrScript.
|
||||
addressStr := "kaspa:qqfgqp8l9l90zwetj84k2jcac2m8falvvyy8xjtnhdac2m8falvvyvc9fuvqt"
|
||||
addressStr := "kaspa:qqj9fg59mptxkr9j0y53j5mwurcmda5mtza9n6v9pm9uj8h0wgk6uma5pvumr"
|
||||
address, err := util.DecodeAddress(addressStr, util.Bech32PrefixKaspa)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -45,15 +45,15 @@ func ExamplePayToAddrScript() {
|
||||
fmt.Println("Script Disassembly:", disasm)
|
||||
|
||||
// Output:
|
||||
// Script Hex: 76aa20128004ff2fcaf13b2b91eb654b1dc2b674f7ec6108734973bb7b856ce9efd8c288ac
|
||||
// Script Disassembly: OP_DUP OP_BLAKE2B 128004ff2fcaf13b2b91eb654b1dc2b674f7ec6108734973bb7b856ce9efd8c2 OP_EQUALVERIFY OP_CHECKSIG
|
||||
// Script Hex: 202454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeac
|
||||
// Script Disassembly: 2454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722dae OP_CHECKSIG
|
||||
}
|
||||
|
||||
// This example demonstrates extracting information from a standard public key
|
||||
// script.
|
||||
func ExampleExtractScriptPubKeyAddress() {
|
||||
// Start with a standard pay-to-pubkey-hash script.
|
||||
scriptHex := "76aa20128004ff2fcaf13b2b91eb65128004ff2fcaf13b2b91eb654b1dc2b674f7ec6188ac"
|
||||
// Start with a standard pay-to-pubkey script.
|
||||
scriptHex := "2089ac24ea10bb751af4939623ccc5e550d96842b64e8fca0f63e94b4373fd555eac"
|
||||
script, err := hex.DecodeString(scriptHex)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -74,6 +74,6 @@ func ExampleExtractScriptPubKeyAddress() {
|
||||
fmt.Println("Address:", address)
|
||||
|
||||
// Output:
|
||||
// Script Class: pubkeyhash
|
||||
// Address: kaspa:qqfgqp8l9l90zwetj84k2y5qqnljljh38v4er6m9fvwu9dn57lkxztu8quj75
|
||||
// Script Class: pubkey
|
||||
// Address: kaspa:qzy6cf82zzah2xh5jwtz8nx9u4gdj6zzke8gljs0v055ksmnl424u6fv7ajrs
|
||||
}
|
||||
|
||||
@@ -204,9 +204,9 @@ const (
|
||||
OpUnknown166 = 0xa6 // 166
|
||||
OpUnknown167 = 0xa7 // 167
|
||||
OpSHA256 = 0xa8 // 168
|
||||
OpUnknown169 = 0xa9 // 169
|
||||
OpCheckMultiSigECDSA = 0xa9 // 169
|
||||
OpBlake2b = 0xaa // 170
|
||||
OpUnknown171 = 0xab // 171
|
||||
OpCheckSigECDSA = 0xab // 171
|
||||
OpCheckSig = 0xac // 172
|
||||
OpCheckSigVerify = 0xad // 173
|
||||
OpCheckMultiSig = 0xae // 174
|
||||
@@ -485,8 +485,10 @@ var opcodeArray = [256]opcode{
|
||||
OpWithin: {OpWithin, "OP_WITHIN", 1, opcodeWithin},
|
||||
|
||||
// Crypto opcodes.
|
||||
OpCheckMultiSigECDSA: {OpCheckMultiSigECDSA, "OP_CHECKMULTISIGECDSA", 1, opcodeCheckMultiSigECDSA},
|
||||
OpSHA256: {OpSHA256, "OP_SHA256", 1, opcodeSha256},
|
||||
OpBlake2b: {OpBlake2b, "OP_BLAKE2B", 1, opcodeBlake2b},
|
||||
OpCheckSigECDSA: {OpCheckSigECDSA, "OP_CHECKSIGECDSA", 1, opcodeCheckSigECDSA},
|
||||
OpCheckSig: {OpCheckSig, "OP_CHECKSIG", 1, opcodeCheckSig},
|
||||
OpCheckSigVerify: {OpCheckSigVerify, "OP_CHECKSIGVERIFY", 1, opcodeCheckSigVerify},
|
||||
OpCheckMultiSig: {OpCheckMultiSig, "OP_CHECKMULTISIG", 1, opcodeCheckMultiSig},
|
||||
@@ -507,8 +509,6 @@ var opcodeArray = [256]opcode{
|
||||
// Undefined opcodes.
|
||||
OpUnknown166: {OpUnknown166, "OP_UNKNOWN166", 1, opcodeInvalid},
|
||||
OpUnknown167: {OpUnknown167, "OP_UNKNOWN167", 1, opcodeInvalid},
|
||||
OpUnknown169: {OpUnknown169, "OP_UNKNOWN169", 1, opcodeInvalid},
|
||||
OpUnknown171: {OpUnknown171, "OP_UNKNOWN171", 1, opcodeInvalid},
|
||||
OpUnknown188: {OpUnknown188, "OP_UNKNOWN188", 1, opcodeInvalid},
|
||||
OpUnknown189: {OpUnknown189, "OP_UNKNOWN189", 1, opcodeInvalid},
|
||||
OpUnknown190: {OpUnknown190, "OP_UNKNOWN190", 1, opcodeInvalid},
|
||||
@@ -1988,7 +1988,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
sigHash, err := consensushashing.CalculateSignatureHash(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
sigHash, err := consensushashing.CalculateSignatureHashSchnorr(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
@@ -2027,6 +2027,89 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func opcodeCheckSigECDSA(op *parsedOpcode, vm *Engine) error {
|
||||
pkBytes, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullSigBytes, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The signature actually needs needs to be longer than this, but at
|
||||
// least 1 byte is needed for the hash type below. The full length is
|
||||
// checked depending on the script flags and upon parsing the signature.
|
||||
if len(fullSigBytes) < 1 {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Trim off hashtype from the signature string and check if the
|
||||
// signature and pubkey conform to the strict encoding requirements
|
||||
// depending on the flags.
|
||||
//
|
||||
// NOTE: When the strict encoding flags are set, any errors in the
|
||||
// signature or public encoding here result in an immediate script error
|
||||
// (and thus no result bool is pushed to the data stack). This differs
|
||||
// from the logic below where any errors in parsing the signature is
|
||||
// treated as the signature failure resulting in false being pushed to
|
||||
// the data stack. This is required because the more general script
|
||||
// validation consensus rules do not have the new strict encoding
|
||||
// requirements enabled by the flags.
|
||||
hashType := consensushashing.SigHashType(fullSigBytes[len(fullSigBytes)-1])
|
||||
sigBytes := fullSigBytes[:len(fullSigBytes)-1]
|
||||
if !hashType.IsStandardSigHashType() {
|
||||
return scriptError(ErrInvalidSigHashType, fmt.Sprintf("invalid hash type 0x%x", hashType))
|
||||
}
|
||||
if err := vm.checkSignatureLengthECDSA(sigBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := vm.checkPubKeyEncodingECDSA(pkBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
sigHash, err := consensushashing.CalculateSignatureHashECDSA(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
pubKey, err := secp256k1.DeserializeECDSAPubKey(pkBytes)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
signature, err := secp256k1.DeserializeECDSASignatureFromSlice(sigBytes)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
var valid bool
|
||||
secpHash := secp256k1.Hash(*sigHash.ByteArray())
|
||||
if vm.sigCacheECDSA != nil {
|
||||
|
||||
valid = vm.sigCacheECDSA.Exists(secpHash, signature, pubKey)
|
||||
if !valid && pubKey.ECDSAVerify(&secpHash, signature) {
|
||||
vm.sigCacheECDSA.Add(secpHash, signature, pubKey)
|
||||
valid = true
|
||||
}
|
||||
} else {
|
||||
valid = pubKey.ECDSAVerify(&secpHash, signature)
|
||||
}
|
||||
|
||||
if !valid && len(sigBytes) > 0 {
|
||||
str := "signature not empty on failed checksig"
|
||||
return scriptError(ErrNullFail, str)
|
||||
}
|
||||
|
||||
vm.dstack.PushBool(valid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// opcodeCheckSigVerify is a combination of opcodeCheckSig and opcodeVerify.
|
||||
// The opcodeCheckSig function is invoked followed by opcodeVerify. See the
|
||||
// documentation for each of those opcodes for more details.
|
||||
@@ -2049,6 +2132,12 @@ type parsedSigInfo struct {
|
||||
parsed bool
|
||||
}
|
||||
|
||||
type parsedSigInfoECDSA struct {
|
||||
signature []byte
|
||||
parsedSignature *secp256k1.ECDSASignature
|
||||
parsed bool
|
||||
}
|
||||
|
||||
// opcodeCheckMultiSig treats the top item on the stack as an integer number of
|
||||
// public keys, followed by that many entries as raw data representing the public
|
||||
// keys, followed by the integer number of signatures, followed by that many
|
||||
@@ -2194,7 +2283,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
sigHash, err := consensushashing.CalculateSignatureHash(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
sigHash, err := consensushashing.CalculateSignatureHashSchnorr(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2231,6 +2320,175 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func opcodeCheckMultiSigECDSA(op *parsedOpcode, vm *Engine) error {
|
||||
numKeys, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
numPubKeys := int(numKeys.Int32())
|
||||
if numPubKeys < 0 {
|
||||
str := fmt.Sprintf("number of pubkeys %d is negative",
|
||||
numPubKeys)
|
||||
return scriptError(ErrInvalidPubKeyCount, str)
|
||||
}
|
||||
if numPubKeys > MaxPubKeysPerMultiSig {
|
||||
str := fmt.Sprintf("too many pubkeys: %d > %d",
|
||||
numPubKeys, MaxPubKeysPerMultiSig)
|
||||
return scriptError(ErrInvalidPubKeyCount, str)
|
||||
}
|
||||
vm.numOps += numPubKeys
|
||||
if vm.numOps > MaxOpsPerScript {
|
||||
str := fmt.Sprintf("exceeded max operation limit of %d",
|
||||
MaxOpsPerScript)
|
||||
return scriptError(ErrTooManyOperations, str)
|
||||
}
|
||||
|
||||
pubKeys := make([][]byte, 0, numPubKeys)
|
||||
for i := 0; i < numPubKeys; i++ {
|
||||
pubKey, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubKeys = append(pubKeys, pubKey)
|
||||
}
|
||||
|
||||
numSigs, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numSignatures := int(numSigs.Int32())
|
||||
if numSignatures < 0 {
|
||||
str := fmt.Sprintf("number of signatures %d is negative",
|
||||
numSignatures)
|
||||
return scriptError(ErrInvalidSignatureCount, str)
|
||||
|
||||
}
|
||||
if numSignatures > numPubKeys {
|
||||
str := fmt.Sprintf("more signatures than pubkeys: %d > %d",
|
||||
numSignatures, numPubKeys)
|
||||
return scriptError(ErrInvalidSignatureCount, str)
|
||||
}
|
||||
|
||||
signatures := make([]*parsedSigInfoECDSA, 0, numSignatures)
|
||||
for i := 0; i < numSignatures; i++ {
|
||||
signature, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sigInfo := &parsedSigInfoECDSA{signature: signature}
|
||||
signatures = append(signatures, sigInfo)
|
||||
}
|
||||
|
||||
success := true
|
||||
numPubKeys++
|
||||
pubKeyIdx := -1
|
||||
signatureIdx := 0
|
||||
|
||||
for numSignatures > 0 {
|
||||
// When there are more signatures than public keys remaining,
|
||||
// there is no way to succeed since too many signatures are
|
||||
// invalid, so exit early.
|
||||
pubKeyIdx++
|
||||
numPubKeys--
|
||||
if numSignatures > numPubKeys {
|
||||
success = false
|
||||
break
|
||||
}
|
||||
|
||||
sigInfo := signatures[signatureIdx]
|
||||
pubKey := pubKeys[pubKeyIdx]
|
||||
|
||||
// The order of the signature and public key evaluation is
|
||||
// important here since it can be distinguished by an
|
||||
// OP_CHECKMULTISIG NOT when the strict encoding flag is set.
|
||||
|
||||
rawSig := sigInfo.signature
|
||||
if len(rawSig) == 0 {
|
||||
// Skip to the next pubkey if signature is empty.
|
||||
continue
|
||||
}
|
||||
|
||||
// Split the signature into hash type and signature components.
|
||||
hashType := consensushashing.SigHashType(rawSig[len(rawSig)-1])
|
||||
signature := rawSig[:len(rawSig)-1]
|
||||
|
||||
// Only parse and check the signature encoding once.
|
||||
var parsedSig *secp256k1.ECDSASignature
|
||||
if !sigInfo.parsed {
|
||||
if !hashType.IsStandardSigHashType() {
|
||||
return scriptError(ErrInvalidSigHashType, fmt.Sprintf("invalid hash type 0x%x", hashType))
|
||||
}
|
||||
if err := vm.checkSignatureLengthECDSA(signature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the signature.
|
||||
parsedSig, err = secp256k1.DeserializeECDSASignatureFromSlice(signature)
|
||||
sigInfo.parsed = true
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sigInfo.parsedSignature = parsedSig
|
||||
} else {
|
||||
// Skip to the next pubkey if the signature is invalid.
|
||||
if sigInfo.parsedSignature == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the already parsed signature.
|
||||
parsedSig = sigInfo.parsedSignature
|
||||
}
|
||||
|
||||
if err := vm.checkPubKeyEncodingECDSA(pubKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the pubkey.
|
||||
parsedPubKey, err := secp256k1.DeserializeECDSAPubKey(pubKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
sigHash, err := consensushashing.CalculateSignatureHashECDSA(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secpHash := secp256k1.Hash(*sigHash.ByteArray())
|
||||
var valid bool
|
||||
if vm.sigCacheECDSA != nil {
|
||||
valid = vm.sigCacheECDSA.Exists(secpHash, parsedSig, parsedPubKey)
|
||||
if !valid && parsedPubKey.ECDSAVerify(&secpHash, parsedSig) {
|
||||
vm.sigCacheECDSA.Add(secpHash, parsedSig, parsedPubKey)
|
||||
valid = true
|
||||
}
|
||||
} else {
|
||||
valid = parsedPubKey.ECDSAVerify(&secpHash, parsedSig)
|
||||
}
|
||||
|
||||
if valid {
|
||||
// PubKey verified, move on to the next signature.
|
||||
signatureIdx++
|
||||
numSignatures--
|
||||
}
|
||||
}
|
||||
|
||||
if !success {
|
||||
for _, sig := range signatures {
|
||||
if len(sig.signature) > 0 {
|
||||
str := "not all signatures empty on failed checkmultisig"
|
||||
return scriptError(ErrNullFail, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.dstack.PushBool(success)
|
||||
return nil
|
||||
}
|
||||
|
||||
// opcodeCheckMultiSigVerify is a combination of opcodeCheckMultiSig and
|
||||
// opcodeVerify. The opcodeCheckMultiSig is invoked followed by opcodeVerify.
|
||||
// See the documentation for each of those opcodes for more details.
|
||||
|
||||
@@ -71,9 +71,9 @@ func TestOpcodeDisasm(t *testing.T) {
|
||||
0x9f: "OP_LESSTHAN", 0xa0: "OP_GREATERTHAN",
|
||||
0xa1: "OP_LESSTHANOREQUAL", 0xa2: "OP_GREATERTHANOREQUAL",
|
||||
0xa3: "OP_MIN", 0xa4: "OP_MAX", 0xa5: "OP_WITHIN",
|
||||
0xa8: "OP_SHA256",
|
||||
0xa8: "OP_SHA256", 0xa9: "OP_CHECKMULTISIGECDSA",
|
||||
0xaa: "OP_BLAKE2B",
|
||||
0xac: "OP_CHECKSIG", 0xad: "OP_CHECKSIGVERIFY",
|
||||
0xab: "OP_CHECKSIGECDSA", 0xac: "OP_CHECKSIG", 0xad: "OP_CHECKSIGVERIFY",
|
||||
0xae: "OP_CHECKMULTISIG", 0xaf: "OP_CHECKMULTISIGVERIFY",
|
||||
0xb0: "OP_CHECKLOCKTIMEVERIFY", 0xb1: "OP_CHECKSEQUENCEVERIFY",
|
||||
0xfa: "OP_SMALLINTEGER", 0xfb: "OP_PUBKEYS",
|
||||
@@ -186,8 +186,8 @@ func TestOpcodeDisasm(t *testing.T) {
|
||||
}
|
||||
|
||||
func isOpUnknown(opcodeVal int) bool {
|
||||
return opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc || opcodeVal == 0xab ||
|
||||
opcodeVal == 0xa6 || opcodeVal == 0xa7 || opcodeVal == 0xa9
|
||||
return opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc ||
|
||||
opcodeVal == 0xa6 || opcodeVal == 0xa7
|
||||
}
|
||||
|
||||
func isNumberedNop(opcodeVal int) bool {
|
||||
|
||||
@@ -260,8 +260,10 @@ func createSpendingTx(sigScript []byte, scriptPubKey *externalapi.ScriptPublicKe
|
||||
func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
|
||||
// Create a signature cache to use only if requested.
|
||||
var sigCache *SigCache
|
||||
var sigCacheECDSA *SigCacheECDSA
|
||||
if useSigCache {
|
||||
sigCache = NewSigCache(10)
|
||||
sigCacheECDSA = NewSigCacheECDSA(10)
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
@@ -343,7 +345,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
|
||||
// used, then create a new engine to execute the scripts.
|
||||
tx := createSpendingTx(scriptSig, scriptPubKey)
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, sigCacheECDSA, &consensushashing.SighashReusedValues{})
|
||||
if err == nil {
|
||||
err = vm.Execute()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type sigCacheEntry struct {
|
||||
pubKey *secp256k1.SchnorrPublicKey
|
||||
}
|
||||
|
||||
// SigCache implements an ECDSA signature verification cache with a randomized
|
||||
// SigCache implements an Schnorr signature verification cache with a randomized
|
||||
// entry eviction policy. Only valid signatures will be added to the cache. The
|
||||
// benefits of SigCache are two fold. Firstly, usage of SigCache mitigates a DoS
|
||||
// attack wherein an attack causes a victim's client to hang due to worst-case
|
||||
|
||||
90
domain/consensus/utils/txscript/sigcache_ecdsa.go
Normal file
90
domain/consensus/utils/txscript/sigcache_ecdsa.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package txscript
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
)
|
||||
|
||||
// sigCacheEntryECDSA represents an entry in the SigCache. Entries within the
|
||||
// SigCache are keyed according to the sigHash of the signature. In the
|
||||
// scenario of a cache-hit (according to the sigHash), an additional comparison
|
||||
// of the signature, and public key will be executed in order to ensure a complete
|
||||
// match. In the occasion that two sigHashes collide, the newer sigHash will
|
||||
// simply overwrite the existing entry.
|
||||
type sigCacheEntryECDSA struct {
|
||||
sig *secp256k1.ECDSASignature
|
||||
pubKey *secp256k1.ECDSAPublicKey
|
||||
}
|
||||
|
||||
// SigCacheECDSA implements an ECDSA signature verification cache with a randomized
|
||||
// entry eviction policy. Only valid signatures will be added to the cache. The
|
||||
// benefits of SigCache are two fold. Firstly, usage of SigCache mitigates a DoS
|
||||
// attack wherein an attack causes a victim's client to hang due to worst-case
|
||||
// behavior triggered while processing attacker crafted invalid transactions. A
|
||||
// detailed description of the mitigated DoS attack can be found here:
|
||||
// https://bitslog.wordpress.com/2013/01/23/fixed-bitcoin-vulnerability-explanation-why-the-signature-cache-is-a-dos-protection/.
|
||||
// Secondly, usage of the SigCache introduces a signature verification
|
||||
// optimization which speeds up the validation of transactions within a block,
|
||||
// if they've already been seen and verified within the mempool.
|
||||
type SigCacheECDSA struct {
|
||||
validSigs map[secp256k1.Hash]sigCacheEntryECDSA
|
||||
maxEntries uint
|
||||
}
|
||||
|
||||
// NewSigCacheECDSA creates and initializes a new instance of SigCache. Its sole
|
||||
// parameter 'maxEntries' represents the maximum number of entries allowed to
|
||||
// exist in the SigCache at any particular moment. Random entries are evicted
|
||||
// to make room for new entries that would cause the number of entries in the
|
||||
// cache to exceed the max.
|
||||
func NewSigCacheECDSA(maxEntries uint) *SigCacheECDSA {
|
||||
return &SigCacheECDSA{
|
||||
validSigs: make(map[secp256k1.Hash]sigCacheEntryECDSA, maxEntries),
|
||||
maxEntries: maxEntries,
|
||||
}
|
||||
}
|
||||
|
||||
// Exists returns true if an existing entry of 'sig' over 'sigHash' for public
|
||||
// key 'pubKey' is found within the SigCache. Otherwise, false is returned.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access. Readers won't be blocked
|
||||
// unless there exists a writer, adding an entry to the SigCache.
|
||||
func (s *SigCacheECDSA) Exists(sigHash secp256k1.Hash, sig *secp256k1.ECDSASignature, pubKey *secp256k1.ECDSAPublicKey) bool {
|
||||
entry, ok := s.validSigs[sigHash]
|
||||
|
||||
return ok && entry.pubKey.IsEqual(pubKey) && entry.sig.IsEqual(sig)
|
||||
}
|
||||
|
||||
// Add adds an entry for a signature over 'sigHash' under public key 'pubKey'
|
||||
// to the signature cache. In the event that the SigCache is 'full', an
|
||||
// existing entry is randomly chosen to be evicted in order to make space for
|
||||
// the new entry.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access. Writers will block
|
||||
// simultaneous readers until function execution has concluded.
|
||||
func (s *SigCacheECDSA) Add(sigHash secp256k1.Hash, sig *secp256k1.ECDSASignature, pubKey *secp256k1.ECDSAPublicKey) {
|
||||
if s.maxEntries == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If adding this new entry will put us over the max number of allowed
|
||||
// entries, then evict an entry.
|
||||
if uint(len(s.validSigs)+1) > s.maxEntries {
|
||||
// Remove a random entry from the map. Relying on the random
|
||||
// starting point of Go's map iteration. It's worth noting that
|
||||
// the random iteration starting point is not 100% guaranteed
|
||||
// by the spec, however most Go compilers support it.
|
||||
// Ultimately, the iteration order isn't important here because
|
||||
// in order to manipulate which items are evicted, an adversary
|
||||
// would need to be able to execute preimage attacks on the
|
||||
// hashing function in order to start eviction at a specific
|
||||
// entry.
|
||||
for sigEntry := range s.validSigs {
|
||||
delete(s.validSigs, sigEntry)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.validSigs[sigHash] = sigCacheEntryECDSA{sig, pubKey}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// public key and the public key. This function is used to generate randomized
|
||||
// test data.
|
||||
func genRandomSig() (*secp256k1.Hash, *secp256k1.SchnorrSignature, *secp256k1.SchnorrPublicKey, error) {
|
||||
privKey, err := secp256k1.GeneratePrivateKey()
|
||||
privKey, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
func RawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
key *secp256k1.SchnorrKeyPair, sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
|
||||
hash, err := consensushashing.CalculateSignatureHash(tx, idx, hashType, sighashReusedValues)
|
||||
hash, err := consensushashing.CalculateSignatureHashSchnorr(tx, idx, hashType, sighashReusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,8 +32,26 @@ func RawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType conse
|
||||
return append(signature.Serialize()[:], byte(hashType)), nil
|
||||
}
|
||||
|
||||
// RawTxInSignatureECDSA returns the serialized ECDSA signature for the input idx of
|
||||
// the given transaction, with hashType appended to it.
|
||||
func RawTxInSignatureECDSA(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
key *secp256k1.ECDSAPrivateKey, sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
|
||||
hash, err := consensushashing.CalculateSignatureHashECDSA(tx, idx, hashType, sighashReusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secpHash := secp256k1.Hash(*hash.ByteArray())
|
||||
signature, err := key.ECDSASign(&secpHash)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("cannot sign tx input: %s", err)
|
||||
}
|
||||
|
||||
return append(signature.Serialize()[:], byte(hashType)), nil
|
||||
}
|
||||
|
||||
// SignatureScript creates an input signature script for tx to spend KAS sent
|
||||
// from a previous output to the owner of privKey. tx must include all
|
||||
// from a previous output to the owner of a Schnorr private key. tx must include all
|
||||
// transaction inputs and outputs, however txin scripts are allowed to be filled
|
||||
// or empty. The returned script is calculated to be used as the idx'th txin
|
||||
// sigscript for tx. script is the ScriptPublicKey of the previous output being used
|
||||
@@ -48,16 +66,26 @@ func SignatureScript(tx *externalapi.DomainTransaction, idx int, hashType consen
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk, err := privKey.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkData, err := pk.Serialize()
|
||||
return NewScriptBuilder().AddData(sig).Script()
|
||||
}
|
||||
|
||||
// SignatureScriptECDSA creates an input signature script for tx to spend KAS sent
|
||||
// from a previous output to the owner of an ECDSA private key. tx must include all
|
||||
// transaction inputs and outputs, however txin scripts are allowed to be filled
|
||||
// or empty. The returned script is calculated to be used as the idx'th txin
|
||||
// sigscript for tx. script is the ScriptPublicKey of the previous output being used
|
||||
// as the idx'th input. privKey is serialized in either a compressed or
|
||||
// uncompressed format based on compress. This format must match the same format
|
||||
// used to generate the payment address, or the script validation will fail.
|
||||
func SignatureScriptECDSA(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
privKey *secp256k1.ECDSAPrivateKey, sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
|
||||
sig, err := RawTxInSignatureECDSA(tx, idx, hashType, privKey, sighashReusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewScriptBuilder().AddData(sig).AddData(pkData[:]).Script()
|
||||
return NewScriptBuilder().AddData(sig).Script()
|
||||
}
|
||||
|
||||
func sign(dagParams *dagconfig.Params, tx *externalapi.DomainTransaction, idx int,
|
||||
@@ -71,7 +99,7 @@ func sign(dagParams *dagconfig.Params, tx *externalapi.DomainTransaction, idx in
|
||||
}
|
||||
|
||||
switch class {
|
||||
case PubKeyHashTy:
|
||||
case PubKeyTy:
|
||||
// look up key for address
|
||||
key, err := kdb.GetKey(address)
|
||||
if err != nil {
|
||||
|
||||
@@ -54,7 +54,7 @@ func checkScripts(msg string, tx *externalapi.DomainTransaction, idx int, sigScr
|
||||
tx.Inputs[idx].SignatureScript = sigScript
|
||||
var flags ScriptFlags
|
||||
vm, err := NewEngine(scriptPubKey, tx, idx,
|
||||
flags, nil, &consensushashing.SighashReusedValues{})
|
||||
flags, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to make script engine for %s: %v",
|
||||
msg, err)
|
||||
@@ -143,7 +143,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Pay to Pubkey Hash (merging with correct)
|
||||
// Pay to Pubkey (merging with correct)
|
||||
for _, hashType := range hashTypes {
|
||||
for _, input := range tx.Inputs {
|
||||
input.UTXOEntry = utxo.NewUTXOEntry(500, scriptPubKey, false, 100)
|
||||
@@ -180,19 +180,19 @@ func TestSignTxOutput(t *testing.T) {
|
||||
|
||||
err = checkScripts(msg, tx, i, sigScript, scriptPubKey)
|
||||
if err != nil {
|
||||
t.Errorf("twice signed script invalid for "+
|
||||
t.Fatalf("twice signed script invalid for "+
|
||||
"%s: %v", msg, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pay to Pubkey Hash (compressed)
|
||||
// Pay to Pubkey
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.Inputs {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
key, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
@@ -213,8 +213,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -238,12 +237,12 @@ func TestSignTxOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Pay to Pubkey Hash with duplicate merge
|
||||
// Pay to Pubkey with duplicate merge
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.Inputs {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
key, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
@@ -264,8 +263,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -316,12 +314,12 @@ func TestSignTxOutput(t *testing.T) {
|
||||
|
||||
// As before, but with p2sh now.
|
||||
|
||||
// Pay to Pubkey Hash
|
||||
// Pay to Pubkey
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.Inputs {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
key, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
@@ -342,8 +340,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -381,12 +378,12 @@ func TestSignTxOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Pay to Pubkey Hash (compressed) with duplicate merge
|
||||
// Pay to Pubkey with duplicate merge
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.Inputs {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
key, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
@@ -407,8 +404,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -474,9 +470,9 @@ func TestSignTxOutput(t *testing.T) {
|
||||
}
|
||||
|
||||
func generateKeys() (keyPair *secp256k1.SchnorrKeyPair, scriptPublicKey *externalapi.ScriptPublicKey,
|
||||
addressPubKeyHash *util.AddressPubKeyHash, err error) {
|
||||
addressPubKeyHash *util.AddressPublicKey, err error) {
|
||||
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
key, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Errorf("failed to make privKey: %s", err)
|
||||
}
|
||||
@@ -490,8 +486,7 @@ func generateKeys() (keyPair *secp256k1.SchnorrKeyPair, scriptPublicKey *externa
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Errorf("failed to serialize a pubkey for %s: %s", pubKey, err)
|
||||
}
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Errorf("failed to make address for %s: %s", serializedPubKey, err)
|
||||
}
|
||||
@@ -534,12 +529,10 @@ var (
|
||||
oldCompressedScriptPubKey = &externalapi.ScriptPublicKey{[]byte{0x76, 0xa9, 0x14, 0x27, 0x4d, 0x9f, 0x7f,
|
||||
0x61, 0x7e, 0x7c, 0x7a, 0x1c, 0x1f, 0xb2, 0x75, 0x79, 0x10,
|
||||
0x43, 0x65, 0x68, 0x27, 0x9d, 0x86, 0x88, 0xac}, 0}
|
||||
p2pkhScriptPubKey = &externalapi.ScriptPublicKey{[]byte{0x76, 0xaa, 0x20,
|
||||
0x51, 0x9c, 0x25, 0xca, 0x95, 0xa0, 0xd8, 0xcd,
|
||||
0xf5, 0xb8, 0x3f, 0x96, 0xa1, 0x5e, 0x8c, 0x1a,
|
||||
0xae, 0x33, 0xeb, 0x50, 0xc8, 0x66, 0xc9, 0xd0,
|
||||
0xa5, 0xce, 0x3e, 0x5f, 0x6b, 0x3b, 0x38, 0x8d,
|
||||
0x88, 0xac}, 0}
|
||||
p2pkScriptPubKey = &externalapi.ScriptPublicKey{[]byte{0x20, 0xb2, 0x52, 0xf0, 0x49, 0x85, 0x78, 0x03, 0x03,
|
||||
0xc8, 0x7d, 0xce, 0x51, 0x7f, 0xa8, 0x69, 0x0b,
|
||||
0x91, 0x95, 0xf4, 0xf3, 0x5c, 0x26, 0x73, 0x05,
|
||||
0x05, 0xa2, 0xee, 0xbc, 0x09, 0x38, 0x34, 0x3a, 0xac}, 0}
|
||||
shortScriptPubKey = &externalapi.ScriptPublicKey{[]byte{0x76, 0xa9, 0x14, 0xd1, 0x7c, 0xb5,
|
||||
0xeb, 0xa4, 0x02, 0xcb, 0x68, 0xe0, 0x69, 0x56, 0xbf, 0x32,
|
||||
0x53, 0x90, 0x0e, 0x0a, 0x88, 0xac}, 0}
|
||||
@@ -638,7 +631,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -654,7 +647,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -663,7 +656,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal + fee,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -679,7 +672,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -695,7 +688,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -711,7 +704,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -727,7 +720,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: false,
|
||||
inputValidates: false,
|
||||
@@ -743,7 +736,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: false,
|
||||
inputValidates: false,
|
||||
@@ -759,7 +752,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -768,7 +761,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal + fee,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -784,7 +777,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -793,7 +786,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal + fee,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -813,7 +806,7 @@ var sigScriptTests = []tstSigScript{
|
||||
func TestSignatureScript(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
privKey, _ := secp256k1.DeserializePrivateKey(&privKeyD)
|
||||
privKey, _ := secp256k1.DeserializeSchnorrPrivateKey(&privKeyD)
|
||||
|
||||
nexttest:
|
||||
for i := range sigScriptTests {
|
||||
@@ -877,7 +870,7 @@ nexttest:
|
||||
// Validate tx input scripts
|
||||
var scriptFlags ScriptFlags
|
||||
for j := range tx.Inputs {
|
||||
vm, err := NewEngine(sigScriptTests[i].inputs[j].txout.ScriptPublicKey, tx, j, scriptFlags, nil,
|
||||
vm, err := NewEngine(sigScriptTests[i].inputs[j].txout.ScriptPublicKey, tx, j, scriptFlags, nil, nil,
|
||||
&consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("cannot create script vm for test %v: %v",
|
||||
|
||||
@@ -21,15 +21,24 @@ type ScriptClass byte
|
||||
// Classes of script payment known about in the blockDAG.
|
||||
const (
|
||||
NonStandardTy ScriptClass = iota // None of the recognized forms.
|
||||
PubKeyHashTy // Pay pubkey hash.
|
||||
PubKeyTy // Pay to pubkey.
|
||||
PubKeyECDSATy // Pay to pubkey ECDSA.
|
||||
ScriptHashTy // Pay to script hash.
|
||||
)
|
||||
|
||||
// Script public key versions for address types.
|
||||
const (
|
||||
addressPublicKeyScriptPublicKeyVersion = 0
|
||||
addressPublicKeyECDSAScriptPublicKeyVersion = 0
|
||||
addressScriptHashScriptPublicKeyVersion = 0
|
||||
)
|
||||
|
||||
// scriptClassToName houses the human-readable strings which describe each
|
||||
// script class.
|
||||
var scriptClassToName = []string{
|
||||
NonStandardTy: "nonstandard",
|
||||
PubKeyHashTy: "pubkeyhash",
|
||||
PubKeyTy: "pubkey",
|
||||
PubKeyECDSATy: "pubkeyecdsa",
|
||||
ScriptHashTy: "scripthash",
|
||||
}
|
||||
|
||||
@@ -43,24 +52,32 @@ func (t ScriptClass) String() string {
|
||||
return scriptClassToName[t]
|
||||
}
|
||||
|
||||
// isPubkeyHash returns true if the script passed is a pay-to-pubkey-hash
|
||||
// isPayToPubkey returns true if the script passed is a pay-to-pubkey
|
||||
// transaction, false otherwise.
|
||||
func isPubkeyHash(pops []parsedOpcode) bool {
|
||||
return len(pops) == 5 &&
|
||||
pops[0].opcode.value == OpDup &&
|
||||
pops[1].opcode.value == OpBlake2b &&
|
||||
pops[2].opcode.value == OpData32 &&
|
||||
pops[3].opcode.value == OpEqualVerify &&
|
||||
pops[4].opcode.value == OpCheckSig
|
||||
func isPayToPubkey(pops []parsedOpcode) bool {
|
||||
return len(pops) == 2 &&
|
||||
pops[0].opcode.value == OpData32 &&
|
||||
pops[1].opcode.value == OpCheckSig
|
||||
}
|
||||
|
||||
// isPayToPubkeyECDSA returns true if the script passed is an ECDSA pay-to-pubkey
|
||||
// transaction, false otherwise.
|
||||
func isPayToPubkeyECDSA(pops []parsedOpcode) bool {
|
||||
return len(pops) == 2 &&
|
||||
pops[0].opcode.value == OpData33 &&
|
||||
pops[1].opcode.value == OpCheckSigECDSA
|
||||
|
||||
}
|
||||
|
||||
// scriptType returns the type of the script being inspected from the known
|
||||
// standard types.
|
||||
func typeOfScript(pops []parsedOpcode) ScriptClass {
|
||||
if isPubkeyHash(pops) {
|
||||
return PubKeyHashTy
|
||||
} else if isScriptHash(pops) {
|
||||
switch {
|
||||
case isPayToPubkey(pops):
|
||||
return PubKeyTy
|
||||
case isPayToPubkeyECDSA(pops):
|
||||
return PubKeyECDSATy
|
||||
case isScriptHash(pops):
|
||||
return ScriptHashTy
|
||||
}
|
||||
return NonStandardTy
|
||||
@@ -85,8 +102,8 @@ func GetScriptClass(script []byte) ScriptClass {
|
||||
func expectedInputs(pops []parsedOpcode, class ScriptClass) int {
|
||||
switch class {
|
||||
|
||||
case PubKeyHashTy:
|
||||
return 2
|
||||
case PubKeyTy:
|
||||
return 1
|
||||
|
||||
case ScriptHashTy:
|
||||
// Not including script. That is handled by the caller.
|
||||
@@ -169,12 +186,21 @@ func CalcScriptInfo(sigScript, scriptPubKey []byte, isP2SH bool) (*ScriptInfo, e
|
||||
return si, nil
|
||||
}
|
||||
|
||||
// payToPubKeyHashScript creates a new script to pay a transaction
|
||||
// output to a 20-byte pubkey hash. It is expected that the input is a valid
|
||||
// hash.
|
||||
func payToPubKeyHashScript(pubKeyHash []byte) ([]byte, error) {
|
||||
return NewScriptBuilder().AddOp(OpDup).AddOp(OpBlake2b).
|
||||
AddData(pubKeyHash).AddOp(OpEqualVerify).AddOp(OpCheckSig).
|
||||
// payToPubKeyScript creates a new script to pay a transaction
|
||||
// output to a 32-byte pubkey.
|
||||
func payToPubKeyScript(pubKey []byte) ([]byte, error) {
|
||||
return NewScriptBuilder().
|
||||
AddData(pubKey).
|
||||
AddOp(OpCheckSig).
|
||||
Script()
|
||||
}
|
||||
|
||||
// payToPubKeyScript creates a new script to pay a transaction
|
||||
// output to a 33-byte pubkey.
|
||||
func payToPubKeyScriptECDSA(pubKey []byte) ([]byte, error) {
|
||||
return NewScriptBuilder().
|
||||
AddData(pubKey).
|
||||
AddOp(OpCheckSigECDSA).
|
||||
Script()
|
||||
}
|
||||
|
||||
@@ -190,16 +216,29 @@ func payToScriptHashScript(scriptHash []byte) ([]byte, error) {
|
||||
func PayToAddrScript(addr util.Address) (*externalapi.ScriptPublicKey, error) {
|
||||
const nilAddrErrStr = "unable to generate payment script for nil address"
|
||||
switch addr := addr.(type) {
|
||||
case *util.AddressPubKeyHash:
|
||||
case *util.AddressPublicKey:
|
||||
if addr == nil {
|
||||
return nil, scriptError(ErrUnsupportedAddress,
|
||||
nilAddrErrStr)
|
||||
}
|
||||
script, err := payToPubKeyHashScript(addr.ScriptAddress())
|
||||
script, err := payToPubKeyScript(addr.ScriptAddress())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &externalapi.ScriptPublicKey{script, constants.MaxScriptPublicKeyVersion}, err
|
||||
|
||||
return &externalapi.ScriptPublicKey{script, addressPublicKeyScriptPublicKeyVersion}, err
|
||||
|
||||
case *util.AddressPublicKeyECDSA:
|
||||
if addr == nil {
|
||||
return nil, scriptError(ErrUnsupportedAddress,
|
||||
nilAddrErrStr)
|
||||
}
|
||||
script, err := payToPubKeyScriptECDSA(addr.ScriptAddress())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &externalapi.ScriptPublicKey{script, addressPublicKeyECDSAScriptPublicKeyVersion}, err
|
||||
|
||||
case *util.AddressScriptHash:
|
||||
if addr == nil {
|
||||
@@ -210,7 +249,8 @@ func PayToAddrScript(addr util.Address) (*externalapi.ScriptPublicKey, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &externalapi.ScriptPublicKey{script, constants.MaxScriptPublicKeyVersion}, err
|
||||
|
||||
return &externalapi.ScriptPublicKey{script, addressScriptHashScriptPublicKeyVersion}, err
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("unable to generate payment script for unsupported "+
|
||||
@@ -276,12 +316,24 @@ func ExtractScriptPubKeyAddress(scriptPubKey *externalapi.ScriptPublicKey, dagPa
|
||||
|
||||
scriptClass := typeOfScript(pops)
|
||||
switch scriptClass {
|
||||
case PubKeyHashTy:
|
||||
// A pay-to-pubkey-hash script is of the form:
|
||||
// OP_DUP OP_BLAKE2B <hash> OP_EQUALVERIFY OP_CHECKSIG
|
||||
// Therefore the pubkey hash is the 3rd item on the stack.
|
||||
// If the pubkey hash is invalid for some reason, return a nil address.
|
||||
addr, err := util.NewAddressPubKeyHash(pops[2].data,
|
||||
case PubKeyTy:
|
||||
// A pay-to-pubkey script is of the form:
|
||||
// <pubkey> OP_CHECKSIG
|
||||
// Therefore the pubkey is the first item on the stack.
|
||||
// If the pubkey is invalid for some reason, return a nil address.
|
||||
addr, err := util.NewAddressPublicKey(pops[0].data,
|
||||
dagParams.Prefix)
|
||||
if err != nil {
|
||||
return scriptClass, nil, nil
|
||||
}
|
||||
return scriptClass, addr, nil
|
||||
|
||||
case PubKeyECDSATy:
|
||||
// A pay-to-pubkey script is of the form:
|
||||
// <pubkey> OP_CHECKSIGECDSA
|
||||
// Therefore the pubkey is the first item on the stack.
|
||||
// If the pubkey is invalid for some reason, return a nil address.
|
||||
addr, err := util.NewAddressPublicKeyECDSA(pops[0].data,
|
||||
dagParams.Prefix)
|
||||
if err != nil {
|
||||
return scriptClass, nil, nil
|
||||
|
||||
@@ -28,14 +28,27 @@ func mustParseShortForm(script string, version uint16) []byte {
|
||||
return s
|
||||
}
|
||||
|
||||
// newAddressPubKeyHash returns a new util.AddressPubKeyHash from the
|
||||
// provided hash. It panics if an error occurs. This is only used in the tests
|
||||
// newAddressPublicKey returns a new util.AddressPublicKey from the
|
||||
// provided public key. It panics if an error occurs. This is only used in the tests
|
||||
// as a helper since the only way it can fail is if there is an error in the
|
||||
// test source code.
|
||||
func newAddressPubKeyHash(pkHash []byte) util.Address {
|
||||
addr, err := util.NewAddressPubKeyHash(pkHash, util.Bech32PrefixKaspa)
|
||||
func newAddressPublicKey(publicKey []byte) util.Address {
|
||||
addr, err := util.NewAddressPublicKey(publicKey, util.Bech32PrefixKaspa)
|
||||
if err != nil {
|
||||
panic("invalid public key hash in test source")
|
||||
panic("invalid public key in test source")
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// newAddressPublicKeyECDSA returns a new util.AddressPublicKeyECDSA from the
|
||||
// provided public key. It panics if an error occurs. This is only used in the tests
|
||||
// as a helper since the only way it can fail is if there is an error in the
|
||||
// test source code.
|
||||
func newAddressPublicKeyECDSA(publicKey []byte) util.Address {
|
||||
addr, err := util.NewAddressPublicKeyECDSA(publicKey, util.Bech32PrefixKaspa)
|
||||
if err != nil {
|
||||
panic("invalid public key in test source")
|
||||
}
|
||||
|
||||
return addr
|
||||
@@ -67,15 +80,22 @@ func TestExtractScriptPubKeyAddrs(t *testing.T) {
|
||||
class ScriptClass
|
||||
}{
|
||||
{
|
||||
name: "standard p2pkh",
|
||||
name: "standard p2pk",
|
||||
script: &externalapi.ScriptPublicKey{
|
||||
Script: hexToBytes("76aa20ad06dd6ddee55cbca9a9e3713bd" +
|
||||
"7587509a30564ad06dd6ddee55cbca9a9e37188ac"),
|
||||
Script: hexToBytes("202454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeac"),
|
||||
Version: 0,
|
||||
},
|
||||
addr: newAddressPubKeyHash(hexToBytes("ad06dd6ddee5" +
|
||||
"5cbca9a9e3713bd7587509a30564ad06dd6ddee55cbca9a9e371")),
|
||||
class: PubKeyHashTy,
|
||||
addr: newAddressPublicKey(hexToBytes("2454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722dae")),
|
||||
class: PubKeyTy,
|
||||
},
|
||||
{
|
||||
name: "standard p2pk ECDSA",
|
||||
script: &externalapi.ScriptPublicKey{
|
||||
Script: hexToBytes("212454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeaaab"),
|
||||
Version: 0,
|
||||
},
|
||||
addr: newAddressPublicKeyECDSA(hexToBytes("2454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeaa")),
|
||||
class: PubKeyECDSATy,
|
||||
},
|
||||
{
|
||||
name: "standard p2sh",
|
||||
@@ -216,8 +236,8 @@ func TestCalcScriptInfo(t *testing.T) {
|
||||
{
|
||||
// Invented scripts, the hashes do not match
|
||||
name: "p2sh standard script",
|
||||
sigScript: "1 81 DATA_37 DUP BLAKE2B DATA_32 0x010203" +
|
||||
"0405060708090a0b0c0d0e0f1011121314fe441065b6532231de2fac56 EQUALVERIFY " +
|
||||
sigScript: "1 81 DATA_34 DATA_32 0x010203" +
|
||||
"0405060708090a0b0c0d0e0f1011121314fe441065b6532231de2fac56 " +
|
||||
"CHECKSIG",
|
||||
scriptPubKey: "BLAKE2B DATA_32 0xfe441065b6532231de2fac56" +
|
||||
"3152205ec4f59c74fe441065b6532231de2fac56 EQUAL",
|
||||
@@ -225,7 +245,7 @@ func TestCalcScriptInfo(t *testing.T) {
|
||||
scriptInfo: ScriptInfo{
|
||||
ScriptPubKeyClass: ScriptHashTy,
|
||||
NumInputs: 3,
|
||||
ExpectedInputs: 3, // nonstandard p2sh.
|
||||
ExpectedInputs: 2, // nonstandard p2sh.
|
||||
SigOps: 1,
|
||||
},
|
||||
},
|
||||
@@ -303,10 +323,10 @@ func (b *bogusAddress) Prefix() util.Bech32Prefix {
|
||||
func TestPayToAddrScript(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p2pkhMain, err := util.NewAddressPubKeyHash(hexToBytes("e34cce70c86"+
|
||||
p2pkMain, err := util.NewAddressPublicKey(hexToBytes("e34cce70c86"+
|
||||
"373273efcc54ce7d2a491bb4a0e84e34cce70c86373273efcc54c"), util.Bech32PrefixKaspa)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create public key hash address: %v", err)
|
||||
t.Fatalf("Unable to create public key address: %v", err)
|
||||
}
|
||||
|
||||
p2shMain, err := util.NewAddressScriptHashFromHash(hexToBytes("e8c300"+
|
||||
@@ -325,11 +345,11 @@ func TestPayToAddrScript(t *testing.T) {
|
||||
expectedVersion uint16
|
||||
err error
|
||||
}{
|
||||
// pay-to-pubkey-hash address on mainnet
|
||||
// pay-to-pubkey address on mainnet
|
||||
{
|
||||
p2pkhMain,
|
||||
"DUP BLAKE2B DATA_32 0xe34cce70c86373273efcc54ce7d2a4" +
|
||||
"91bb4a0e84e34cce70c86373273efcc54c EQUALVERIFY CHECKSIG",
|
||||
p2pkMain,
|
||||
"DATA_32 0xe34cce70c86373273efcc54ce7d2a4" +
|
||||
"91bb4a0e84e34cce70c86373273efcc54c CHECKSIG",
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
@@ -343,7 +363,7 @@ func TestPayToAddrScript(t *testing.T) {
|
||||
},
|
||||
|
||||
// Supported address types with nil pointers.
|
||||
{(*util.AddressPubKeyHash)(nil), "", 0, errUnsupportedAddress},
|
||||
{(*util.AddressPublicKey)(nil), "", 0, errUnsupportedAddress},
|
||||
{(*util.AddressScriptHash)(nil), "", 0, errUnsupportedAddress},
|
||||
|
||||
// Unsupported address type.
|
||||
@@ -392,17 +412,21 @@ var scriptClassTests = []struct {
|
||||
}{
|
||||
// p2pk
|
||||
{
|
||||
name: "Pay Pubkey",
|
||||
script: "DATA_65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e" +
|
||||
"97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e16" +
|
||||
"0bfa9b8b64f9d4c03f999b8643f656b412a3 CHECKSIG",
|
||||
class: NonStandardTy,
|
||||
name: "Pay Pubkey",
|
||||
script: "DATA_32 0x89ac24ea10bb751af4939623ccc5e550d96842b64e8fca0f63e94b4373fd555e CHECKSIG",
|
||||
class: PubKeyTy,
|
||||
},
|
||||
// p2pk ECDSA
|
||||
{
|
||||
name: "Pay Pubkey ECDSA",
|
||||
script: "DATA_33 0x89ac24ea10bb751af4939623ccc5e550d96842b64e8fca0f63e94b4373fd555eab CHECKSIGECDSA",
|
||||
class: PubKeyECDSATy,
|
||||
},
|
||||
{
|
||||
name: "Pay PubkeyHash",
|
||||
script: "DUP BLAKE2B DATA_32 0x660d4ef3a743e3e696ad990364e55543e3e696ad990364e555e555" +
|
||||
"c271ad504b EQUALVERIFY CHECKSIG",
|
||||
class: PubKeyHashTy,
|
||||
class: NonStandardTy,
|
||||
},
|
||||
// mutlisig
|
||||
{
|
||||
@@ -513,9 +537,14 @@ func TestStringifyClass(t *testing.T) {
|
||||
stringed: "nonstandard",
|
||||
},
|
||||
{
|
||||
name: "pubkeyhash",
|
||||
class: PubKeyHashTy,
|
||||
stringed: "pubkeyhash",
|
||||
name: "pubkey",
|
||||
class: PubKeyTy,
|
||||
stringed: "pubkey",
|
||||
},
|
||||
{
|
||||
name: "pubkeyecdsa",
|
||||
class: PubKeyECDSATy,
|
||||
stringed: "pubkeyecdsa",
|
||||
},
|
||||
{
|
||||
name: "scripthash",
|
||||
|
||||
@@ -38,8 +38,8 @@ func main() {
|
||||
// later...
|
||||
|
||||
// Create and print new payment address, specific to the active network.
|
||||
pubKeyHash := make([]byte, 20)
|
||||
addr, err := util.NewAddressPubKeyHash(pubKeyHash, dagParams)
|
||||
pubKey := make([]byte, 32)
|
||||
addr, err := util.NewAddressPubKey(pubKey, dagParams)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ variable (either directly, or hidden in a library call).
|
||||
// later...
|
||||
|
||||
// Create and print new payment address, specific to the active network.
|
||||
pubKeyHash := make([]byte, 20)
|
||||
addr, err := util.NewAddressPubKeyHash(pubKeyHash, dagParams)
|
||||
pubKey := make([]byte, 32)
|
||||
addr, err := util.NewAddressPubKey(pubKey, dagParams)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -36,8 +36,7 @@ func New(dagParams *dagconfig.Params, db infrastructuredatabase.Database, isArch
|
||||
}
|
||||
|
||||
miningManagerFactory := miningmanager.NewFactory()
|
||||
miningManager := miningManagerFactory.NewMiningManager(consensusInstance, dagParams.MaxMassAcceptedByBlock,
|
||||
dagParams.RelayNonStdTxs)
|
||||
miningManager := miningManagerFactory.NewMiningManager(consensusInstance, dagParams)
|
||||
|
||||
return &domain{
|
||||
consensus: consensusInstance,
|
||||
|
||||
@@ -2,21 +2,22 @@ package miningmanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/miningmanager/blocktemplatebuilder"
|
||||
mempoolpkg "github.com/kaspanet/kaspad/domain/miningmanager/mempool"
|
||||
)
|
||||
|
||||
// Factory instantiates new mining managers
|
||||
type Factory interface {
|
||||
NewMiningManager(consensus externalapi.Consensus, blockMaxMass uint64, acceptNonStd bool) MiningManager
|
||||
NewMiningManager(consensus externalapi.Consensus, params *dagconfig.Params) MiningManager
|
||||
}
|
||||
|
||||
type factory struct{}
|
||||
|
||||
// NewMiningManager instantiate a new mining manager
|
||||
func (f *factory) NewMiningManager(consensus externalapi.Consensus, blockMaxMass uint64, acceptNonStd bool) MiningManager {
|
||||
mempool := mempoolpkg.New(consensus, acceptNonStd)
|
||||
blockTemplateBuilder := blocktemplatebuilder.New(consensus, mempool, blockMaxMass)
|
||||
func (f *factory) NewMiningManager(consensus externalapi.Consensus, params *dagconfig.Params) MiningManager {
|
||||
mempool := mempoolpkg.New(consensus, params)
|
||||
blockTemplateBuilder := blocktemplatebuilder.New(consensus, mempool, params.MaxMassAcceptedByBlock)
|
||||
|
||||
return &miningManager{
|
||||
mempool: mempool,
|
||||
|
||||
@@ -7,6 +7,7 @@ package mempool
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -88,16 +89,17 @@ type mempool struct {
|
||||
// to on an unconditional timer.
|
||||
nextExpireScan mstime.Time
|
||||
|
||||
mtx sync.RWMutex
|
||||
policy policy
|
||||
mtx sync.RWMutex
|
||||
policy policy
|
||||
dagParams *dagconfig.Params
|
||||
}
|
||||
|
||||
// New returns a new memory pool for validating and storing standalone
|
||||
// transactions until they are mined into a block.
|
||||
func New(consensus consensusexternalapi.Consensus, acceptNonStd bool) miningmanagermodel.Mempool {
|
||||
func New(consensus consensusexternalapi.Consensus, dagParams *dagconfig.Params) miningmanagermodel.Mempool {
|
||||
policy := policy{
|
||||
MaxTxVersion: constants.MaxTransactionVersion,
|
||||
AcceptNonStd: acceptNonStd,
|
||||
AcceptNonStd: dagParams.RelayNonStdTxs,
|
||||
MaxOrphanTxs: 5,
|
||||
MaxOrphanTxSize: 100000,
|
||||
MinRelayTxFee: 1000, // 1 sompi per byte
|
||||
@@ -113,6 +115,7 @@ func New(consensus consensusexternalapi.Consensus, acceptNonStd bool) miningmana
|
||||
mempoolUTXOSet: newMempoolUTXOSet(),
|
||||
consensus: consensus,
|
||||
nextExpireScan: mstime.Now().Add(orphanExpireScanInterval),
|
||||
dagParams: dagParams,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,6 +726,11 @@ func (mp *mempool) maybeAcceptTransaction(tx *consensusexternalapi.DomainTransac
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if tx.Mass > mp.dagParams.MaxMassAcceptedByBlock {
|
||||
return nil, nil, newRuleError(errors.Errorf("The transaction mass is %d which is "+
|
||||
"higher than the maxmimum of %d", tx.Mass, mp.dagParams.MaxMassAcceptedByBlock))
|
||||
}
|
||||
|
||||
// Don't allow transactions with non-standard inputs if the network
|
||||
// parameters forbid their acceptance.
|
||||
if !mp.policy.AcceptNonStd {
|
||||
|
||||
@@ -127,43 +127,18 @@ func isDust(txOut *consensusexternalapi.DomainTransactionOutput, minRelayTxFee u
|
||||
// input script to redeem it. Since there is no input script
|
||||
// to redeem it yet, use the minimum size of a typical input script.
|
||||
//
|
||||
// Pay-to-pubkey-hash bytes breakdown:
|
||||
//
|
||||
// Output to hash (34 bytes):
|
||||
// 8 value, 1 script len, 25 script [1 OP_DUP, 1 OP_HASH_160,
|
||||
// 1 OP_DATA_20, 20 hash, 1 OP_EQUALVERIFY, 1 OP_CHECKSIG]
|
||||
//
|
||||
// Input with compressed pubkey (148 bytes):
|
||||
// 36 prev outpoint, 1 script len, 107 script [1 OP_DATA_72, 72 sig,
|
||||
// 1 OP_DATA_33, 33 compressed pubkey], 4 sequence
|
||||
//
|
||||
// Input with uncompressed pubkey (180 bytes):
|
||||
// 36 prev outpoint, 1 script len, 139 script [1 OP_DATA_72, 72 sig,
|
||||
// 1 OP_DATA_65, 65 compressed pubkey], 4 sequence
|
||||
//
|
||||
// Pay-to-pubkey bytes breakdown:
|
||||
//
|
||||
// Output to compressed pubkey (44 bytes):
|
||||
// 8 value, 1 script len, 35 script [1 OP_DATA_33,
|
||||
// 33 compressed pubkey, 1 OP_CHECKSIG]
|
||||
// Output to pubkey (43 bytes):
|
||||
// 8 value, 1 script len, 34 script [1 OP_DATA_32,
|
||||
// 32 pubkey, 1 OP_CHECKSIG]
|
||||
//
|
||||
// Output to uncompressed pubkey (76 bytes):
|
||||
// 8 value, 1 script len, 67 script [1 OP_DATA_65, 65 pubkey,
|
||||
// 1 OP_CHECKSIG]
|
||||
// Input (105 bytes):
|
||||
// 36 prev outpoint, 1 script len, 64 script [1 OP_DATA_64,
|
||||
// 64 sig], 4 sequence
|
||||
//
|
||||
// Input (114 bytes):
|
||||
// 36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72,
|
||||
// 72 sig], 4 sequence
|
||||
//
|
||||
// Theoretically this could examine the script type of the output script
|
||||
// and use a different size for the typical input script size for
|
||||
// pay-to-pubkey vs pay-to-pubkey-hash inputs per the above breakdowns,
|
||||
// but the only combination which is less than the value chosen is
|
||||
// a pay-to-pubkey script with a compressed pubkey, which is not very
|
||||
// common.
|
||||
//
|
||||
// The most common scripts are pay-to-pubkey-hash, and as per the above
|
||||
// breakdown, the minimum size of a p2pkh input script is 148 bytes. So
|
||||
// The most common scripts are pay-to-pubkey, and as per the above
|
||||
// breakdown, the minimum size of a p2pk input script is 148 bytes. So
|
||||
// that figure is used.
|
||||
totalSize := estimatedsize.TransactionOutputEstimatedSerializedSize(txOut) + 148
|
||||
|
||||
@@ -172,7 +147,7 @@ func isDust(txOut *consensusexternalapi.DomainTransactionOutput, minRelayTxFee u
|
||||
// minFreeTxRelayFee is in sompi/KB, so multiply by 1000 to
|
||||
// convert to bytes.
|
||||
//
|
||||
// Using the typical values for a pay-to-pubkey-hash transaction from
|
||||
// Using the typical values for a pay-to-pubkey transaction from
|
||||
// the breakdown above and the default minimum free transaction relay
|
||||
// fee of 1000, this equates to values less than 546 sompi being
|
||||
// considered dust.
|
||||
|
||||
@@ -179,9 +179,9 @@ func TestCheckTransactionStandard(t *testing.T) {
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
addrHash := [32]byte{0x01}
|
||||
addr, err := util.NewAddressPubKeyHash(addrHash[:], util.Bech32PrefixKaspaTest)
|
||||
addr, err := util.NewAddressPublicKey(addrHash[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAddressPubKeyHash: unexpected error: %v", err)
|
||||
t.Fatalf("NewAddressPublicKey: unexpected error: %v", err)
|
||||
}
|
||||
dummyScriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
@@ -200,7 +200,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
||||
code RejectCode
|
||||
}{
|
||||
{
|
||||
name: "Typical pay-to-pubkey-hash transaction",
|
||||
name: "Typical pay-to-pubkey transaction",
|
||||
tx: consensusexternalapi.DomainTransaction{Version: 0, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
||||
height: 300000,
|
||||
isStandard: true,
|
||||
|
||||
418
domain/miningmanager/miningmanager_test.go
Normal file
418
domain/miningmanager/miningmanager_test.go
Normal file
@@ -0,0 +1,418 @@
|
||||
package miningmanager_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
|
||||
"github.com/kaspanet/kaspad/domain/miningmanager"
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
|
||||
"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/consensus/utils/txscript"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
const blockMaxMass uint64 = 10000000
|
||||
|
||||
// TestValidateAndInsertTransaction verifies that valid transactions were successfully inserted into the mempool.
|
||||
func TestValidateAndInsertTransaction(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertTransaction")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up TestConsensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
miningFactory := miningmanager.NewFactory()
|
||||
miningManager := miningFactory.NewMiningManager(tc, params)
|
||||
transactionsToInsert := make([]*externalapi.DomainTransaction, 10)
|
||||
for i := range transactionsToInsert {
|
||||
transactionsToInsert[i] = createTransactionWithUTXOEntry(t, i)
|
||||
err = miningManager.ValidateAndInsertTransaction(transactionsToInsert[i], true)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertTransaction: %v", err)
|
||||
}
|
||||
}
|
||||
// The UTXOEntry was filled manually for those transactions, so the transactions won't be considered orphans.
|
||||
// Therefore, all the transactions expected to be contained in the mempool.
|
||||
transactionsFromMempool := miningManager.AllTransactions()
|
||||
if len(transactionsToInsert) != len(transactionsFromMempool) {
|
||||
t.Fatalf("Wrong number of transactions in mempool: expected: %d, got: %d", len(transactionsToInsert), len(transactionsFromMempool))
|
||||
}
|
||||
for _, transactionToInsert := range transactionsToInsert {
|
||||
if !contains(transactionToInsert, transactionsFromMempool) {
|
||||
t.Fatalf("Missing transaction %s in the mempool", consensushashing.TransactionID(transactionToInsert))
|
||||
}
|
||||
}
|
||||
|
||||
// The parent's transaction was inserted by consensus(AddBlock), and we want to verify that
|
||||
// the transaction is not considered an orphan and inserted into the mempool.
|
||||
transactionNotAnOrphan, err := createChildTxWhenParentTxWasAddedByConsensus(params, tc)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in createParentAndChildrenTransaction: %v", err)
|
||||
}
|
||||
err = miningManager.ValidateAndInsertTransaction(transactionNotAnOrphan, true)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertTransaction: %v", err)
|
||||
}
|
||||
transactionsFromMempool = miningManager.AllTransactions()
|
||||
if !contains(transactionNotAnOrphan, transactionsFromMempool) {
|
||||
t.Fatalf("Missing transaction %s in the mempool", consensushashing.TransactionID(transactionNotAnOrphan))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestInsertDoubleTransactionsToMempool verifies that an attempt to insert a transaction
|
||||
// more than once into the mempool will result in raising an appropriate error.
|
||||
func TestInsertDoubleTransactionsToMempool(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestInsertDoubleTransactionsToMempool")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up TestConsensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
miningFactory := miningmanager.NewFactory()
|
||||
miningManager := miningFactory.NewMiningManager(tc, params)
|
||||
transaction := createTransactionWithUTXOEntry(t, 0)
|
||||
err = miningManager.ValidateAndInsertTransaction(transaction, true)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertTransaction: %v", err)
|
||||
}
|
||||
err = miningManager.ValidateAndInsertTransaction(transaction, true)
|
||||
if err == nil || !strings.Contains(err.Error(), "already have transaction") {
|
||||
t.Fatalf("ValidateAndInsertTransaction: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestHandleNewBlockTransactions verifies that all the transactions in the block were successfully removed from the mempool.
|
||||
func TestHandleNewBlockTransactions(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestHandleNewBlockTransactions")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up TestConsensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
miningFactory := miningmanager.NewFactory()
|
||||
miningManager := miningFactory.NewMiningManager(tc, params)
|
||||
transactionsToInsert := make([]*externalapi.DomainTransaction, 10)
|
||||
for i := range transactionsToInsert {
|
||||
transaction := createTransactionWithUTXOEntry(t, i)
|
||||
transactionsToInsert[i] = transaction
|
||||
err = miningManager.ValidateAndInsertTransaction(transaction, true)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertTransaction: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
const partialLength = 3
|
||||
blockWithFirstPartOfTheTransactions := append([]*externalapi.DomainTransaction{nil}, transactionsToInsert[0:partialLength]...)
|
||||
blockWithRestOfTheTransactions := append([]*externalapi.DomainTransaction{nil}, transactionsToInsert[partialLength:]...)
|
||||
_, err = miningManager.HandleNewBlockTransactions(blockWithFirstPartOfTheTransactions)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleNewBlockTransactions: %v", err)
|
||||
}
|
||||
mempoolTransactions := miningManager.AllTransactions()
|
||||
for _, removedTransaction := range blockWithFirstPartOfTheTransactions {
|
||||
if contains(removedTransaction, mempoolTransactions) {
|
||||
t.Fatalf("This transaction shouldnt be in mempool: %s", consensushashing.TransactionID(removedTransaction))
|
||||
}
|
||||
}
|
||||
|
||||
// There are no chained/double-spends transactions, and hence it is expected that all the other
|
||||
// transactions, will still be included in the mempool.
|
||||
mempoolTransactions = miningManager.AllTransactions()
|
||||
for _, transaction := range blockWithRestOfTheTransactions[transactionhelper.CoinbaseTransactionIndex+1:] {
|
||||
if !contains(transaction, mempoolTransactions) {
|
||||
t.Fatalf("This transaction %s should be in mempool.", consensushashing.TransactionID(transaction))
|
||||
}
|
||||
}
|
||||
// Handle all the other transactions.
|
||||
_, err = miningManager.HandleNewBlockTransactions(blockWithRestOfTheTransactions)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleNewBlockTransactions: %v", err)
|
||||
}
|
||||
if len(miningManager.AllTransactions()) != 0 {
|
||||
blockIDs := domainBlocksToBlockIds(miningManager.AllTransactions())
|
||||
t.Fatalf("The mempool contains unexpected transactions: %s", blockIDs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func domainBlocksToBlockIds(blocks []*externalapi.DomainTransaction) []*externalapi.DomainTransactionID {
|
||||
blockIDs := make([]*externalapi.DomainTransactionID, len(blocks))
|
||||
for i := range blockIDs {
|
||||
blockIDs[i] = consensushashing.TransactionID(blocks[i])
|
||||
}
|
||||
return blockIDs
|
||||
}
|
||||
|
||||
// TestDoubleSpends verifies that any transactions which are now double spends as a result of the block's new transactions
|
||||
// will be removed from the mempool.
|
||||
func TestDoubleSpends(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestDoubleSpends")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed setting up TestConsensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
miningFactory := miningmanager.NewFactory()
|
||||
miningManager := miningFactory.NewMiningManager(tc, params)
|
||||
transactionInTheMempool := createTransactionWithUTXOEntry(t, 0)
|
||||
err = miningManager.ValidateAndInsertTransaction(transactionInTheMempool, true)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertTransaction: %v", err)
|
||||
}
|
||||
doubleSpendTransactionInTheBlock := createTransactionWithUTXOEntry(t, 0)
|
||||
doubleSpendTransactionInTheBlock.Inputs[0].PreviousOutpoint = transactionInTheMempool.Inputs[0].PreviousOutpoint
|
||||
blockTransactions := []*externalapi.DomainTransaction{nil, doubleSpendTransactionInTheBlock}
|
||||
_, err = miningManager.HandleNewBlockTransactions(blockTransactions)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleNewBlockTransactions: %v", err)
|
||||
}
|
||||
if contains(transactionInTheMempool, miningManager.AllTransactions()) {
|
||||
t.Fatalf("The transaction %s, shouldn't be in the mempool, since at least one "+
|
||||
"output was already spent.", consensushashing.TransactionID(transactionInTheMempool))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestOrphanTransactions verifies that a transaction could be a part of a new block template, only if it's not an orphan.
|
||||
func TestOrphanTransactions(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestOrphanTransactions")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up TestConsensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
miningFactory := miningmanager.NewFactory()
|
||||
miningManager := miningFactory.NewMiningManager(tc, params)
|
||||
// Before each parent transaction, We will add two blocks by consensus in order to fund the parent transactions.
|
||||
parentTransactions, childTransactions, err := createArraysOfParentAndChildrenTransactions(tc)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in createArraysOfParentAndChildrenTransactions: %v", err)
|
||||
}
|
||||
for _, orphanTransaction := range childTransactions {
|
||||
err = miningManager.ValidateAndInsertTransaction(orphanTransaction, true)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertTransaction: %v", err)
|
||||
}
|
||||
}
|
||||
transactionsMempool := miningManager.AllTransactions()
|
||||
for _, transaction := range transactionsMempool {
|
||||
if contains(transaction, childTransactions) {
|
||||
t.Fatalf("Error: an orphan transaction is exist in the mempool")
|
||||
}
|
||||
}
|
||||
|
||||
block, err := miningManager.GetBlockTemplate(&externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{Script: nil, Version: 0},
|
||||
ExtraData: nil})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed get a block template: %v", err)
|
||||
}
|
||||
for _, transactionFromBlock := range block.Transactions[1:] {
|
||||
for _, orphanTransaction := range childTransactions {
|
||||
if consensushashing.TransactionID(transactionFromBlock) == consensushashing.TransactionID(orphanTransaction) {
|
||||
t.Fatalf("Tranasaction with unknown parents is exist in a block that was built from GetTemplate option.")
|
||||
}
|
||||
}
|
||||
}
|
||||
tips, err := tc.Tips()
|
||||
if err != nil {
|
||||
t.Fatalf("Tips: %v.", err)
|
||||
}
|
||||
blockParentsTransactionsHash, _, err := tc.AddBlock(tips, nil, parentTransactions)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %v", err)
|
||||
}
|
||||
|
||||
_, _, err = tc.AddBlock([]*externalapi.DomainHash{blockParentsTransactionsHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %v", err)
|
||||
}
|
||||
|
||||
blockParentsTransactions, err := tc.GetBlock(blockParentsTransactionsHash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %v", err)
|
||||
}
|
||||
_, err = miningManager.HandleNewBlockTransactions(blockParentsTransactions.Transactions)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleNewBlockTransactions: %v", err)
|
||||
}
|
||||
transactionsMempool = miningManager.AllTransactions()
|
||||
if len(transactionsMempool) != len(childTransactions) {
|
||||
t.Fatalf("Expected %d transactions in the mempool but got %d", len(childTransactions), len(transactionsMempool))
|
||||
}
|
||||
|
||||
for _, transaction := range transactionsMempool {
|
||||
if !contains(transaction, childTransactions) {
|
||||
t.Fatalf("Error: the transaction %s, should be in the mempool since its not "+
|
||||
"oprhan anymore.", consensushashing.TransactionID(transaction))
|
||||
}
|
||||
}
|
||||
block, err = miningManager.GetBlockTemplate(&externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{Script: nil, Version: 0},
|
||||
ExtraData: nil})
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlockTemplate: %v", err)
|
||||
}
|
||||
for _, transactionFromBlock := range block.Transactions[1:] {
|
||||
isContained := false
|
||||
for _, childTransaction := range childTransactions {
|
||||
if *consensushashing.TransactionID(transactionFromBlock) == *consensushashing.TransactionID(childTransaction) {
|
||||
isContained = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isContained {
|
||||
t.Fatalf("Error: Unknown Transaction %s in a block.", consensushashing.TransactionID(transactionFromBlock))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func createTransactionWithUTXOEntry(t *testing.T, i int) *externalapi.DomainTransaction {
|
||||
prevOutTxID := externalapi.DomainTransactionID{}
|
||||
prevOutPoint := externalapi.DomainOutpoint{TransactionID: prevOutTxID, Index: uint32(i)}
|
||||
scriptPublicKey, redeemScript := testutils.OpTrueScript()
|
||||
signatureScript, err := txscript.PayToScriptHashSignatureScript(redeemScript, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToScriptHashSignatureScript: %v", err)
|
||||
}
|
||||
txInputWithMaxSequence := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: constants.SequenceLockTimeIsSeconds,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100000000, // 1 KAS
|
||||
scriptPublicKey,
|
||||
true,
|
||||
uint64(5)),
|
||||
}
|
||||
txOut := externalapi.DomainTransactionOutput{
|
||||
Value: 10000,
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
tx := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInputWithMaxSequence},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
Gas: 0,
|
||||
Fee: 289,
|
||||
Mass: 1,
|
||||
LockTime: 0}
|
||||
|
||||
return &tx
|
||||
}
|
||||
|
||||
func createArraysOfParentAndChildrenTransactions(tc testapi.TestConsensus) ([]*externalapi.DomainTransaction,
|
||||
[]*externalapi.DomainTransaction, error) {
|
||||
|
||||
const numOfTransactions = 5
|
||||
transactions := make([]*externalapi.DomainTransaction, numOfTransactions)
|
||||
parentTransactions := make([]*externalapi.DomainTransaction, len(transactions))
|
||||
var err error
|
||||
for i := range transactions {
|
||||
parentTransactions[i], transactions[i], err = createParentAndChildrenTransactions(tc)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return parentTransactions, transactions, nil
|
||||
}
|
||||
|
||||
func createParentAndChildrenTransactions(tc testapi.TestConsensus) (*externalapi.DomainTransaction,
|
||||
*externalapi.DomainTransaction, error) {
|
||||
|
||||
// We will add two blocks by consensus before the parent transactions, in order to fund the parent transactions.
|
||||
tips, err := tc.Tips()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_, _, err = tc.AddBlock(tips, nil, nil)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "AddBlock: %v", err)
|
||||
}
|
||||
|
||||
tips, err = tc.Tips()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fundingBlockHashForParent, _, err := tc.AddBlock(tips, nil, nil)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "AddBlock: ")
|
||||
}
|
||||
fundingBlockForParent, err := tc.GetBlock(fundingBlockHashForParent)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "GetBlock: ")
|
||||
}
|
||||
fundingTransactionForParent := fundingBlockForParent.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
txParent, err := testutils.CreateTransaction(fundingTransactionForParent, 1000)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
txChild, err := testutils.CreateTransaction(txParent, 1000)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return txParent, txChild, nil
|
||||
}
|
||||
|
||||
func createChildTxWhenParentTxWasAddedByConsensus(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) {
|
||||
|
||||
firstBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "AddBlock: %v", err)
|
||||
}
|
||||
ParentBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{firstBlockHash}, nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "AddBlock: ")
|
||||
}
|
||||
ParentBlock, err := tc.GetBlock(ParentBlockHash)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetBlock: ")
|
||||
}
|
||||
parentTransaction := ParentBlock.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
txChild, err := testutils.CreateTransaction(parentTransaction, 1000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txChild, nil
|
||||
}
|
||||
|
||||
func contains(transaction *externalapi.DomainTransaction, transactions []*externalapi.DomainTransaction) bool {
|
||||
for _, candidateTransaction := range transactions {
|
||||
if candidateTransaction.Equal(transaction) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user