mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-22 03:32:55 +00:00
Compare commits
42 Commits
v0.10.4
...
testsCheck
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1175a8afe7 | ||
|
|
7e6eaf9299 | ||
|
|
79c74c482b | ||
|
|
3b0394eefe | ||
|
|
b9339e8815 | ||
|
|
43e6467ff1 | ||
|
|
363494ef7a | ||
|
|
8aadb0bd44 | ||
|
|
092177d9a5 | ||
|
|
d1df97c4c5 | ||
|
|
4f52a6de51 | ||
|
|
55ee5f3115 | ||
|
|
3084e3a366 | ||
|
|
4f4a8934e7 | ||
|
|
16ba2bd312 | ||
|
|
6613faee2d | ||
|
|
edc459ae1b | ||
|
|
d7f2cf81c0 | ||
|
|
4658f9d05c | ||
|
|
010df3b0d3 | ||
|
|
346598e67f | ||
|
|
268906a7ce | ||
|
|
befc60b185 | ||
|
|
dd3e04e671 | ||
|
|
9c743db4d6 | ||
|
|
eb3dba5c88 | ||
|
|
e46e2580b1 | ||
|
|
414f58fb90 | ||
|
|
9df80957b1 | ||
|
|
268c9fa83c | ||
|
|
2e3592e351 | ||
|
|
19718ac102 | ||
|
|
28a8e96e65 | ||
|
|
4df283934a | ||
|
|
ab89efe3dc | ||
|
|
fa16c30cf3 | ||
|
|
c28366eb50 | ||
|
|
dc0bf56bf3 | ||
|
|
91de1807ad | ||
|
|
830684167c | ||
|
|
1f56a68a28 | ||
|
|
13a6b4cc51 |
@@ -63,6 +63,8 @@ Join our discord server using the following link: https://discord.gg/YNYnNN5Pf2
|
||||
The [integrated github issue tracker](https://github.com/kaspanet/kaspad/issues)
|
||||
is used for this project.
|
||||
|
||||
Issue priorities may be seen at https://github.com/orgs/kaspanet/projects/4
|
||||
|
||||
## Documentation
|
||||
|
||||
The [documentation](https://github.com/kaspanet/docs) is a work-in-progress
|
||||
|
||||
@@ -163,14 +163,10 @@ func outpointToDomainOutpoint(outpoint *Outpoint) *externalapi.DomainOutpoint {
|
||||
func RPCTransactionToDomainTransaction(rpcTransaction *RPCTransaction) (*externalapi.DomainTransaction, error) {
|
||||
inputs := make([]*externalapi.DomainTransactionInput, len(rpcTransaction.Inputs))
|
||||
for i, input := range rpcTransaction.Inputs {
|
||||
transactionID, err := transactionid.FromString(input.PreviousOutpoint.TransactionID)
|
||||
previousOutpoint, err := RPCOutpointToDomainOutpoint(input.PreviousOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
previousOutpoint := &externalapi.DomainOutpoint{
|
||||
TransactionID: *transactionID,
|
||||
Index: input.PreviousOutpoint.Index,
|
||||
}
|
||||
signatureScript, err := hex.DecodeString(input.SignatureScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -213,6 +209,36 @@ func RPCTransactionToDomainTransaction(rpcTransaction *RPCTransaction) (*externa
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RPCOutpointToDomainOutpoint converts RPCOutpoint to DomainOutpoint
|
||||
func RPCOutpointToDomainOutpoint(outpoint *RPCOutpoint) (*externalapi.DomainOutpoint, error) {
|
||||
transactionID, err := transactionid.FromString(outpoint.TransactionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &externalapi.DomainOutpoint{
|
||||
TransactionID: *transactionID,
|
||||
Index: outpoint.Index,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RPCUTXOEntryToUTXOEntry converts RPCUTXOEntry to UTXOEntry
|
||||
func RPCUTXOEntryToUTXOEntry(entry *RPCUTXOEntry) (externalapi.UTXOEntry, error) {
|
||||
script, err := hex.DecodeString(entry.ScriptPublicKey.Script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return utxo.NewUTXOEntry(
|
||||
entry.Amount,
|
||||
&externalapi.ScriptPublicKey{
|
||||
Script: script,
|
||||
Version: entry.ScriptPublicKey.Version,
|
||||
},
|
||||
entry.IsCoinbase,
|
||||
entry.BlockDAAScore,
|
||||
), nil
|
||||
}
|
||||
|
||||
// DomainTransactionToRPCTransaction converts DomainTransactions to RPCTransactions
|
||||
func DomainTransactionToRPCTransaction(transaction *externalapi.DomainTransaction) *RPCTransaction {
|
||||
inputs := make([]*RPCTransactionInput, len(transaction.Inputs))
|
||||
@@ -257,22 +283,27 @@ func OutpointAndUTXOEntryPairsToDomainOutpointAndUTXOEntryPairs(
|
||||
|
||||
domainOutpointAndUTXOEntryPairs := make([]*externalapi.OutpointAndUTXOEntryPair, len(outpointAndUTXOEntryPairs))
|
||||
for i, outpointAndUTXOEntryPair := range outpointAndUTXOEntryPairs {
|
||||
domainOutpointAndUTXOEntryPairs[i] = &externalapi.OutpointAndUTXOEntryPair{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: outpointAndUTXOEntryPair.Outpoint.TxID,
|
||||
Index: outpointAndUTXOEntryPair.Outpoint.Index,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
outpointAndUTXOEntryPair.UTXOEntry.Amount,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.ScriptPublicKey,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.IsCoinbase,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.BlockDAAScore,
|
||||
),
|
||||
}
|
||||
domainOutpointAndUTXOEntryPairs[i] = outpointAndUTXOEntryPairToDomainOutpointAndUTXOEntryPair(outpointAndUTXOEntryPair)
|
||||
}
|
||||
return domainOutpointAndUTXOEntryPairs
|
||||
}
|
||||
|
||||
func outpointAndUTXOEntryPairToDomainOutpointAndUTXOEntryPair(
|
||||
outpointAndUTXOEntryPair *OutpointAndUTXOEntryPair) *externalapi.OutpointAndUTXOEntryPair {
|
||||
return &externalapi.OutpointAndUTXOEntryPair{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: outpointAndUTXOEntryPair.Outpoint.TxID,
|
||||
Index: outpointAndUTXOEntryPair.Outpoint.Index,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
outpointAndUTXOEntryPair.UTXOEntry.Amount,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.ScriptPublicKey,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.IsCoinbase,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.BlockDAAScore,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// DomainOutpointAndUTXOEntryPairsToOutpointAndUTXOEntryPairs converts
|
||||
// domain OutpointAndUTXOEntryPairs to OutpointAndUTXOEntryPairs
|
||||
func DomainOutpointAndUTXOEntryPairsToOutpointAndUTXOEntryPairs(
|
||||
|
||||
@@ -137,6 +137,8 @@ const (
|
||||
CmdPruningPointUTXOSetOverrideNotificationMessage
|
||||
CmdStopNotifyingPruningPointUTXOSetOverrideRequestMessage
|
||||
CmdStopNotifyingPruningPointUTXOSetOverrideResponseMessage
|
||||
CmdEstimateNetworkHashesPerSecondRequestMessage
|
||||
CmdEstimateNetworkHashesPerSecondResponseMessage
|
||||
CmdNotifyVirtualDaaScoreChangedRequestMessage
|
||||
CmdNotifyVirtualDaaScoreChangedResponseMessage
|
||||
CmdVirtualDaaScoreChangedNotificationMessage
|
||||
@@ -251,6 +253,8 @@ var RPCMessageCommandToString = map[MessageCommand]string{
|
||||
CmdPruningPointUTXOSetOverrideNotificationMessage: "PruningPointUTXOSetOverrideNotification",
|
||||
CmdStopNotifyingPruningPointUTXOSetOverrideRequestMessage: "StopNotifyingPruningPointUTXOSetOverrideRequest",
|
||||
CmdStopNotifyingPruningPointUTXOSetOverrideResponseMessage: "StopNotifyingPruningPointUTXOSetOverrideResponse",
|
||||
CmdEstimateNetworkHashesPerSecondRequestMessage: "EstimateNetworkHashesPerSecondRequest",
|
||||
CmdEstimateNetworkHashesPerSecondResponseMessage: "EstimateNetworkHashesPerSecondResponse",
|
||||
CmdNotifyVirtualDaaScoreChangedRequestMessage: "NotifyVirtualDaaScoreChangedRequest",
|
||||
CmdNotifyVirtualDaaScoreChangedResponseMessage: "NotifyVirtualDaaScoreChangedResponse",
|
||||
CmdVirtualDaaScoreChangedNotificationMessage: "VirtualDaaScoreChangedNotification",
|
||||
|
||||
43
app/appmessage/rpc_estimate_network_hashes_per_second.go
Normal file
43
app/appmessage/rpc_estimate_network_hashes_per_second.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package appmessage
|
||||
|
||||
// EstimateNetworkHashesPerSecondRequestMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type EstimateNetworkHashesPerSecondRequestMessage struct {
|
||||
baseMessage
|
||||
StartHash string
|
||||
WindowSize uint32
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *EstimateNetworkHashesPerSecondRequestMessage) Command() MessageCommand {
|
||||
return CmdEstimateNetworkHashesPerSecondRequestMessage
|
||||
}
|
||||
|
||||
// NewEstimateNetworkHashesPerSecondRequestMessage returns a instance of the message
|
||||
func NewEstimateNetworkHashesPerSecondRequestMessage(startHash string, windowSize uint32) *EstimateNetworkHashesPerSecondRequestMessage {
|
||||
return &EstimateNetworkHashesPerSecondRequestMessage{
|
||||
StartHash: startHash,
|
||||
WindowSize: windowSize,
|
||||
}
|
||||
}
|
||||
|
||||
// EstimateNetworkHashesPerSecondResponseMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type EstimateNetworkHashesPerSecondResponseMessage struct {
|
||||
baseMessage
|
||||
NetworkHashesPerSecond uint64
|
||||
|
||||
Error *RPCError
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *EstimateNetworkHashesPerSecondResponseMessage) Command() MessageCommand {
|
||||
return CmdEstimateNetworkHashesPerSecondResponseMessage
|
||||
}
|
||||
|
||||
// NewEstimateNetworkHashesPerSecondResponseMessage returns a instance of the message
|
||||
func NewEstimateNetworkHashesPerSecondResponseMessage(networkHashesPerSecond uint64) *EstimateNetworkHashesPerSecondResponseMessage {
|
||||
return &EstimateNetworkHashesPerSecondResponseMessage{
|
||||
NetworkHashesPerSecond: networkHashesPerSecond,
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ package appmessage
|
||||
// its respective RPC message
|
||||
type GetBlockRequestMessage struct {
|
||||
baseMessage
|
||||
Hash string
|
||||
IncludeTransactionVerboseData bool
|
||||
Hash string
|
||||
IncludeTransactions bool
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -14,10 +14,10 @@ func (msg *GetBlockRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockRequestMessage returns a instance of the message
|
||||
func NewGetBlockRequestMessage(hash string, includeTransactionVerboseData bool) *GetBlockRequestMessage {
|
||||
func NewGetBlockRequestMessage(hash string, includeTransactions bool) *GetBlockRequestMessage {
|
||||
return &GetBlockRequestMessage{
|
||||
Hash: hash,
|
||||
IncludeTransactionVerboseData: includeTransactionVerboseData,
|
||||
Hash: hash,
|
||||
IncludeTransactions: includeTransactions,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ package appmessage
|
||||
// its respective RPC message
|
||||
type GetBlocksRequestMessage struct {
|
||||
baseMessage
|
||||
LowHash string
|
||||
IncludeBlocks bool
|
||||
IncludeTransactionVerboseData bool
|
||||
LowHash string
|
||||
IncludeBlocks bool
|
||||
IncludeTransactions bool
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -16,11 +16,11 @@ func (msg *GetBlocksRequestMessage) Command() MessageCommand {
|
||||
|
||||
// NewGetBlocksRequestMessage returns a instance of the message
|
||||
func NewGetBlocksRequestMessage(lowHash string, includeBlocks bool,
|
||||
includeTransactionVerboseData bool) *GetBlocksRequestMessage {
|
||||
includeTransactions bool) *GetBlocksRequestMessage {
|
||||
return &GetBlocksRequestMessage{
|
||||
LowHash: lowHash,
|
||||
IncludeBlocks: includeBlocks,
|
||||
IncludeTransactionVerboseData: includeTransactionVerboseData,
|
||||
LowHash: lowHash,
|
||||
IncludeBlocks: includeBlocks,
|
||||
IncludeTransactions: includeTransactions,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol"
|
||||
"github.com/kaspanet/kaspad/app/rpc"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
infrastructuredatabase "github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/dnsseed"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
@@ -46,8 +44,6 @@ func (a *ComponentManager) Start() {
|
||||
panics.Exit(log, fmt.Sprintf("Error starting the net adapter: %+v", err))
|
||||
}
|
||||
|
||||
a.maybeSeedFromDNS()
|
||||
|
||||
a.connectionManager.Start()
|
||||
}
|
||||
|
||||
@@ -157,23 +153,6 @@ func setupRPC(
|
||||
return rpcManager
|
||||
}
|
||||
|
||||
func (a *ComponentManager) maybeSeedFromDNS() {
|
||||
if !a.cfg.DisableDNSSeed {
|
||||
dnsseed.SeedFromDNS(a.cfg.NetParams(), a.cfg.DNSSeed, false, nil,
|
||||
a.cfg.Lookup, func(addresses []*appmessage.NetAddress) {
|
||||
// Kaspad uses a lookup of the dns seeder here. Since seeder returns
|
||||
// IPs of nodes and not its own IP, we can not know real IP of
|
||||
// source. So we'll take first returned address as source.
|
||||
a.addressManager.AddAddresses(addresses...)
|
||||
})
|
||||
|
||||
dnsseed.SeedFromGRPC(a.cfg.NetParams(), a.cfg.GRPCSeed, false, nil,
|
||||
func(addresses []*appmessage.NetAddress) {
|
||||
a.addressManager.AddAddresses(addresses...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// P2PNodeID returns the network ID associated with this ComponentManager
|
||||
func (a *ComponentManager) P2PNodeID() *id.ID {
|
||||
return a.netAdapter.ID()
|
||||
|
||||
@@ -49,9 +49,8 @@ func (flow *handleRequestHeadersFlow) start() error {
|
||||
|
||||
// GetHashesBetween is a relatively heavy operation so we limit it
|
||||
// in order to avoid locking the consensus for too long
|
||||
// maxBlocks MUST be >= MergeSetSizeLimit + 1
|
||||
const maxBlocks = 1 << 10
|
||||
blockHashes, _, err := flow.Domain().Consensus().GetHashesBetween(lowHash, highHash, maxBlocks)
|
||||
const maxBlueScoreDifference = 1 << 10
|
||||
blockHashes, _, err := flow.Domain().Consensus().GetHashesBetween(lowHash, highHash, maxBlueScoreDifference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -130,6 +130,10 @@ type fakeRelayInvsContext struct {
|
||||
rwLock sync.RWMutex
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) {
|
||||
panic(errors.Errorf("called unimplemented function from test '%s'", f.testName))
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetBlockEvenIfHeaderOnly(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
@@ -186,7 +190,7 @@ func (f *fakeRelayInvsContext) GetBlockAcceptanceData(blockHash *externalapi.Dom
|
||||
panic(errors.Errorf("called unimplemented function from test '%s'", f.testName))
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetHashesBetween(lowHash, highHash *externalapi.DomainHash, maxBlocks uint64) (hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) {
|
||||
func (f *fakeRelayInvsContext) GetHashesBetween(lowHash, highHash *externalapi.DomainHash, maxBlueScoreDifference uint64) (hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) {
|
||||
panic(errors.Errorf("called unimplemented function from test '%s'", f.testName))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package transactionrelay_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
type mocTransactionsRelayContext struct {
|
||||
netAdapter *netadapter.NetAdapter
|
||||
domain domain.Domain
|
||||
sharedRequestedTransactions *transactionrelay.SharedRequestedTransactions
|
||||
}
|
||||
|
||||
func (m *mocTransactionsRelayContext) NetAdapter() *netadapter.NetAdapter {
|
||||
return m.netAdapter
|
||||
}
|
||||
|
||||
func (m *mocTransactionsRelayContext) Domain() domain.Domain {
|
||||
return m.domain
|
||||
}
|
||||
|
||||
func (m *mocTransactionsRelayContext) SharedRequestedTransactions() *transactionrelay.SharedRequestedTransactions {
|
||||
return m.sharedRequestedTransactions
|
||||
}
|
||||
|
||||
func (m *mocTransactionsRelayContext) Broadcast(appmessage.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mocTransactionsRelayContext) OnTransactionAddedToMempool() {
|
||||
}
|
||||
|
||||
// TestHandleRelayedTransactionsNotFound tests the flow of HandleRelayedTransactions when the peer doesn't
|
||||
// have the requested transactions in the mempool.
|
||||
func TestHandleRelayedTransactionsNotFound(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
|
||||
var log = logger.RegisterSubSystem("PROT")
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestHandleRelayedTransactionsNotFound")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up test consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
sharedRequestedTransactions := transactionrelay.NewSharedRequestedTransactions()
|
||||
adapter, err := netadapter.NewNetAdapter(config.DefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a NetAdapter: %v", err)
|
||||
}
|
||||
domainInstance, err := domain.New(consensusConfig, tc.Database())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set up a domain instance: %v", err)
|
||||
}
|
||||
context := &mocTransactionsRelayContext{
|
||||
netAdapter: adapter,
|
||||
domain: domainInstance,
|
||||
sharedRequestedTransactions: sharedRequestedTransactions,
|
||||
}
|
||||
incomingRoute := router.NewRoute()
|
||||
defer incomingRoute.Close()
|
||||
peerIncomingRoute := router.NewRoute()
|
||||
defer peerIncomingRoute.Close()
|
||||
|
||||
txID1 := externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})
|
||||
txID2 := externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02})
|
||||
txIDs := []*externalapi.DomainTransactionID{txID1, txID2}
|
||||
invMessage := appmessage.NewMsgInvTransaction(txIDs)
|
||||
err = incomingRoute.Enqueue(invMessage)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error from incomingRoute.Enqueue: %v", err)
|
||||
}
|
||||
// The goroutine is representing the peer's actions.
|
||||
spawn("peerResponseToTheTransactionsRequest", func() {
|
||||
msg, err := peerIncomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
t.Fatalf("Dequeue: %v", err)
|
||||
}
|
||||
inv := msg.(*appmessage.MsgRequestTransactions)
|
||||
|
||||
if len(txIDs) != len(inv.IDs) {
|
||||
t.Fatalf("TestHandleRelayedTransactions: expected %d transactions ID, but got %d", len(txIDs), len(inv.IDs))
|
||||
}
|
||||
|
||||
for i, id := range inv.IDs {
|
||||
if txIDs[i].String() != id.String() {
|
||||
t.Fatalf("TestHandleRelayedTransactions: expected equal txID: expected %s, but got %s", txIDs[i].String(), id.String())
|
||||
}
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgTransactionNotFound(txIDs[i]))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error from incomingRoute.Enqueue: %v", err)
|
||||
}
|
||||
}
|
||||
// Insert an unexpected message type to stop the infinity loop.
|
||||
err = incomingRoute.Enqueue(&appmessage.MsgAddresses{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error from incomingRoute.Enqueue: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
err = transactionrelay.HandleRelayedTransactions(context, incomingRoute, peerIncomingRoute)
|
||||
// Since we inserted an unexpected message type to stop the infinity loop,
|
||||
// we expect the error will be infected from this specific message and also the
|
||||
// error will count as a protocol message.
|
||||
if protocolErr := (protocolerrors.ProtocolError{}); err == nil || !errors.As(err, &protocolErr) {
|
||||
t.Fatalf("Expected to protocol error")
|
||||
} else {
|
||||
if !protocolErr.ShouldBan {
|
||||
t.Fatalf("Exepcted shouldBan true, but got false.")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unexpected Addresses [code 3] message in the block relay flow while expecting an inv message") {
|
||||
t.Fatalf("Unexpected error: expected: an error due to existence of an Addresses message "+
|
||||
"in the block relay flow, but got: %v", protocolErr.Cause)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestOnClosedIncomingRoute verifies that an appropriate error message will be returned when
|
||||
// trying to dequeue a message from a closed route.
|
||||
func TestOnClosedIncomingRoute(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestOnClosedIncomingRoute")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up test consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
sharedRequestedTransactions := transactionrelay.NewSharedRequestedTransactions()
|
||||
adapter, err := netadapter.NewNetAdapter(config.DefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to creat a NetAdapter : %v", err)
|
||||
}
|
||||
domainInstance, err := domain.New(consensusConfig, tc.Database())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set up a domain instance: %v", err)
|
||||
}
|
||||
context := &mocTransactionsRelayContext{
|
||||
netAdapter: adapter,
|
||||
domain: domainInstance,
|
||||
sharedRequestedTransactions: sharedRequestedTransactions,
|
||||
}
|
||||
incomingRoute := router.NewRoute()
|
||||
outgoingRoute := router.NewRoute()
|
||||
defer outgoingRoute.Close()
|
||||
|
||||
txID := externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})
|
||||
txIDs := []*externalapi.DomainTransactionID{txID}
|
||||
|
||||
err = incomingRoute.Enqueue(&appmessage.MsgInvTransaction{TxIDs: txIDs})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error from incomingRoute.Enqueue: %v", err)
|
||||
}
|
||||
incomingRoute.Close()
|
||||
err = transactionrelay.HandleRelayedTransactions(context, incomingRoute, outgoingRoute)
|
||||
if err == nil || !errors.Is(err, router.ErrRouteClosed) {
|
||||
t.Fatalf("Unexpected error: expected: %v, got : %v", router.ErrRouteClosed, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package transactionrelay_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/pkg/errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestHandleRequestedTransactionsNotFound tests the flow of HandleRequestedTransactions
|
||||
// when the requested transactions don't found in the mempool.
|
||||
func TestHandleRequestedTransactionsNotFound(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
var log = logger.RegisterSubSystem("PROT")
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestHandleRequestedTransactionsNotFound")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up test Consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
sharedRequestedTransactions := transactionrelay.NewSharedRequestedTransactions()
|
||||
adapter, err := netadapter.NewNetAdapter(config.DefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a NetAdapter: %v", err)
|
||||
}
|
||||
domainInstance, err := domain.New(consensusConfig, tc.Database())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set up a domain Instance: %v", err)
|
||||
}
|
||||
context := &mocTransactionsRelayContext{
|
||||
netAdapter: adapter,
|
||||
domain: domainInstance,
|
||||
sharedRequestedTransactions: sharedRequestedTransactions,
|
||||
}
|
||||
incomingRoute := router.NewRoute()
|
||||
outgoingRoute := router.NewRoute()
|
||||
defer outgoingRoute.Close()
|
||||
|
||||
txID1 := externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})
|
||||
txID2 := externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02})
|
||||
txIDs := []*externalapi.DomainTransactionID{txID1, txID2}
|
||||
msg := appmessage.NewMsgRequestTransactions(txIDs)
|
||||
err = incomingRoute.Enqueue(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error from incomingRoute.Enqueue: %v", err)
|
||||
}
|
||||
// The goroutine is representing the peer's actions.
|
||||
spawn("peerResponseToTheTransactionsMessages", func() {
|
||||
for i, id := range txIDs {
|
||||
msg, err := outgoingRoute.Dequeue()
|
||||
if err != nil {
|
||||
t.Fatalf("Dequeue: %s", err)
|
||||
}
|
||||
outMsg := msg.(*appmessage.MsgTransactionNotFound)
|
||||
if txIDs[i].String() != outMsg.ID.String() {
|
||||
t.Fatalf("TestHandleRelayedTransactions: expected equal txID: expected %s, but got %s", txIDs[i].String(), id.String())
|
||||
}
|
||||
}
|
||||
// Closed the incomingRoute for stop the infinity loop.
|
||||
incomingRoute.Close()
|
||||
})
|
||||
|
||||
err = transactionrelay.HandleRequestedTransactions(context, incomingRoute, outgoingRoute)
|
||||
// Make sure the error is due to the closed route.
|
||||
if err == nil || !errors.Is(err, router.ErrRouteClosed) {
|
||||
t.Fatalf("Unexpected error: expected: %v, got : %v", router.ErrRouteClosed, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/rejects"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -11,10 +9,12 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/handshake"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/ping"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/rejects"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
@@ -106,7 +106,7 @@ func (m *Manager) handleError(err error, netConnection *netadapter.NetConnection
|
||||
log.Warnf("Banning %s (reason: %s)", netConnection, protocolErr.Cause)
|
||||
|
||||
err := m.context.ConnectionManager().Ban(netConnection)
|
||||
if !errors.Is(err, connmanager.ErrCannotBanPermanent) {
|
||||
if err != nil && !errors.Is(err, connmanager.ErrCannotBanPermanent) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ var handlers = map[appmessage.MessageCommand]handler{
|
||||
appmessage.CmdGetInfoRequestMessage: rpchandlers.HandleGetInfo,
|
||||
appmessage.CmdNotifyPruningPointUTXOSetOverrideRequestMessage: rpchandlers.HandleNotifyPruningPointUTXOSetOverrideRequest,
|
||||
appmessage.CmdStopNotifyingPruningPointUTXOSetOverrideRequestMessage: rpchandlers.HandleStopNotifyingPruningPointUTXOSetOverrideRequest,
|
||||
appmessage.CmdEstimateNetworkHashesPerSecondRequestMessage: rpchandlers.HandleEstimateNetworkHashesPerSecond,
|
||||
appmessage.CmdNotifyVirtualDaaScoreChangedRequestMessage: rpchandlers.HandleNotifyVirtualDaaScoreChanged,
|
||||
}
|
||||
|
||||
|
||||
39
app/rpc/rpchandlers/estimate_network_hashes_per_second.go
Normal file
39
app/rpc/rpchandlers/estimate_network_hashes_per_second.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package rpchandlers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// HandleEstimateNetworkHashesPerSecond handles the respectively named RPC command
|
||||
func HandleEstimateNetworkHashesPerSecond(
|
||||
context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
|
||||
|
||||
estimateNetworkHashesPerSecondRequest := request.(*appmessage.EstimateNetworkHashesPerSecondRequestMessage)
|
||||
|
||||
windowSize := int(estimateNetworkHashesPerSecondRequest.WindowSize)
|
||||
startHash := model.VirtualBlockHash
|
||||
if estimateNetworkHashesPerSecondRequest.StartHash != "" {
|
||||
var err error
|
||||
startHash, err = externalapi.NewDomainHashFromString(estimateNetworkHashesPerSecondRequest.StartHash)
|
||||
if err != nil {
|
||||
response := &appmessage.EstimateNetworkHashesPerSecondResponseMessage{}
|
||||
response.Error = appmessage.RPCErrorf("StartHash '%s' is not a valid block hash",
|
||||
estimateNetworkHashesPerSecondRequest.StartHash)
|
||||
return response, nil
|
||||
}
|
||||
}
|
||||
|
||||
networkHashesPerSecond, err := context.Domain.Consensus().EstimateNetworkHashesPerSecond(startHash, windowSize)
|
||||
if err != nil {
|
||||
response := &appmessage.EstimateNetworkHashesPerSecondResponseMessage{}
|
||||
response.Error = appmessage.RPCErrorf("could not resolve network hashes per "+
|
||||
"second for startHash %s and window size %d: %s", startHash, windowSize, err)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
return appmessage.NewEstimateNetworkHashesPerSecondResponseMessage(networkHashesPerSecond), nil
|
||||
}
|
||||
@@ -29,13 +29,13 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
|
||||
|
||||
response := appmessage.NewGetBlockResponseMessage()
|
||||
|
||||
if getBlockRequest.IncludeTransactionVerboseData {
|
||||
if getBlockRequest.IncludeTransactions {
|
||||
response.Block = appmessage.DomainBlockToRPCBlock(block)
|
||||
} else {
|
||||
response.Block = appmessage.DomainBlockToRPCBlock(&externalapi.DomainBlock{Header: block.Header})
|
||||
}
|
||||
|
||||
err = context.PopulateBlockWithVerboseData(response.Block, block.Header, block, getBlockRequest.IncludeTransactionVerboseData)
|
||||
err = context.PopulateBlockWithVerboseData(response.Block, block.Header, block, getBlockRequest.IncludeTransactions)
|
||||
if err != nil {
|
||||
if errors.Is(err, rpccontext.ErrBuildBlockVerboseDataInvalidBlock) {
|
||||
errorMessage := &appmessage.GetBlockResponseMessage{}
|
||||
|
||||
@@ -8,15 +8,21 @@ import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxBlocksInGetBlocksResponse is the max amount of blocks that are
|
||||
// allowed in a GetBlocksResult.
|
||||
maxBlocksInGetBlocksResponse = 1000
|
||||
)
|
||||
|
||||
// HandleGetBlocks handles the respectively named RPC command
|
||||
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 IncludeBlocks
|
||||
if !getBlocksRequest.IncludeBlocks && getBlocksRequest.IncludeTransactionVerboseData {
|
||||
// Validate that user didn't set IncludeTransactions without setting IncludeBlocks
|
||||
if !getBlocksRequest.IncludeBlocks && getBlocksRequest.IncludeTransactions {
|
||||
return &appmessage.GetBlocksResponseMessage{
|
||||
Error: appmessage.RPCErrorf(
|
||||
"If includeTransactionVerboseData is set, then includeBlockVerboseData must be set as well"),
|
||||
"If includeTransactions is set, then includeBlockVerboseData must be set as well"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -49,11 +55,7 @@ func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appm
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We use +1 because lowHash is also returned
|
||||
// maxBlocks MUST be >= MergeSetSizeLimit + 1
|
||||
maxBlocks := context.Config.NetParams().MergeSetSizeLimit + 1
|
||||
blockHashes, highHash, err := context.Domain.Consensus().GetHashesBetween(lowHash, virtualSelectedParent, maxBlocks)
|
||||
blockHashes, highHash, err := context.Domain.Consensus().GetHashesBetween(lowHash, virtualSelectedParent, maxBlocksInGetBlocksResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -72,6 +74,12 @@ func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appm
|
||||
blockHashes = append(blockHashes, virtualSelectedParentAnticone...)
|
||||
}
|
||||
|
||||
// Both GetHashesBetween and Anticone might return more then the allowed number of blocks, so
|
||||
// trim any extra blocks.
|
||||
if len(blockHashes) > maxBlocksInGetBlocksResponse {
|
||||
blockHashes = blockHashes[:maxBlocksInGetBlocksResponse]
|
||||
}
|
||||
|
||||
// Prepare the response
|
||||
response := appmessage.NewGetBlocksResponseMessage()
|
||||
response.BlockHashes = hashes.ToStrings(blockHashes)
|
||||
@@ -83,12 +91,12 @@ func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appm
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if getBlocksRequest.IncludeTransactionVerboseData {
|
||||
if getBlocksRequest.IncludeTransactions {
|
||||
rpcBlocks[i] = appmessage.DomainBlockToRPCBlock(block)
|
||||
} else {
|
||||
rpcBlocks[i] = appmessage.DomainBlockToRPCBlock(&externalapi.DomainBlock{Header: block.Header})
|
||||
}
|
||||
err = context.PopulateBlockWithVerboseData(rpcBlocks[i], block.Header, nil, getBlocksRequest.IncludeTransactionVerboseData)
|
||||
err = context.PopulateBlockWithVerboseData(rpcBlocks[i], block.Header, nil, getBlocksRequest.IncludeTransactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
Kaspad v0.10.4 - 2021-07-05
|
||||
===========================
|
||||
* Make use of maxBlocks instead of maxBlueScoreDifference in antiPastHashesBetween (#1774)
|
||||
|
||||
Kaspad v0.10.3 - 2021-06-06
|
||||
===========================
|
||||
Non-breaking changes:
|
||||
* Implement NotifyVirtualDaaScoreChanged (#1737)
|
||||
|
||||
Kaspad v0.10.2 - 2021-05-18
|
||||
===========================
|
||||
Non-breaking changes:
|
||||
|
||||
@@ -48,6 +48,10 @@ func setField(commandValue reflect.Value, parameterValue reflect.Value, paramete
|
||||
}
|
||||
|
||||
func stringToValue(parameterDesc *parameterDescription, valueStr string) (reflect.Value, error) {
|
||||
if valueStr == "-" {
|
||||
return reflect.Zero(parameterDesc.typeof), nil
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
var err error
|
||||
switch parameterDesc.typeof.Kind() {
|
||||
|
||||
@@ -24,6 +24,7 @@ var commandTypes = []reflect.Type{
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetVirtualSelectedParentBlueScoreRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetVirtualSelectedParentChainFromBlockRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_ResolveFinalityConflictRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_EstimateNetworkHashesPerSecondRequest{}),
|
||||
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetBlockTemplateRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_SubmitBlockRequest{}),
|
||||
|
||||
@@ -27,7 +27,8 @@ func parseConfig() (*configFlags, error) {
|
||||
}
|
||||
parser := flags.NewParser(cfg, flags.HelpFlag)
|
||||
parser.Usage = "kaspactl [OPTIONS] [COMMAND] [COMMAND PARAMETERS].\n\nCommand can be supplied only if --json is not used." +
|
||||
"\n\nUse `kaspactl --list-commands` to get a list of all commands and their parameters"
|
||||
"\n\nUse `kaspactl --list-commands` to get a list of all commands and their parameters." +
|
||||
"\nFor optional parameters- use '-' without quotes to not pass the parameter.\n"
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,50 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
func balance(conf *balanceConfig) error {
|
||||
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
|
||||
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tearDown()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
response, err := daemonClient.GetBalance(ctx, &pb.GetBalanceRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{addr.String()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockDAGInfo, err := client.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var availableBalance, pendingBalance uint64
|
||||
for _, entry := range getUTXOsByAddressesResponse.Entries {
|
||||
if isUTXOSpendable(entry, blockDAGInfo.VirtualDAAScore, conf.ActiveNetParams.BlockCoinbaseMaturity) {
|
||||
availableBalance += entry.UTXOEntry.Amount
|
||||
} else {
|
||||
pendingBalance += entry.UTXOEntry.Amount
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Balance:\t\tKAS %f\n", float64(availableBalance)/util.SompiPerKaspa)
|
||||
if pendingBalance > 0 {
|
||||
fmt.Printf("Pending balance:\tKAS %f\n", float64(pendingBalance)/util.SompiPerKaspa)
|
||||
fmt.Printf("Balance:\t\tKAS %f\n", float64(response.Available)/util.SompiPerKaspa)
|
||||
if response.Pending > 0 {
|
||||
fmt.Printf("Pending balance:\tKAS %f\n", float64(response.Pending)/util.SompiPerKaspa)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
)
|
||||
|
||||
func broadcast(conf *broadcastConfig) error {
|
||||
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
|
||||
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tearDown()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
|
||||
transaction, err := hex.DecodeString(conf.Transaction)
|
||||
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)
|
||||
response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Transaction was sent successfully")
|
||||
fmt.Printf("Transaction ID: \t%s\n", transactionID)
|
||||
fmt.Printf("Transaction ID: \t%s\n", response.TxID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,30 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func isUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
|
||||
if !entry.UTXOEntry.IsCoinbase {
|
||||
return true
|
||||
}
|
||||
blockBlueScore := entry.UTXOEntry.BlockDAAScore
|
||||
return blockBlueScore+coinbaseMaturity < virtualDAAScore
|
||||
}
|
||||
const daemonTimeout = 2 * time.Minute
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ const (
|
||||
broadcastSubCmd = "broadcast"
|
||||
showAddressSubCmd = "show-address"
|
||||
dumpUnencryptedDataSubCmd = "dump-unencrypted-data"
|
||||
startDaemonSubCmd = "start-daemon"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultListen = "localhost:8082"
|
||||
defaultRPCServer = "localhost"
|
||||
)
|
||||
|
||||
type configFlags struct {
|
||||
@@ -25,6 +31,8 @@ type configFlags struct {
|
||||
|
||||
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))"`
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
Yes bool `long:"yes" short:"y" description:"Assume \"yes\" to all questions"`
|
||||
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"`
|
||||
@@ -34,46 +42,56 @@ type createConfig struct {
|
||||
}
|
||||
|
||||
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"`
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
|
||||
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"`
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
|
||||
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"`
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
|
||||
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))"`
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
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"`
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
|
||||
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))"`
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
type startDaemonConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
|
||||
Listen string `short:"l" long:"listen" description:"Address to listen on (default: 0.0.0.0:8082)"`
|
||||
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))"`
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
Yes bool `long:"yes" short:"y" description:"Assume \"yes\" to all questions"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
@@ -85,15 +103,15 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
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{}
|
||||
balanceConf := &balanceConfig{DaemonAddress: defaultListen}
|
||||
parser.AddCommand(balanceSubCmd, "Shows the balance of a public address",
|
||||
"Shows the balance for a public address in Kaspa", balanceConf)
|
||||
|
||||
sendConf := &sendConfig{}
|
||||
sendConf := &sendConfig{DaemonAddress: defaultListen}
|
||||
parser.AddCommand(sendSubCmd, "Sends a Kaspa transaction to a public address",
|
||||
"Sends a Kaspa transaction to a public address", sendConf)
|
||||
|
||||
createUnsignedTransactionConf := &createUnsignedTransactionConfig{}
|
||||
createUnsignedTransactionConf := &createUnsignedTransactionConfig{DaemonAddress: defaultListen}
|
||||
parser.AddCommand(createUnsignedTransactionSubCmd, "Create an unsigned Kaspa transaction",
|
||||
"Create an unsigned Kaspa transaction", createUnsignedTransactionConf)
|
||||
|
||||
@@ -101,11 +119,11 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
parser.AddCommand(signSubCmd, "Sign the given partially signed transaction",
|
||||
"Sign the given partially signed transaction", signConf)
|
||||
|
||||
broadcastConf := &broadcastConfig{}
|
||||
broadcastConf := &broadcastConfig{DaemonAddress: defaultListen}
|
||||
parser.AddCommand(broadcastSubCmd, "Broadcast the given transaction",
|
||||
"Broadcast the given transaction", broadcastConf)
|
||||
|
||||
showAddressConf := &showAddressConfig{}
|
||||
showAddressConf := &showAddressConfig{DaemonAddress: defaultListen}
|
||||
parser.AddCommand(showAddressSubCmd, "Shows the public address of the current wallet",
|
||||
"Shows the public address of the current wallet", showAddressConf)
|
||||
|
||||
@@ -114,6 +132,12 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
"Prints the unencrypted wallet data including its private keys. Anyone that sees it can access "+
|
||||
"the funds. Use only on safe environment.", dumpUnencryptedDataConf)
|
||||
|
||||
startDaemonConf := &startDaemonConfig{
|
||||
RPCServer: defaultRPCServer,
|
||||
Listen: defaultListen,
|
||||
}
|
||||
parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf)
|
||||
|
||||
_, err := parser.Parse()
|
||||
|
||||
if err != nil {
|
||||
@@ -183,6 +207,13 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
printErrorAndExit(err)
|
||||
}
|
||||
config = dumpUnencryptedDataConf
|
||||
case startDaemonSubCmd:
|
||||
combineNetworkFlags(&startDaemonConf.NetworkFlags, &cfg.NetworkFlags)
|
||||
err := startDaemonConf.ResolveNetwork(parser)
|
||||
if err != nil {
|
||||
printErrorAndExit(err)
|
||||
}
|
||||
config = startDaemonConf
|
||||
}
|
||||
|
||||
return parser.Command.Active.Name, config
|
||||
|
||||
@@ -2,70 +2,77 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func create(conf *createConfig) error {
|
||||
var encryptedPrivateKeys []*keys.EncryptedPrivateKey
|
||||
var publicKeys [][]byte
|
||||
var encryptedMnemonics []*keys.EncryptedMnemonic
|
||||
var signerExtendedPublicKeys []string
|
||||
var err error
|
||||
isMultisig := conf.NumPublicKeys > 1
|
||||
if !conf.Import {
|
||||
encryptedPrivateKeys, publicKeys, err = keys.CreateKeyPairs(conf.NumPrivateKeys, conf.ECDSA)
|
||||
encryptedMnemonics, signerExtendedPublicKeys, err = keys.CreateMnemonics(conf.NetParams(), conf.NumPrivateKeys, conf.Password, isMultisig)
|
||||
} else {
|
||||
encryptedPrivateKeys, publicKeys, err = keys.ImportKeyPairs(conf.NumPrivateKeys)
|
||||
encryptedMnemonics, signerExtendedPublicKeys, err = keys.ImportMnemonics(conf.NetParams(), conf.NumPrivateKeys, conf.Password, isMultisig)
|
||||
}
|
||||
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)
|
||||
for i, extendedPublicKey := range signerExtendedPublicKeys {
|
||||
fmt.Printf("Extended public key of mnemonic #%d:\n%s\n\n", i+1, extendedPublicKey)
|
||||
}
|
||||
|
||||
extendedPublicKeys := make([]string, conf.NumPrivateKeys, conf.NumPublicKeys)
|
||||
copy(extendedPublicKeys, signerExtendedPublicKeys)
|
||||
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()
|
||||
extendedPublicKey, err := utils.ReadLine(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = bip32.DeserializeExtendedKey(string(extendedPublicKey))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s is invalid extended public key", string(extendedPublicKey))
|
||||
}
|
||||
|
||||
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)
|
||||
extendedPublicKeys = append(extendedPublicKeys, string(extendedPublicKey))
|
||||
}
|
||||
|
||||
err = keys.WriteKeysFile(
|
||||
conf.NetParams(), conf.KeysFile, encryptedPrivateKeys, publicKeys, conf.MinimumSignatures, conf.ECDSA)
|
||||
cosignerIndex, err := libkaspawallet.MinimumCosignerIndex(signerExtendedPublicKeys, extendedPublicKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
|
||||
file := keys.File{
|
||||
EncryptedMnemonics: encryptedMnemonics,
|
||||
ExtendedPublicKeys: extendedPublicKeys,
|
||||
MinimumSignatures: conf.MinimumSignatures,
|
||||
CosignerIndex: cosignerIndex,
|
||||
ECDSA: conf.ECDSA,
|
||||
}
|
||||
|
||||
err = file.SetPath(conf.NetParams(), conf.KeysFile, conf.Yes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
err = file.Save()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("The wallet address is:\n%s\n", addr)
|
||||
fmt.Printf("Wrote the keys into %s\n", file.Path())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,62 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
|
||||
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tearDown()
|
||||
|
||||
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
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
|
||||
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)
|
||||
response, err := daemonClient.CreateUnsignedTransaction(ctx, &pb.CreateUnsignedTransactionRequest{
|
||||
Address: conf.ToAddress,
|
||||
Amount: sendAmountSompi,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Created unsigned transaction")
|
||||
fmt.Println(hex.EncodeToString(psTx))
|
||||
fmt.Println(hex.EncodeToString(response.UnsignedTransaction))
|
||||
return nil
|
||||
}
|
||||
|
||||
18
cmd/kaspawallet/daemon/client/client.go
Normal file
18
cmd/kaspawallet/daemon/client/client.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Connect connects to the kaspawalletd server, and returns the client instance
|
||||
func Connect(address string) (pb.KaspawalletdClient, func(), error) {
|
||||
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pb.NewKaspawalletdClient(conn), func() {
|
||||
conn.Close()
|
||||
}, nil
|
||||
}
|
||||
3
cmd/kaspawallet/daemon/pb/generate.go
Normal file
3
cmd/kaspawallet/daemon/pb/generate.go
Normal file
@@ -0,0 +1,3 @@
|
||||
//go:generate protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative kaspawalletd.proto
|
||||
|
||||
package pb
|
||||
730
cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go
Normal file
730
cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go
Normal file
@@ -0,0 +1,730 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.12.3
|
||||
// source: kaspawalletd.proto
|
||||
|
||||
package pb
|
||||
|
||||
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 GetBalanceRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *GetBalanceRequest) Reset() {
|
||||
*x = GetBalanceRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetBalanceRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetBalanceRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetBalanceRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 GetBalanceRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetBalanceRequest) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type GetBalanceResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Available uint64 `protobuf:"varint,1,opt,name=available,proto3" json:"available,omitempty"`
|
||||
Pending uint64 `protobuf:"varint,2,opt,name=pending,proto3" json:"pending,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetBalanceResponse) Reset() {
|
||||
*x = GetBalanceResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetBalanceResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetBalanceResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetBalanceResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 GetBalanceResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetBalanceResponse) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *GetBalanceResponse) GetAvailable() uint64 {
|
||||
if x != nil {
|
||||
return x.Available
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *GetBalanceResponse) GetPending() uint64 {
|
||||
if x != nil {
|
||||
return x.Pending
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type CreateUnsignedTransactionRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateUnsignedTransactionRequest) Reset() {
|
||||
*x = CreateUnsignedTransactionRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CreateUnsignedTransactionRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CreateUnsignedTransactionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CreateUnsignedTransactionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 CreateUnsignedTransactionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CreateUnsignedTransactionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *CreateUnsignedTransactionRequest) GetAddress() string {
|
||||
if x != nil {
|
||||
return x.Address
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateUnsignedTransactionRequest) GetAmount() uint64 {
|
||||
if x != nil {
|
||||
return x.Amount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type CreateUnsignedTransactionResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UnsignedTransaction []byte `protobuf:"bytes,1,opt,name=unsignedTransaction,proto3" json:"unsignedTransaction,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateUnsignedTransactionResponse) Reset() {
|
||||
*x = CreateUnsignedTransactionResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CreateUnsignedTransactionResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CreateUnsignedTransactionResponse) ProtoMessage() {}
|
||||
|
||||
func (x *CreateUnsignedTransactionResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 CreateUnsignedTransactionResponse.ProtoReflect.Descriptor instead.
|
||||
func (*CreateUnsignedTransactionResponse) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *CreateUnsignedTransactionResponse) GetUnsignedTransaction() []byte {
|
||||
if x != nil {
|
||||
return x.UnsignedTransaction
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetReceiveAddressRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *GetReceiveAddressRequest) Reset() {
|
||||
*x = GetReceiveAddressRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetReceiveAddressRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetReceiveAddressRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetReceiveAddressRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 GetReceiveAddressRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetReceiveAddressRequest) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
type GetReceiveAddressResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetReceiveAddressResponse) Reset() {
|
||||
*x = GetReceiveAddressResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetReceiveAddressResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetReceiveAddressResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetReceiveAddressResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 GetReceiveAddressResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetReceiveAddressResponse) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *GetReceiveAddressResponse) GetAddress() string {
|
||||
if x != nil {
|
||||
return x.Address
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type BroadcastRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Transaction []byte `protobuf:"bytes,1,opt,name=transaction,proto3" json:"transaction,omitempty"`
|
||||
}
|
||||
|
||||
func (x *BroadcastRequest) Reset() {
|
||||
*x = BroadcastRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *BroadcastRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*BroadcastRequest) ProtoMessage() {}
|
||||
|
||||
func (x *BroadcastRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 BroadcastRequest.ProtoReflect.Descriptor instead.
|
||||
func (*BroadcastRequest) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *BroadcastRequest) GetTransaction() []byte {
|
||||
if x != nil {
|
||||
return x.Transaction
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BroadcastResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
TxID string `protobuf:"bytes,1,opt,name=txID,proto3" json:"txID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *BroadcastResponse) Reset() {
|
||||
*x = BroadcastResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *BroadcastResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*BroadcastResponse) ProtoMessage() {}
|
||||
|
||||
func (x *BroadcastResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 BroadcastResponse.ProtoReflect.Descriptor instead.
|
||||
func (*BroadcastResponse) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *BroadcastResponse) GetTxID() string {
|
||||
if x != nil {
|
||||
return x.TxID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ShutdownRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *ShutdownRequest) Reset() {
|
||||
*x = ShutdownRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ShutdownRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ShutdownRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ShutdownRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 ShutdownRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ShutdownRequest) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
type ShutdownResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *ShutdownResponse) Reset() {
|
||||
*x = ShutdownResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ShutdownResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ShutdownResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ShutdownResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_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 ShutdownResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ShutdownResponse) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
var File_kaspawalletd_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_kaspawalletd_proto_rawDesc = []byte{
|
||||
0x0a, 0x12, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e,
|
||||
0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4c, 0x0a, 0x12, 0x47, 0x65, 0x74,
|
||||
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x04, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07,
|
||||
0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x54, 0x0a, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64,
|
||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x55, 0x0a,
|
||||
0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54,
|
||||
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72,
|
||||
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x13, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69,
|
||||
0x76, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x22, 0x35, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64,
|
||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x34, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x61, 0x64,
|
||||
0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x74,
|
||||
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x27, 0x0a,
|
||||
0x11, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x04, 0x74, 0x78, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f,
|
||||
0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75,
|
||||
0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe4, 0x02,
|
||||
0x0a, 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x37,
|
||||
0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x47,
|
||||
0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x13, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73,
|
||||
0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a,
|
||||
0x11, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||
0x73, 0x73, 0x12, 0x19, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e,
|
||||
0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x08, 0x53,
|
||||
0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x10, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f,
|
||||
0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x53, 0x68, 0x75, 0x74,
|
||||
0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x34,
|
||||
0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x42, 0x72,
|
||||
0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,
|
||||
0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70,
|
||||
0x61, 0x64, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c,
|
||||
0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_kaspawalletd_proto_rawDescOnce sync.Once
|
||||
file_kaspawalletd_proto_rawDescData = file_kaspawalletd_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_kaspawalletd_proto_rawDescGZIP() []byte {
|
||||
file_kaspawalletd_proto_rawDescOnce.Do(func() {
|
||||
file_kaspawalletd_proto_rawDescData = protoimpl.X.CompressGZIP(file_kaspawalletd_proto_rawDescData)
|
||||
})
|
||||
return file_kaspawalletd_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||
var file_kaspawalletd_proto_goTypes = []interface{}{
|
||||
(*GetBalanceRequest)(nil), // 0: GetBalanceRequest
|
||||
(*GetBalanceResponse)(nil), // 1: GetBalanceResponse
|
||||
(*CreateUnsignedTransactionRequest)(nil), // 2: CreateUnsignedTransactionRequest
|
||||
(*CreateUnsignedTransactionResponse)(nil), // 3: CreateUnsignedTransactionResponse
|
||||
(*GetReceiveAddressRequest)(nil), // 4: GetReceiveAddressRequest
|
||||
(*GetReceiveAddressResponse)(nil), // 5: GetReceiveAddressResponse
|
||||
(*BroadcastRequest)(nil), // 6: BroadcastRequest
|
||||
(*BroadcastResponse)(nil), // 7: BroadcastResponse
|
||||
(*ShutdownRequest)(nil), // 8: ShutdownRequest
|
||||
(*ShutdownResponse)(nil), // 9: ShutdownResponse
|
||||
}
|
||||
var file_kaspawalletd_proto_depIdxs = []int32{
|
||||
0, // 0: kaspawalletd.GetBalance:input_type -> GetBalanceRequest
|
||||
2, // 1: kaspawalletd.CreateUnsignedTransaction:input_type -> CreateUnsignedTransactionRequest
|
||||
4, // 2: kaspawalletd.GetReceiveAddress:input_type -> GetReceiveAddressRequest
|
||||
8, // 3: kaspawalletd.Shutdown:input_type -> ShutdownRequest
|
||||
6, // 4: kaspawalletd.Broadcast:input_type -> BroadcastRequest
|
||||
1, // 5: kaspawalletd.GetBalance:output_type -> GetBalanceResponse
|
||||
3, // 6: kaspawalletd.CreateUnsignedTransaction:output_type -> CreateUnsignedTransactionResponse
|
||||
5, // 7: kaspawalletd.GetReceiveAddress:output_type -> GetReceiveAddressResponse
|
||||
9, // 8: kaspawalletd.Shutdown:output_type -> ShutdownResponse
|
||||
7, // 9: kaspawalletd.Broadcast:output_type -> BroadcastResponse
|
||||
5, // [5:10] is the sub-list for method output_type
|
||||
0, // [0:5] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_kaspawalletd_proto_init() }
|
||||
func file_kaspawalletd_proto_init() {
|
||||
if File_kaspawalletd_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_kaspawalletd_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetBalanceRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetBalanceResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CreateUnsignedTransactionRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CreateUnsignedTransactionResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetReceiveAddressRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetReceiveAddressResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*BroadcastRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*BroadcastResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ShutdownRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ShutdownResponse); 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_kaspawalletd_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 10,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_kaspawalletd_proto_goTypes,
|
||||
DependencyIndexes: file_kaspawalletd_proto_depIdxs,
|
||||
MessageInfos: file_kaspawalletd_proto_msgTypes,
|
||||
}.Build()
|
||||
File_kaspawalletd_proto = out.File
|
||||
file_kaspawalletd_proto_rawDesc = nil
|
||||
file_kaspawalletd_proto_goTypes = nil
|
||||
file_kaspawalletd_proto_depIdxs = nil
|
||||
}
|
||||
49
cmd/kaspawallet/daemon/pb/kaspawalletd.proto
Normal file
49
cmd/kaspawallet/daemon/pb/kaspawalletd.proto
Normal file
@@ -0,0 +1,49 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb";
|
||||
|
||||
service kaspawalletd {
|
||||
rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {}
|
||||
rpc CreateUnsignedTransaction (CreateUnsignedTransactionRequest) returns (CreateUnsignedTransactionResponse) {}
|
||||
rpc GetReceiveAddress (GetReceiveAddressRequest) returns (GetReceiveAddressResponse) {}
|
||||
rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {}
|
||||
rpc Broadcast (BroadcastRequest) returns (BroadcastResponse) {}
|
||||
}
|
||||
|
||||
message GetBalanceRequest {
|
||||
}
|
||||
|
||||
message GetBalanceResponse {
|
||||
uint64 available = 1;
|
||||
uint64 pending = 2;
|
||||
}
|
||||
|
||||
message CreateUnsignedTransactionRequest {
|
||||
string address = 1;
|
||||
uint64 amount = 2;
|
||||
}
|
||||
|
||||
message CreateUnsignedTransactionResponse {
|
||||
bytes unsignedTransaction = 1;
|
||||
}
|
||||
|
||||
message GetReceiveAddressRequest {
|
||||
}
|
||||
|
||||
message GetReceiveAddressResponse {
|
||||
string address = 1;
|
||||
}
|
||||
|
||||
message BroadcastRequest {
|
||||
bytes transaction = 1;
|
||||
}
|
||||
|
||||
message BroadcastResponse {
|
||||
string txID = 1;
|
||||
}
|
||||
|
||||
message ShutdownRequest {
|
||||
}
|
||||
|
||||
message ShutdownResponse {
|
||||
}
|
||||
234
cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go
Normal file
234
cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go
Normal file
@@ -0,0 +1,234 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion6
|
||||
|
||||
// KaspawalletdClient is the client API for Kaspawalletd service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type KaspawalletdClient interface {
|
||||
GetBalance(ctx context.Context, in *GetBalanceRequest, opts ...grpc.CallOption) (*GetBalanceResponse, error)
|
||||
CreateUnsignedTransaction(ctx context.Context, in *CreateUnsignedTransactionRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionResponse, error)
|
||||
GetReceiveAddress(ctx context.Context, in *GetReceiveAddressRequest, opts ...grpc.CallOption) (*GetReceiveAddressResponse, error)
|
||||
Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error)
|
||||
Broadcast(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error)
|
||||
}
|
||||
|
||||
type kaspawalletdClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewKaspawalletdClient(cc grpc.ClientConnInterface) KaspawalletdClient {
|
||||
return &kaspawalletdClient{cc}
|
||||
}
|
||||
|
||||
func (c *kaspawalletdClient) GetBalance(ctx context.Context, in *GetBalanceRequest, opts ...grpc.CallOption) (*GetBalanceResponse, error) {
|
||||
out := new(GetBalanceResponse)
|
||||
err := c.cc.Invoke(ctx, "/kaspawalletd/GetBalance", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *kaspawalletdClient) CreateUnsignedTransaction(ctx context.Context, in *CreateUnsignedTransactionRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionResponse, error) {
|
||||
out := new(CreateUnsignedTransactionResponse)
|
||||
err := c.cc.Invoke(ctx, "/kaspawalletd/CreateUnsignedTransaction", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *kaspawalletdClient) GetReceiveAddress(ctx context.Context, in *GetReceiveAddressRequest, opts ...grpc.CallOption) (*GetReceiveAddressResponse, error) {
|
||||
out := new(GetReceiveAddressResponse)
|
||||
err := c.cc.Invoke(ctx, "/kaspawalletd/GetReceiveAddress", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *kaspawalletdClient) Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error) {
|
||||
out := new(ShutdownResponse)
|
||||
err := c.cc.Invoke(ctx, "/kaspawalletd/Shutdown", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *kaspawalletdClient) Broadcast(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error) {
|
||||
out := new(BroadcastResponse)
|
||||
err := c.cc.Invoke(ctx, "/kaspawalletd/Broadcast", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// KaspawalletdServer is the server API for Kaspawalletd service.
|
||||
// All implementations must embed UnimplementedKaspawalletdServer
|
||||
// for forward compatibility
|
||||
type KaspawalletdServer interface {
|
||||
GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error)
|
||||
CreateUnsignedTransaction(context.Context, *CreateUnsignedTransactionRequest) (*CreateUnsignedTransactionResponse, error)
|
||||
GetReceiveAddress(context.Context, *GetReceiveAddressRequest) (*GetReceiveAddressResponse, error)
|
||||
Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
|
||||
Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error)
|
||||
mustEmbedUnimplementedKaspawalletdServer()
|
||||
}
|
||||
|
||||
// UnimplementedKaspawalletdServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedKaspawalletdServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedKaspawalletdServer) GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetBalance not implemented")
|
||||
}
|
||||
func (*UnimplementedKaspawalletdServer) CreateUnsignedTransaction(context.Context, *CreateUnsignedTransactionRequest) (*CreateUnsignedTransactionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateUnsignedTransaction not implemented")
|
||||
}
|
||||
func (*UnimplementedKaspawalletdServer) GetReceiveAddress(context.Context, *GetReceiveAddressRequest) (*GetReceiveAddressResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetReceiveAddress not implemented")
|
||||
}
|
||||
func (*UnimplementedKaspawalletdServer) Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Shutdown not implemented")
|
||||
}
|
||||
func (*UnimplementedKaspawalletdServer) Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Broadcast not implemented")
|
||||
}
|
||||
func (*UnimplementedKaspawalletdServer) mustEmbedUnimplementedKaspawalletdServer() {}
|
||||
|
||||
func RegisterKaspawalletdServer(s *grpc.Server, srv KaspawalletdServer) {
|
||||
s.RegisterService(&_Kaspawalletd_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Kaspawalletd_GetBalance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetBalanceRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(KaspawalletdServer).GetBalance(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/kaspawalletd/GetBalance",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(KaspawalletdServer).GetBalance(ctx, req.(*GetBalanceRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Kaspawalletd_CreateUnsignedTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CreateUnsignedTransactionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(KaspawalletdServer).CreateUnsignedTransaction(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/kaspawalletd/CreateUnsignedTransaction",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(KaspawalletdServer).CreateUnsignedTransaction(ctx, req.(*CreateUnsignedTransactionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Kaspawalletd_GetReceiveAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetReceiveAddressRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(KaspawalletdServer).GetReceiveAddress(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/kaspawalletd/GetReceiveAddress",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(KaspawalletdServer).GetReceiveAddress(ctx, req.(*GetReceiveAddressRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Kaspawalletd_Shutdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ShutdownRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(KaspawalletdServer).Shutdown(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/kaspawalletd/Shutdown",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(KaspawalletdServer).Shutdown(ctx, req.(*ShutdownRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Kaspawalletd_Broadcast_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(BroadcastRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(KaspawalletdServer).Broadcast(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/kaspawalletd/Broadcast",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(KaspawalletdServer).Broadcast(ctx, req.(*BroadcastRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Kaspawalletd_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "kaspawalletd",
|
||||
HandlerType: (*KaspawalletdServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetBalance",
|
||||
Handler: _Kaspawalletd_GetBalance_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CreateUnsignedTransaction",
|
||||
Handler: _Kaspawalletd_CreateUnsignedTransaction_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetReceiveAddress",
|
||||
Handler: _Kaspawalletd_GetReceiveAddress_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Shutdown",
|
||||
Handler: _Kaspawalletd_Shutdown_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Broadcast",
|
||||
Handler: _Kaspawalletd_Broadcast_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "kaspawalletd.proto",
|
||||
}
|
||||
83
cmd/kaspawallet/daemon/server/address.go
Normal file
83
cmd/kaspawallet/daemon/server/address.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *server) changeAddress() (util.Address, error) {
|
||||
err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.keysFile.Save()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
walletAddr := &walletAddress{
|
||||
index: s.keysFile.LastUsedInternalIndex(),
|
||||
cosignerIndex: s.keysFile.CosignerIndex,
|
||||
keyChain: internalKeychain,
|
||||
}
|
||||
path := s.walletAddressPath(walletAddr)
|
||||
return libkaspawallet.Address(s.params, s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, path, s.keysFile.ECDSA)
|
||||
}
|
||||
|
||||
func (s *server) GetReceiveAddress(_ context.Context, request *pb.GetReceiveAddressRequest) (*pb.GetReceiveAddressResponse, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if !s.isSynced() {
|
||||
return nil, errors.New("server is not synced")
|
||||
}
|
||||
|
||||
err := s.keysFile.SetLastUsedExternalIndex(s.keysFile.LastUsedExternalIndex() + 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.keysFile.Save()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
walletAddr := &walletAddress{
|
||||
index: s.keysFile.LastUsedExternalIndex(),
|
||||
cosignerIndex: s.keysFile.CosignerIndex,
|
||||
keyChain: externalKeychain,
|
||||
}
|
||||
path := s.walletAddressPath(walletAddr)
|
||||
address, err := libkaspawallet.Address(s.params, s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, path, s.keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.GetReceiveAddressResponse{Address: address.String()}, nil
|
||||
}
|
||||
|
||||
func (s *server) walletAddressString(wAddr *walletAddress) (string, error) {
|
||||
path := s.walletAddressPath(wAddr)
|
||||
addr, err := libkaspawallet.Address(s.params, s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, path, s.keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return addr.String(), nil
|
||||
}
|
||||
|
||||
func (s *server) walletAddressPath(wAddr *walletAddress) string {
|
||||
if s.isMultisig() {
|
||||
return fmt.Sprintf("m/%d/%d/%d", wAddr.cosignerIndex, wAddr.keyChain, wAddr.index)
|
||||
}
|
||||
return fmt.Sprintf("m/%d/%d", wAddr.keyChain, wAddr.index)
|
||||
}
|
||||
|
||||
func (s *server) isMultisig() bool {
|
||||
return len(s.keysFile.ExtendedPublicKeys) > 1
|
||||
}
|
||||
37
cmd/kaspawallet/daemon/server/balance.go
Normal file
37
cmd/kaspawallet/daemon/server/balance.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
)
|
||||
|
||||
func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.GetBalanceResponse, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var availableBalance, pendingBalance uint64
|
||||
for _, entry := range s.utxos {
|
||||
if isUTXOSpendable(entry, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
|
||||
availableBalance += entry.UTXOEntry.Amount()
|
||||
} else {
|
||||
pendingBalance += entry.UTXOEntry.Amount()
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.GetBalanceResponse{
|
||||
Available: availableBalance,
|
||||
Pending: pendingBalance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isUTXOSpendable(entry *walletUTXO, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
|
||||
if !entry.UTXOEntry.IsCoinbase() {
|
||||
return true
|
||||
}
|
||||
return entry.UTXOEntry.BlockDAAScore()+coinbaseMaturity < virtualDAAScore
|
||||
}
|
||||
33
cmd/kaspawallet/daemon/server/broadcast.go
Normal file
33
cmd/kaspawallet/daemon/server/broadcast.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *server) Broadcast(_ context.Context, request *pb.BroadcastRequest) (*pb.BroadcastResponse, error) {
|
||||
tx, err := libkaspawallet.ExtractTransaction(request.Transaction, s.keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txID, err := sendTransaction(s.rpcClient, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.BroadcastResponse{TxID: txID}, 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
|
||||
}
|
||||
15
cmd/kaspawallet/daemon/server/common.go
Normal file
15
cmd/kaspawallet/daemon/server/common.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package server
|
||||
|
||||
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
type walletUTXO struct {
|
||||
Outpoint *externalapi.DomainOutpoint
|
||||
UTXOEntry externalapi.UTXOEntry
|
||||
address *walletAddress
|
||||
}
|
||||
|
||||
type walletAddress struct {
|
||||
index uint32
|
||||
cosignerIndex uint32
|
||||
keyChain uint8
|
||||
}
|
||||
95
cmd/kaspawallet/daemon/server/create_unsigned_transaction.go
Normal file
95
cmd/kaspawallet/daemon/server/create_unsigned_transaction.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.CreateUnsignedTransactionRequest) (*pb.CreateUnsignedTransactionResponse, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if !s.isSynced() {
|
||||
return nil, errors.New("server is not synced")
|
||||
}
|
||||
|
||||
err := s.refreshExistingUTXOs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toAddress, err := util.DecodeAddress(request.Address, s.params.Prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Implement a better fee estimation mechanism
|
||||
const feePerInput = 1000
|
||||
selectedUTXOs, changeSompi, err := s.selectUTXOs(request.Amount, feePerInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changeAddress, err := s.changeAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
||||
s.keysFile.MinimumSignatures,
|
||||
[]*libkaspawallet.Payment{{
|
||||
Address: toAddress,
|
||||
Amount: request.Amount,
|
||||
}, {
|
||||
Address: changeAddress,
|
||||
Amount: changeSompi,
|
||||
}}, selectedUTXOs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.CreateUnsignedTransactionResponse{UnsignedTransaction: unsignedTransaction}, nil
|
||||
}
|
||||
|
||||
func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) (
|
||||
selectedUTXOs []*libkaspawallet.UTXO, changeSompi uint64, err error) {
|
||||
|
||||
selectedUTXOs = []*libkaspawallet.UTXO{}
|
||||
totalValue := uint64(0)
|
||||
|
||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
for _, utxo := range s.utxos {
|
||||
if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
|
||||
continue
|
||||
}
|
||||
|
||||
selectedUTXOs = append(selectedUTXOs, &libkaspawallet.UTXO{
|
||||
Outpoint: utxo.Outpoint,
|
||||
UTXOEntry: utxo.UTXOEntry,
|
||||
DerivationPath: s.walletAddressPath(utxo.address),
|
||||
})
|
||||
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
|
||||
}
|
||||
47
cmd/kaspawallet/daemon/server/log.go
Normal file
47
cmd/kaspawallet/daemon/server/log.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
var (
|
||||
backendLog = logger.NewBackend()
|
||||
log = backendLog.Logger("KSWD")
|
||||
spawn = panics.GoroutineWrapperFunc(log)
|
||||
|
||||
defaultAppDir = util.AppDir("kaspawallet", false)
|
||||
defaultLogFile = filepath.Join(defaultAppDir, "daemon.log")
|
||||
defaultErrLogFile = filepath.Join(defaultAppDir, "daemon_err.log")
|
||||
)
|
||||
|
||||
func initLog(logFile, errLogFile string) {
|
||||
log.SetLevel(logger.LevelDebug)
|
||||
err := backendLog.AddLogFile(logFile, logger.LevelTrace)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", logFile, logger.LevelTrace, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = backendLog.AddLogFile(errLogFile, logger.LevelWarn)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", errLogFile, logger.LevelWarn, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = backendLog.AddLogWriter(os.Stdout, logger.LevelInfo)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding stdout to the loggerfor level %s: %s", logger.LevelWarn, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = backendLog.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error starting the logger: %s ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
15
cmd/kaspawallet/daemon/server/rpc.go
Normal file
15
cmd/kaspawallet/daemon/server/rpc.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
109
cmd/kaspawallet/daemon/server/server.go
Normal file
109
cmd/kaspawallet/daemon/server/server.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
|
||||
"github.com/kaspanet/kaspad/infrastructure/os/signal"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedKaspawalletdServer
|
||||
|
||||
rpcClient *rpcclient.RPCClient
|
||||
params *dagconfig.Params
|
||||
|
||||
lock sync.RWMutex
|
||||
utxos map[externalapi.DomainOutpoint]*walletUTXO
|
||||
nextSyncStartIndex uint32
|
||||
keysFile *keys.File
|
||||
shutdown chan struct{}
|
||||
}
|
||||
|
||||
// Start starts the kaspawalletd server
|
||||
func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string) error {
|
||||
initLog(defaultLogFile, defaultErrLogFile)
|
||||
|
||||
defer panics.HandlePanic(log, "MAIN", nil)
|
||||
interrupt := signal.InterruptListener()
|
||||
|
||||
listener, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
return (errors.Wrapf(err, "Error listening to tcp at %s", listen))
|
||||
}
|
||||
log.Infof("Listening on %s", listen)
|
||||
|
||||
rpcClient, err := connectToRPC(params, rpcServer)
|
||||
if err != nil {
|
||||
return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer))
|
||||
}
|
||||
|
||||
keysFile, err := keys.ReadKeysFile(params, keysFilePath)
|
||||
if err != nil {
|
||||
return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer))
|
||||
}
|
||||
|
||||
serverInstance := &server{
|
||||
rpcClient: rpcClient,
|
||||
params: params,
|
||||
utxos: make(map[externalapi.DomainOutpoint]*walletUTXO),
|
||||
nextSyncStartIndex: 0,
|
||||
keysFile: keysFile,
|
||||
shutdown: make(chan struct{}),
|
||||
}
|
||||
|
||||
spawn("serverInstance.sync", func() {
|
||||
err := serverInstance.sync()
|
||||
if err != nil {
|
||||
printErrorAndExit(errors.Wrap(err, "error syncing the wallet"))
|
||||
}
|
||||
})
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterKaspawalletdServer(grpcServer, serverInstance)
|
||||
|
||||
spawn("grpcServer.Serve", func() {
|
||||
err := grpcServer.Serve(listener)
|
||||
if err != nil {
|
||||
printErrorAndExit(errors.Wrap(err, "Error serving gRPC"))
|
||||
}
|
||||
})
|
||||
|
||||
select {
|
||||
case <-serverInstance.shutdown:
|
||||
case <-interrupt:
|
||||
const stopTimeout = 2 * time.Second
|
||||
|
||||
stopChan := make(chan interface{})
|
||||
spawn("gRPCServer.Stop", func() {
|
||||
grpcServer.GracefulStop()
|
||||
close(stopChan)
|
||||
})
|
||||
|
||||
select {
|
||||
case <-stopChan:
|
||||
case <-time.After(stopTimeout):
|
||||
log.Warnf("Could not gracefully stop: timed out after %s", stopTimeout)
|
||||
grpcServer.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printErrorAndExit(err error) {
|
||||
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
13
cmd/kaspawallet/daemon/server/shutdown.go
Normal file
13
cmd/kaspawallet/daemon/server/shutdown.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
)
|
||||
|
||||
func (s *server) Shutdown(ctx context.Context, request *pb.ShutdownRequest) (*pb.ShutdownResponse, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
close(s.shutdown)
|
||||
return &pb.ShutdownResponse{}, nil
|
||||
}
|
||||
263
cmd/kaspawallet/daemon/server/sync.go
Normal file
263
cmd/kaspawallet/daemon/server/sync.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// externalKeychain is the key chain that is used to create receive addresses
|
||||
externalKeychain = 0
|
||||
// internalKeychain is used to create change addresses
|
||||
internalKeychain = 1
|
||||
)
|
||||
|
||||
var keyChains = []uint8{externalKeychain, internalKeychain}
|
||||
|
||||
type walletAddressSet map[string]*walletAddress
|
||||
|
||||
func (was walletAddressSet) strings() []string {
|
||||
addresses := make([]string, 0, len(was))
|
||||
for addr := range was {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func (s *server) sync() error {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
err := s.collectUTXOsFromRecentAddresses()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.collectUTXOsFromFarAddresses()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.refreshExistingUTXOsWithLock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const numIndexesToQuery = 100
|
||||
|
||||
// addressesToQuery scans the addresses in the given range. Because
|
||||
// each cosigner in a multisig has its own unique path for generating
|
||||
// addresses it goes over all the cosigners and add their addresses
|
||||
// for each key chain.
|
||||
func (s *server) addressesToQuery(start, end uint32) (walletAddressSet, error) {
|
||||
addresses := make(walletAddressSet)
|
||||
for index := start; index < end; index++ {
|
||||
for cosignerIndex := uint32(0); cosignerIndex < uint32(len(s.keysFile.ExtendedPublicKeys)); cosignerIndex++ {
|
||||
for _, keychain := range keyChains {
|
||||
address := &walletAddress{
|
||||
index: index,
|
||||
cosignerIndex: cosignerIndex,
|
||||
keyChain: keychain,
|
||||
}
|
||||
addressString, err := s.walletAddressString(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addresses[addressString] = address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// collectUTXOsFromFarAddresses collects numIndexesToQuery UTXOs
|
||||
// from the last point it stopped in the previous call.
|
||||
func (s *server) collectUTXOsFromFarAddresses() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
err := s.collectUTXOs(s.nextSyncStartIndex, s.nextSyncStartIndex+numIndexesToQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.nextSyncStartIndex += numIndexesToQuery
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) maxUsedIndex() uint32 {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
maxUsedIndex := s.keysFile.LastUsedExternalIndex()
|
||||
if s.keysFile.LastUsedInternalIndex() > maxUsedIndex {
|
||||
maxUsedIndex = s.keysFile.LastUsedInternalIndex()
|
||||
}
|
||||
|
||||
return maxUsedIndex
|
||||
}
|
||||
|
||||
// collectUTXOsFromRecentAddresses collects UTXOs from used addresses until
|
||||
// the address with the index of the last used address + 1000.
|
||||
// collectUTXOsFromRecentAddresses scans addresses in batches of numIndexesToQuery,
|
||||
// and releases the lock between scans.
|
||||
func (s *server) collectUTXOsFromRecentAddresses() error {
|
||||
maxUsedIndex := s.maxUsedIndex()
|
||||
for i := uint32(0); i < maxUsedIndex+1000; i += numIndexesToQuery {
|
||||
err := s.collectUTXOsWithLock(i, i+numIndexesToQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) collectUTXOsWithLock(start, end uint32) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return s.collectUTXOs(start, end)
|
||||
}
|
||||
|
||||
func (s *server) collectUTXOs(start, end uint32) error {
|
||||
addressSet, err := s.addressesToQuery(start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getUTXOsByAddressesResponse, err := s.rpcClient.GetUTXOsByAddresses(addressSet.strings())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.updateLastUsedIndexes(addressSet, getUTXOsByAddressesResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.updateUTXOs(addressSet, getUTXOsByAddressesResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) updateUTXOs(addressSet walletAddressSet,
|
||||
getUTXOsByAddressesResponse *appmessage.GetUTXOsByAddressesResponseMessage) error {
|
||||
|
||||
for _, entry := range getUTXOsByAddressesResponse.Entries {
|
||||
err := s.addEntryToUTXOSet(entry, addressSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) updateLastUsedIndexes(addressSet walletAddressSet,
|
||||
getUTXOsByAddressesResponse *appmessage.GetUTXOsByAddressesResponseMessage) error {
|
||||
|
||||
lastUsedExternalIndex := s.keysFile.LastUsedExternalIndex()
|
||||
lastUsedInternalIndex := s.keysFile.LastUsedInternalIndex()
|
||||
|
||||
for _, entry := range getUTXOsByAddressesResponse.Entries {
|
||||
walletAddress, ok := addressSet[entry.Address]
|
||||
if !ok {
|
||||
return errors.Errorf("Got result from address %s even though it wasn't requested", entry.Address)
|
||||
}
|
||||
|
||||
if walletAddress.cosignerIndex != s.keysFile.CosignerIndex {
|
||||
continue
|
||||
}
|
||||
|
||||
if walletAddress.keyChain == externalKeychain {
|
||||
if walletAddress.index > lastUsedExternalIndex {
|
||||
lastUsedExternalIndex = walletAddress.index
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if walletAddress.index > lastUsedInternalIndex {
|
||||
lastUsedInternalIndex = walletAddress.index
|
||||
}
|
||||
}
|
||||
|
||||
err := s.keysFile.SetLastUsedExternalIndex(lastUsedExternalIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.keysFile.SetLastUsedInternalIndex(lastUsedInternalIndex)
|
||||
}
|
||||
|
||||
func (s *server) refreshExistingUTXOsWithLock() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return s.refreshExistingUTXOs()
|
||||
}
|
||||
|
||||
func (s *server) addEntryToUTXOSet(entry *appmessage.UTXOsByAddressesEntry, addressSet walletAddressSet) error {
|
||||
outpoint, err := appmessage.RPCOutpointToDomainOutpoint(entry.Outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utxoEntry, err := appmessage.RPCUTXOEntryToUTXOEntry(entry.UTXOEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
address, ok := addressSet[entry.Address]
|
||||
if !ok {
|
||||
return errors.Errorf("Got result from address %s even though it wasn't requested", entry.Address)
|
||||
}
|
||||
|
||||
s.utxos[*outpoint] = &walletUTXO{
|
||||
Outpoint: outpoint,
|
||||
UTXOEntry: utxoEntry,
|
||||
address: address,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) refreshExistingUTXOs() error {
|
||||
addressSet := make(walletAddressSet, len(s.utxos))
|
||||
for _, utxo := range s.utxos {
|
||||
addressString, err := s.walletAddressString(utxo.address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addressSet[addressString] = utxo.address
|
||||
}
|
||||
|
||||
getUTXOsByAddressesResponse, err := s.rpcClient.GetUTXOsByAddresses(addressSet.strings())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.utxos = make(map[externalapi.DomainOutpoint]*walletUTXO, len(getUTXOsByAddressesResponse.Entries))
|
||||
for _, entry := range getUTXOsByAddressesResponse.Entries {
|
||||
err := s.addEntryToUTXOSet(entry, addressSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) isSynced() bool {
|
||||
return s.nextSyncStartIndex > s.keysFile.LastUsedInternalIndex() && s.nextSyncStartIndex > s.keysFile.LastUsedExternalIndex()
|
||||
}
|
||||
@@ -3,17 +3,20 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func dumpUnencryptedData(conf *dumpUnencryptedDataConfig) error {
|
||||
err := confirmDump()
|
||||
if err != nil {
|
||||
return err
|
||||
if !conf.Yes {
|
||||
err := confirmDump()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
|
||||
@@ -21,29 +24,29 @@ func dumpUnencryptedData(conf *dumpUnencryptedDataConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKeys, err := keysFile.DecryptPrivateKeys()
|
||||
mnemonics, err := keysFile.DecryptMnemonics(conf.Password)
|
||||
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)
|
||||
mnemonicPublicKeys := make(map[string]struct{})
|
||||
for i, mnemonic := range mnemonics {
|
||||
fmt.Printf("Mnemonic #%d:\n%s\n\n", i+1, mnemonic)
|
||||
publicKey, err := libkaspawallet.MasterPublicKeyFromMnemonic(conf.NetParams(), mnemonic, len(keysFile.ExtendedPublicKeys) > 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKeysPublicKeys[string(publicKey)] = struct{}{}
|
||||
mnemonicPublicKeys[publicKey] = struct{}{}
|
||||
}
|
||||
|
||||
i := 1
|
||||
for _, publicKey := range keysFile.PublicKeys {
|
||||
if _, exists := privateKeysPublicKeys[string(publicKey)]; exists {
|
||||
for _, extendedPublicKey := range keysFile.ExtendedPublicKeys {
|
||||
if _, exists := mnemonicPublicKeys[extendedPublicKey]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Public key #%d:\n%x\n\n", i, publicKey)
|
||||
fmt.Printf("Extended Public key #%d:\n%s\n\n", i, extendedPublicKey)
|
||||
i++
|
||||
}
|
||||
|
||||
@@ -55,14 +58,14 @@ 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()
|
||||
line, err := utils.ReadLine(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
if isPrefix || string(line) != "y" {
|
||||
if string(line) != "y" {
|
||||
return errors.Errorf("Dump aborted by user")
|
||||
}
|
||||
|
||||
|
||||
@@ -4,73 +4,79 @@ import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
"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()
|
||||
// CreateMnemonics generates `numKeys` number of mnemonics.
|
||||
func CreateMnemonics(params *dagconfig.Params, numKeys uint32, cmdLinePassword string, isMultisig bool) (encryptedPrivateKeys []*EncryptedMnemonic, extendedPublicKeys []string, err error) {
|
||||
mnemonics := make([]string, numKeys)
|
||||
for i := uint32(0); i < numKeys; i++ {
|
||||
var err error
|
||||
mnemonics[i], err = libkaspawallet.CreateMnemonic()
|
||||
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)
|
||||
return encryptedMnemonicExtendedPublicKeyPairs(params, mnemonics, cmdLinePassword, isMultisig)
|
||||
}
|
||||
|
||||
// ImportMnemonics imports a `numKeys` of mnemonics.
|
||||
func ImportMnemonics(params *dagconfig.Params, numKeys uint32, cmdLinePassword string, isMultisig bool) (encryptedPrivateKeys []*EncryptedMnemonic, extendedPublicKeys []string, err error) {
|
||||
mnemonics := make([]string, numKeys)
|
||||
for i := uint32(0); i < numKeys; i++ {
|
||||
privateKey, publicKey, err := keyPairFunction(i)
|
||||
fmt.Printf("Enter mnemonic #%d here:\n", i+1)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
mnemonic, err := utils.ReadLine(reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
publicKeys = append(publicKeys, publicKey)
|
||||
if !bip39.IsMnemonicValid(string(mnemonic)) {
|
||||
return nil, nil, errors.Errorf("mnemonic is invalid")
|
||||
}
|
||||
|
||||
encryptedPrivateKey, err := encryptPrivateKey(privateKey, password)
|
||||
mnemonics[i] = string(mnemonic)
|
||||
}
|
||||
return encryptedMnemonicExtendedPublicKeyPairs(params, mnemonics, cmdLinePassword, isMultisig)
|
||||
}
|
||||
|
||||
func encryptedMnemonicExtendedPublicKeyPairs(params *dagconfig.Params, mnemonics []string, cmdLinePassword string, isMultisig bool) (
|
||||
encryptedPrivateKeys []*EncryptedMnemonic, extendedPublicKeys []string, err error) {
|
||||
password := []byte(cmdLinePassword)
|
||||
if len(password) == 0 {
|
||||
|
||||
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([]*EncryptedMnemonic, 0, len(mnemonics))
|
||||
for _, mnemonic := range mnemonics {
|
||||
extendedPublicKey, err := libkaspawallet.MasterPublicKeyFromMnemonic(params, mnemonic, isMultisig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
extendedPublicKeys = append(extendedPublicKeys, extendedPublicKey)
|
||||
|
||||
encryptedPrivateKey, err := encryptMnemonic(mnemonic, password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
encryptedPrivateKeys = append(encryptedPrivateKeys, encryptedPrivateKey)
|
||||
}
|
||||
|
||||
return encryptedPrivateKeys, publicKeys, nil
|
||||
return encryptedPrivateKeys, extendedPublicKeys, nil
|
||||
}
|
||||
|
||||
func generateSalt() ([]byte, error) {
|
||||
@@ -83,7 +89,9 @@ func generateSalt() ([]byte, error) {
|
||||
return salt, nil
|
||||
}
|
||||
|
||||
func encryptPrivateKey(privateKey []byte, password []byte) (*EncryptedPrivateKey, error) {
|
||||
func encryptMnemonic(mnemonic string, password []byte) (*EncryptedMnemonic, error) {
|
||||
mnemonicBytes := []byte(mnemonic)
|
||||
|
||||
salt, err := generateSalt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -95,15 +103,15 @@ func encryptPrivateKey(privateKey []byte, password []byte) (*EncryptedPrivateKey
|
||||
}
|
||||
|
||||
// Select a random nonce, and leave capacity for the ciphertext.
|
||||
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(privateKey)+aead.Overhead())
|
||||
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(mnemonicBytes)+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)
|
||||
cipher := aead.Seal(nonce, nonce, []byte(mnemonicBytes), nil)
|
||||
|
||||
return &EncryptedPrivateKey{
|
||||
return &EncryptedMnemonic{
|
||||
cipher: cipher,
|
||||
salt: salt,
|
||||
}, nil
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -31,53 +32,62 @@ type encryptedPrivateKeyJSON struct {
|
||||
}
|
||||
|
||||
type keysFileJSON struct {
|
||||
EncryptedPrivateKeys []*encryptedPrivateKeyJSON `json:"encryptedPrivateKeys"`
|
||||
PublicKeys []string `json:"publicKeys"`
|
||||
MinimumSignatures uint32 `json:"minimumSignatures"`
|
||||
ECDSA bool `json:"ecdsa"`
|
||||
EncryptedPrivateKeys []*encryptedPrivateKeyJSON `json:"encryptedMnemonics"`
|
||||
ExtendedPublicKeys []string `json:"publicKeys"`
|
||||
MinimumSignatures uint32 `json:"minimumSignatures"`
|
||||
CosignerIndex uint32 `json:"cosignerIndex"`
|
||||
LastUsedExternalIndex uint32 `json:"lastUsedExternalIndex"`
|
||||
LastUsedInternalIndex uint32 `json:"lastUsedInternalIndex"`
|
||||
ECDSA bool `json:"ecdsa"`
|
||||
}
|
||||
|
||||
// EncryptedPrivateKey represents an encrypted private key
|
||||
type EncryptedPrivateKey struct {
|
||||
// EncryptedMnemonic represents an encrypted mnemonic
|
||||
type EncryptedMnemonic 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
|
||||
// File holds all the data related to the wallet keys
|
||||
type File struct {
|
||||
EncryptedMnemonics []*EncryptedMnemonic
|
||||
ExtendedPublicKeys []string
|
||||
MinimumSignatures uint32
|
||||
CosignerIndex uint32
|
||||
lastUsedExternalIndex uint32
|
||||
lastUsedInternalIndex uint32
|
||||
ECDSA bool
|
||||
path string
|
||||
}
|
||||
|
||||
func (d *Data) toJSON() *keysFileJSON {
|
||||
encryptedPrivateKeysJSON := make([]*encryptedPrivateKeyJSON, len(d.encryptedPrivateKeys))
|
||||
for i, encryptedPrivateKey := range d.encryptedPrivateKeys {
|
||||
func (d *File) toJSON() *keysFileJSON {
|
||||
encryptedPrivateKeysJSON := make([]*encryptedPrivateKeyJSON, len(d.EncryptedMnemonics))
|
||||
for i, encryptedPrivateKey := range d.EncryptedMnemonics {
|
||||
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,
|
||||
EncryptedPrivateKeys: encryptedPrivateKeysJSON,
|
||||
ExtendedPublicKeys: d.ExtendedPublicKeys,
|
||||
MinimumSignatures: d.MinimumSignatures,
|
||||
ECDSA: d.ECDSA,
|
||||
CosignerIndex: d.CosignerIndex,
|
||||
LastUsedExternalIndex: d.lastUsedExternalIndex,
|
||||
LastUsedInternalIndex: d.lastUsedInternalIndex,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Data) fromJSON(fileJSON *keysFileJSON) error {
|
||||
func (d *File) fromJSON(fileJSON *keysFileJSON) error {
|
||||
d.MinimumSignatures = fileJSON.MinimumSignatures
|
||||
d.ECDSA = fileJSON.ECDSA
|
||||
d.ExtendedPublicKeys = fileJSON.ExtendedPublicKeys
|
||||
d.CosignerIndex = fileJSON.CosignerIndex
|
||||
d.lastUsedExternalIndex = fileJSON.LastUsedExternalIndex
|
||||
d.lastUsedInternalIndex = fileJSON.LastUsedInternalIndex
|
||||
|
||||
d.encryptedPrivateKeys = make([]*EncryptedPrivateKey, len(fileJSON.EncryptedPrivateKeys))
|
||||
d.EncryptedMnemonics = make([]*EncryptedMnemonic, len(fileJSON.EncryptedPrivateKeys))
|
||||
for i, encryptedPrivateKeyJSON := range fileJSON.EncryptedPrivateKeys {
|
||||
cipher, err := hex.DecodeString(encryptedPrivateKeyJSON.Cipher)
|
||||
if err != nil {
|
||||
@@ -89,32 +99,92 @@ func (d *Data) fromJSON(fileJSON *keysFileJSON) error {
|
||||
return err
|
||||
}
|
||||
|
||||
d.encryptedPrivateKeys[i] = &EncryptedPrivateKey{
|
||||
d.EncryptedMnemonics[i] = &EncryptedMnemonic{
|
||||
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
|
||||
// SetPath sets the path where the file is saved to.
|
||||
func (d *File) SetPath(params *dagconfig.Params, path string, forceOverride bool) error {
|
||||
if path == "" {
|
||||
path = defaultKeysFile(params)
|
||||
}
|
||||
|
||||
if !forceOverride {
|
||||
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)? ", d.path)
|
||||
line, err := utils.ReadLine(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if string(line) != "y" {
|
||||
return errors.Errorf("aborted setting the file path to %s", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.path = path
|
||||
return nil
|
||||
}
|
||||
|
||||
// Path returns the file path.
|
||||
func (d *File) Path() string {
|
||||
return d.path
|
||||
}
|
||||
|
||||
// SetLastUsedExternalIndex sets the last used index in the external key
|
||||
// chain, and saves the file with the updated data.
|
||||
func (d *File) SetLastUsedExternalIndex(index uint32) error {
|
||||
if d.lastUsedExternalIndex == index {
|
||||
return nil
|
||||
}
|
||||
|
||||
d.lastUsedExternalIndex = index
|
||||
return d.Save()
|
||||
}
|
||||
|
||||
// LastUsedExternalIndex returns the last used index in the external key
|
||||
// chain and saves the file with the updated data.
|
||||
func (d *File) LastUsedExternalIndex() uint32 {
|
||||
return d.lastUsedExternalIndex
|
||||
}
|
||||
|
||||
// SetLastUsedInternalIndex sets the last used index in the internal key chain, and saves the file.
|
||||
func (d *File) SetLastUsedInternalIndex(index uint32) error {
|
||||
if d.lastUsedInternalIndex == index {
|
||||
return nil
|
||||
}
|
||||
|
||||
d.lastUsedInternalIndex = index
|
||||
return d.Save()
|
||||
}
|
||||
|
||||
// LastUsedInternalIndex returns the last used index in the internal key chain
|
||||
func (d *File) LastUsedInternalIndex() uint32 {
|
||||
return d.lastUsedInternalIndex
|
||||
}
|
||||
|
||||
// DecryptMnemonics 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 {
|
||||
func (d *File) DecryptMnemonics(cmdLinePassword string) ([]string, error) {
|
||||
password := []byte(cmdLinePassword)
|
||||
if len(password) == 0 {
|
||||
password = getPassword("Password:")
|
||||
}
|
||||
privateKeys := make([]string, len(d.EncryptedMnemonics))
|
||||
for i, encryptedPrivateKey := range d.EncryptedMnemonics {
|
||||
var err error
|
||||
privateKeys[i], err = decryptPrivateKey(encryptedPrivateKey, password)
|
||||
privateKeys[i], err = decryptMnemonic(encryptedPrivateKey, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -124,7 +194,7 @@ func (d *Data) DecryptPrivateKeys() ([][]byte, error) {
|
||||
}
|
||||
|
||||
// ReadKeysFile returns the data related to the keys file
|
||||
func ReadKeysFile(netParams *dagconfig.Params, path string) (*Data, error) {
|
||||
func ReadKeysFile(netParams *dagconfig.Params, path string) (*File, error) {
|
||||
if path == "" {
|
||||
path = defaultKeysFile(netParams)
|
||||
}
|
||||
@@ -142,7 +212,9 @@ func ReadKeysFile(netParams *dagconfig.Params, path string) (*Data, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keysFile := &Data{}
|
||||
keysFile := &File{
|
||||
path: path,
|
||||
}
|
||||
err = keysFile.fromJSON(decodedFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -180,57 +252,29 @@ func pathExists(path string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// WriteKeysFile writes a keys file with the given data
|
||||
func WriteKeysFile(netParams *dagconfig.Params, path string, encryptedPrivateKeys []*EncryptedPrivateKey,
|
||||
publicKeys [][]byte, minimumSignatures uint32, ecdsa bool) error {
|
||||
|
||||
if path == "" {
|
||||
path = defaultKeysFile(netParams)
|
||||
// Save writes the file contents to the disk.
|
||||
func (d *File) Save() error {
|
||||
if d.path == "" {
|
||||
return errors.New("cannot save a file with uninitialized path")
|
||||
}
|
||||
|
||||
exists, err := pathExists(path)
|
||||
err := createFileDirectoryIfDoesntExist(d.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)
|
||||
file, err := os.OpenFile(d.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())
|
||||
err = encoder.Encode(d.toJSON())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Wrote the keys into %s\n", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -239,19 +283,24 @@ func getAEAD(password, salt []byte) (cipher.AEAD, error) {
|
||||
return chacha20poly1305.NewX(key)
|
||||
}
|
||||
|
||||
func decryptPrivateKey(encryptedPrivateKey *EncryptedPrivateKey, password []byte) ([]byte, error) {
|
||||
func decryptMnemonic(encryptedPrivateKey *EncryptedMnemonic, password []byte) (string, error) {
|
||||
aead, err := getAEAD(password, encryptedPrivateKey.salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(encryptedPrivateKey.cipher) < aead.NonceSize() {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
return "", 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)
|
||||
decrypted, err := aead.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(decrypted), nil
|
||||
}
|
||||
|
||||
49
cmd/kaspawallet/libkaspawallet/bip32/base58/alphabet.go
Normal file
49
cmd/kaspawallet/libkaspawallet/bip32/base58/alphabet.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// AUTOGENERATED by genalphabet.go; do not edit.
|
||||
|
||||
package base58
|
||||
|
||||
const (
|
||||
// alphabet is the modified base58 alphabet used by Bitcoin.
|
||||
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
|
||||
alphabetIdx0 = '1'
|
||||
)
|
||||
|
||||
var b58 = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 255, 255, 255, 255, 255, 255,
|
||||
255, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 255, 17, 18, 19, 20, 21, 255,
|
||||
22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 255, 255, 255, 255, 255,
|
||||
255, 33, 34, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 255, 44, 45, 46,
|
||||
47, 48, 49, 50, 51, 52, 53, 54,
|
||||
55, 56, 57, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
75
cmd/kaspawallet/libkaspawallet/bip32/base58/base58.go
Normal file
75
cmd/kaspawallet/libkaspawallet/bip32/base58/base58.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
//go:generate go run genalphabet.go
|
||||
|
||||
var bigRadix = big.NewInt(58)
|
||||
var bigZero = big.NewInt(0)
|
||||
|
||||
// Decode decodes a modified base58 string to a byte slice.
|
||||
func Decode(b string) []byte {
|
||||
answer := big.NewInt(0)
|
||||
j := big.NewInt(1)
|
||||
|
||||
scratch := new(big.Int)
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
tmp := b58[b[i]]
|
||||
if tmp == 255 {
|
||||
return []byte("")
|
||||
}
|
||||
scratch.SetInt64(int64(tmp))
|
||||
scratch.Mul(j, scratch)
|
||||
answer.Add(answer, scratch)
|
||||
j.Mul(j, bigRadix)
|
||||
}
|
||||
|
||||
tmpval := answer.Bytes()
|
||||
|
||||
var numZeros int
|
||||
for numZeros = 0; numZeros < len(b); numZeros++ {
|
||||
if b[numZeros] != alphabetIdx0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
flen := numZeros + len(tmpval)
|
||||
val := make([]byte, flen)
|
||||
copy(val[numZeros:], tmpval)
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// Encode encodes a byte slice to a modified base58 string.
|
||||
func Encode(b []byte) string {
|
||||
x := new(big.Int)
|
||||
x.SetBytes(b)
|
||||
|
||||
answer := make([]byte, 0, len(b)*136/100)
|
||||
for x.Cmp(bigZero) > 0 {
|
||||
mod := new(big.Int)
|
||||
x.DivMod(x, bigRadix, mod)
|
||||
answer = append(answer, alphabet[mod.Int64()])
|
||||
}
|
||||
|
||||
// leading zero bytes
|
||||
for _, i := range b {
|
||||
if i != 0 {
|
||||
break
|
||||
}
|
||||
answer = append(answer, alphabetIdx0)
|
||||
}
|
||||
|
||||
// reverse
|
||||
alen := len(answer)
|
||||
for i := 0; i < alen/2; i++ {
|
||||
answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
|
||||
}
|
||||
|
||||
return string(answer)
|
||||
}
|
||||
98
cmd/kaspawallet/libkaspawallet/bip32/base58/base58_test.go
Normal file
98
cmd/kaspawallet/libkaspawallet/bip32/base58/base58_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
)
|
||||
|
||||
var stringTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"", ""},
|
||||
{" ", "Z"},
|
||||
{"-", "n"},
|
||||
{"0", "q"},
|
||||
{"1", "r"},
|
||||
{"-1", "4SU"},
|
||||
{"11", "4k8"},
|
||||
{"abc", "ZiCa"},
|
||||
{"1234598760", "3mJr7AoUXx2Wqd"},
|
||||
{"abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"},
|
||||
{"00000000000000000000000000000000000000000000000000000000000000", "3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y"},
|
||||
}
|
||||
|
||||
var invalidStringTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"0", ""},
|
||||
{"O", ""},
|
||||
{"I", ""},
|
||||
{"l", ""},
|
||||
{"3mJr0", ""},
|
||||
{"O3yxU", ""},
|
||||
{"3sNI", ""},
|
||||
{"4kl8", ""},
|
||||
{"0OIl", ""},
|
||||
{"!@#$%^&*()-_=+~`", ""},
|
||||
}
|
||||
|
||||
var hexTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"61", "2g"},
|
||||
{"626262", "a3gV"},
|
||||
{"636363", "aPEr"},
|
||||
{"73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"},
|
||||
{"00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"},
|
||||
{"516b6fcd0f", "ABnLTmg"},
|
||||
{"bf4f89001e670274dd", "3SEo3LWLoPntC"},
|
||||
{"572e4794", "3EFU7m"},
|
||||
{"ecac89cad93923c02321", "EJDM8drfXA6uyA"},
|
||||
{"10c8511e", "Rt5zm"},
|
||||
{"00000000000000000000", "1111111111"},
|
||||
}
|
||||
|
||||
func TestBase58(t *testing.T) {
|
||||
// Encode tests
|
||||
for x, test := range stringTests {
|
||||
tmp := []byte(test.in)
|
||||
if res := base58.Encode(tmp); res != test.out {
|
||||
t.Errorf("Encode test #%d failed: got: %s want: %s",
|
||||
x, res, test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Decode tests
|
||||
for x, test := range hexTests {
|
||||
b, err := hex.DecodeString(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("hex.DecodeString failed failed #%d: got: %s", x, test.in)
|
||||
continue
|
||||
}
|
||||
if res := base58.Decode(test.out); !bytes.Equal(res, b) {
|
||||
t.Errorf("Decode test #%d failed: got: %q want: %q",
|
||||
x, res, test.in)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Decode with invalid input
|
||||
for x, test := range invalidStringTests {
|
||||
if res := base58.Decode(test.in); string(res) != test.out {
|
||||
t.Errorf("Decode invalidString test #%d failed: got: %q want: %q",
|
||||
x, res, test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
)
|
||||
|
||||
func BenchmarkBase58Encode(b *testing.B) {
|
||||
b.StopTimer()
|
||||
data := bytes.Repeat([]byte{0xff}, 5000)
|
||||
b.SetBytes(int64(len(data)))
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
base58.Encode(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBase58Decode(b *testing.B) {
|
||||
b.StopTimer()
|
||||
data := bytes.Repeat([]byte{0xff}, 5000)
|
||||
encoded := base58.Encode(data)
|
||||
b.SetBytes(int64(len(encoded)))
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
base58.Decode(encoded)
|
||||
}
|
||||
}
|
||||
52
cmd/kaspawallet/libkaspawallet/bip32/base58/base58check.go
Normal file
52
cmd/kaspawallet/libkaspawallet/bip32/base58/base58check.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrChecksum indicates that the checksum of a check-encoded string does not verify against
|
||||
// the checksum.
|
||||
var ErrChecksum = errors.New("checksum error")
|
||||
|
||||
// ErrInvalidFormat indicates that the check-encoded string has an invalid format.
|
||||
var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing")
|
||||
|
||||
// checksum: first four bytes of sha256^2
|
||||
func checksum(input []byte) (cksum [4]byte) {
|
||||
h := sha256.Sum256(input)
|
||||
h2 := sha256.Sum256(h[:])
|
||||
copy(cksum[:], h2[:4])
|
||||
return
|
||||
}
|
||||
|
||||
// CheckEncode prepends a version byte and appends a four byte checksum.
|
||||
func CheckEncode(input []byte, version byte) string {
|
||||
b := make([]byte, 0, 1+len(input)+4)
|
||||
b = append(b, version)
|
||||
b = append(b, input[:]...)
|
||||
cksum := checksum(b)
|
||||
b = append(b, cksum[:]...)
|
||||
return Encode(b)
|
||||
}
|
||||
|
||||
// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
|
||||
func CheckDecode(input string) (result []byte, version byte, err error) {
|
||||
decoded := Decode(input)
|
||||
if len(decoded) < 5 {
|
||||
return nil, 0, ErrInvalidFormat
|
||||
}
|
||||
version = decoded[0]
|
||||
var cksum [4]byte
|
||||
copy(cksum[:], decoded[len(decoded)-4:])
|
||||
if checksum(decoded[:len(decoded)-4]) != cksum {
|
||||
return nil, 0, ErrChecksum
|
||||
}
|
||||
payload := decoded[1 : len(decoded)-4]
|
||||
result = append(result, payload...)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
)
|
||||
|
||||
var checkEncodingStringTests = []struct {
|
||||
version byte
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{20, "", "3MNQE1X"},
|
||||
{20, " ", "B2Kr6dBE"},
|
||||
{20, "-", "B3jv1Aft"},
|
||||
{20, "0", "B482yuaX"},
|
||||
{20, "1", "B4CmeGAC"},
|
||||
{20, "-1", "mM7eUf6kB"},
|
||||
{20, "11", "mP7BMTDVH"},
|
||||
{20, "abc", "4QiVtDjUdeq"},
|
||||
{20, "1234598760", "ZmNb8uQn5zvnUohNCEPP"},
|
||||
{20, "abcdefghijklmnopqrstuvwxyz", "K2RYDcKfupxwXdWhSAxQPCeiULntKm63UXyx5MvEH2"},
|
||||
{20, "00000000000000000000000000000000000000000000000000000000000000", "bi1EWXwJay2udZVxLJozuTb8Meg4W9c6xnmJaRDjg6pri5MBAxb9XwrpQXbtnqEoRV5U2pixnFfwyXC8tRAVC8XxnjK"},
|
||||
}
|
||||
|
||||
func TestBase58Check(t *testing.T) {
|
||||
for x, test := range checkEncodingStringTests {
|
||||
// test encoding
|
||||
if res := base58.CheckEncode([]byte(test.in), test.version); res != test.out {
|
||||
t.Errorf("CheckEncode test #%d failed: got %s, want: %s", x, res, test.out)
|
||||
}
|
||||
|
||||
// test decoding
|
||||
res, version, err := base58.CheckDecode(test.out)
|
||||
if err != nil {
|
||||
t.Errorf("CheckDecode test #%d failed with err: %v", x, err)
|
||||
} else if version != test.version {
|
||||
t.Errorf("CheckDecode test #%d failed: got version: %d want: %d", x, version, test.version)
|
||||
} else if string(res) != test.in {
|
||||
t.Errorf("CheckDecode test #%d failed: got: %s want: %s", x, res, test.in)
|
||||
}
|
||||
}
|
||||
|
||||
// test the two decoding failure cases
|
||||
// case 1: checksum error
|
||||
_, _, err := base58.CheckDecode("3MNQE1Y")
|
||||
if err != base58.ErrChecksum {
|
||||
t.Error("Checkdecode test failed, expected ErrChecksum")
|
||||
}
|
||||
// case 2: invalid formats (string lengths below 5 mean the version byte and/or the checksum
|
||||
// bytes are missing).
|
||||
testString := ""
|
||||
for len := 0; len < 4; len++ {
|
||||
// make a string of length `len`
|
||||
_, _, err = base58.CheckDecode(testString)
|
||||
if err != base58.ErrInvalidFormat {
|
||||
t.Error("Checkdecode test failed, expected ErrInvalidFormat")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
17
cmd/kaspawallet/libkaspawallet/bip32/base58/cov_report.sh
Normal file
17
cmd/kaspawallet/libkaspawallet/bip32/base58/cov_report.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script uses gocov to generate a test coverage report.
|
||||
# The gocov tool my be obtained with the following command:
|
||||
# go get github.com/axw/gocov/gocov
|
||||
#
|
||||
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||
|
||||
# Check for gocov.
|
||||
type gocov >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo >&2 "This script requires the gocov tool."
|
||||
echo >&2 "You may obtain it with the following command:"
|
||||
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||
exit 1
|
||||
fi
|
||||
gocov test | gocov report
|
||||
29
cmd/kaspawallet/libkaspawallet/bip32/base58/doc.go
Normal file
29
cmd/kaspawallet/libkaspawallet/bip32/base58/doc.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package base58 provides an API for working with modified base58 and Base58Check
|
||||
encodings.
|
||||
|
||||
Modified Base58 Encoding
|
||||
|
||||
Standard base58 encoding is similar to standard base64 encoding except, as the
|
||||
name implies, it uses a 58 character alphabet which results in an alphanumeric
|
||||
string and allows some characters which are problematic for humans to be
|
||||
excluded. Due to this, there can be various base58 alphabets.
|
||||
|
||||
The modified base58 alphabet used by Bitcoin, and hence this package, omits the
|
||||
0, O, I, and l characters that look the same in many fonts and are therefore
|
||||
hard to humans to distinguish.
|
||||
|
||||
Base58Check Encoding Scheme
|
||||
|
||||
The Base58Check encoding scheme is primarily used for Bitcoin addresses at the
|
||||
time of this writing, however it can be used to generically encode arbitrary
|
||||
byte arrays into human-readable strings along with a version byte that can be
|
||||
used to differentiate the same payload. For Bitcoin addresses, the extra
|
||||
version is used to differentiate the network of otherwise identical public keys
|
||||
which helps prevent using an address intended for one network on another.
|
||||
*/
|
||||
package base58
|
||||
71
cmd/kaspawallet/libkaspawallet/bip32/base58/example_test.go
Normal file
71
cmd/kaspawallet/libkaspawallet/bip32/base58/example_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
)
|
||||
|
||||
// This example demonstrates how to decode modified base58 encoded data.
|
||||
func ExampleDecode() {
|
||||
// Decode example modified base58 encoded data.
|
||||
encoded := "25JnwSn7XKfNQ"
|
||||
decoded := base58.Decode(encoded)
|
||||
|
||||
// Show the decoded data.
|
||||
fmt.Println("Decoded Data:", string(decoded))
|
||||
|
||||
// Output:
|
||||
// Decoded Data: Test data
|
||||
}
|
||||
|
||||
// This example demonstrates how to encode data using the modified base58
|
||||
// encoding scheme.
|
||||
func ExampleEncode() {
|
||||
// Encode example data with the modified base58 encoding scheme.
|
||||
data := []byte("Test data")
|
||||
encoded := base58.Encode(data)
|
||||
|
||||
// Show the encoded data.
|
||||
fmt.Println("Encoded Data:", encoded)
|
||||
|
||||
// Output:
|
||||
// Encoded Data: 25JnwSn7XKfNQ
|
||||
}
|
||||
|
||||
// This example demonstrates how to decode Base58Check encoded data.
|
||||
func ExampleCheckDecode() {
|
||||
// Decode an example Base58Check encoded data.
|
||||
encoded := "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
|
||||
decoded, version, err := base58.CheckDecode(encoded)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Show the decoded data.
|
||||
fmt.Printf("Decoded data: %x\n", decoded)
|
||||
fmt.Println("Version Byte:", version)
|
||||
|
||||
// Output:
|
||||
// Decoded data: 62e907b15cbf27d5425399ebf6f0fb50ebb88f18
|
||||
// Version Byte: 0
|
||||
}
|
||||
|
||||
// This example demonstrates how to encode data using the Base58Check encoding
|
||||
// scheme.
|
||||
func ExampleCheckEncode() {
|
||||
// Encode example data with the Base58Check encoding scheme.
|
||||
data := []byte("Test data")
|
||||
encoded := base58.CheckEncode(data, 0)
|
||||
|
||||
// Show the encoded data.
|
||||
fmt.Println("Encoded Data:", encoded)
|
||||
|
||||
// Output:
|
||||
// Encoded Data: 182iP79GRURMp7oMHDU
|
||||
}
|
||||
46
cmd/kaspawallet/libkaspawallet/bip32/bip32.go
Normal file
46
cmd/kaspawallet/libkaspawallet/bip32/bip32.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package bip32
|
||||
|
||||
import "crypto/rand"
|
||||
|
||||
// GenerateSeed generates seed that can be used to initialize a master key.
|
||||
func GenerateSeed() ([]byte, error) {
|
||||
randBytes := make([]byte, 32)
|
||||
_, err := rand.Read(randBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return randBytes, nil
|
||||
}
|
||||
|
||||
// NewMasterWithPath returns a new master key based on the given seed and version, with a derivation
|
||||
// to the given path.
|
||||
func NewMasterWithPath(seed []byte, version [4]byte, pathString string) (*ExtendedKey, error) {
|
||||
masterKey, err := NewMaster(seed, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return masterKey.DeriveFromPath(pathString)
|
||||
}
|
||||
|
||||
// NewPublicMasterWithPath returns a new public master key based on the given seed and version, with a derivation
|
||||
// to the given path.
|
||||
func NewPublicMasterWithPath(seed []byte, version [4]byte, pathString string) (*ExtendedKey, error) {
|
||||
masterKey, err := NewMaster(seed, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path, err := parsePath(pathString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
descendantKey, err := masterKey.path(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return descendantKey.Public()
|
||||
}
|
||||
420
cmd/kaspawallet/libkaspawallet/bip32/bip32_test.go
Normal file
420
cmd/kaspawallet/libkaspawallet/bip32/bip32_test.go
Normal file
@@ -0,0 +1,420 @@
|
||||
package bip32
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBIP32SpecVectors(t *testing.T) {
|
||||
type testPath struct {
|
||||
path string
|
||||
extendedPublicKey string
|
||||
extendedPrivateKey string
|
||||
}
|
||||
|
||||
type testVector struct {
|
||||
seed string
|
||||
version [4]byte
|
||||
paths []testPath
|
||||
}
|
||||
|
||||
// test vectors are copied from https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Test_Vectors
|
||||
testVectors := []testVector{
|
||||
{
|
||||
seed: "000102030405060708090a0b0c0d0e0f",
|
||||
version: BitcoinMainnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
|
||||
extendedPrivateKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
|
||||
},
|
||||
{
|
||||
path: "m/0'",
|
||||
extendedPublicKey: "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
|
||||
extendedPrivateKey: "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
|
||||
},
|
||||
{
|
||||
path: "m/0'/1",
|
||||
extendedPublicKey: "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
|
||||
extendedPrivateKey: "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
|
||||
},
|
||||
{
|
||||
path: "m/0'/1/2'",
|
||||
extendedPublicKey: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
|
||||
extendedPrivateKey: "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
|
||||
},
|
||||
{
|
||||
path: "m/0'/1/2'/2",
|
||||
extendedPublicKey: "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
|
||||
extendedPrivateKey: "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
|
||||
},
|
||||
{
|
||||
path: "m/0'/1/2'/2/1000000000",
|
||||
extendedPublicKey: "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
|
||||
extendedPrivateKey: "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
seed: "000102030405060708090a0b0c0d0e0f",
|
||||
version: KaspaMainnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "kpub2C2CKMtB3F5r4LEGRnS3o73omeQB3KJ5QfAzC5R3t9bpChBEZNitvn92JYeCTMtnR7oE1im7DhsxGqV72JErXFG9G3YnTHRnZPkGZLFE6PZ",
|
||||
extendedPrivateKey: "kprv5y2qurMHCsXYqr9oKku3Ry75DcZgdraE3SFPPh1SKp4qKtr61qQeNypYTGztwUUiVauHWmjxaQXeUKHxj4QCuDG4ULpZHkvBoH9XX19ynXm",
|
||||
},
|
||||
{
|
||||
path: "m/0'",
|
||||
extendedPublicKey: "kpub2EHcK5Be8WCqCwMydYJgg99v6TxXRPn66GbtAAoArLo6ZyUQycFz3vVS5pCuCfoKRL5nsxJXxLx3FETEyKyEb8isTgM3NbL15KsprxXRXYP",
|
||||
extendedPrivateKey: "kprv61JFuZekJ8eXzTHWXWmgK1DBYS831w4Ej3gHMnPZJ1G7hB9GS4wjW8AxEYMEMBrgCdnyt54pxmNXC5KgNegPhHLaYDVhXid5WHnNxE7Nir6",
|
||||
},
|
||||
{
|
||||
path: "m/0'/1",
|
||||
extendedPublicKey: "kpub2GTjWrjXXD5u3PQRMoCZGt3a9qwdRRWP2bGikSZynybJoWyYhQgJ1VPfVtfUccWfP3hqfNke4wSWqYC4Sf98GnYoktBtrELGi4Qc9xmGTUP",
|
||||
extendedPrivateKey: "kprv63UP7MCdgqXbpuKxFmfYuk6qbp791xnXfNM7x4ANEe4KvieQ9sN3Th5BebYHx7dieiYfgtfG3UKwL1quVzUNUSq23zTRbUPwB66kV2rWPC8",
|
||||
},
|
||||
{
|
||||
path: "m/0'/1/2'",
|
||||
extendedPublicKey: "kpub2K51ZPZPE5wJuZCWcPbvdt5iNzp9gy6NN8WPzms8xqxkDNAfWAWiuvwb3urK4UwyjZoaGkjFSt1VHsLM9kgfLEheLnA2wBPxRkKkFDqc9zP",
|
||||
extendedPrivateKey: "kprv665f9t2VPiP1h583WN4vGk8ypxyfHWNWzuaoCPTXQWRmLZqWxdCUN8d7CdkuvM9DABa4HMcBTt9qZDaf61PZbYGgQc1ykQdsnMqy7fTCNrm",
|
||||
},
|
||||
{
|
||||
path: "m/0'/1/2'/2",
|
||||
extendedPublicKey: "kpub2MJQPpgLQZcHz2gEJep1XPF2Tp6tKZQZocPhFjPcHHXMaTo2ZwD67WQWjEqhUH6iCsvkQmDCVcubrHgMF47s3qAuFZiDmNHnSSEbPpuRWiZ",
|
||||
extendedPrivateKey: "kprv68K3zK9SaC3zmYbmCdH1AFJHunGPv6giSPU6TLyziwzNhfTt2PtqZi62sxANP1YeDyhkuGqkNhc12QV7HRvunvrior75JVTawLK8d8zN34Z",
|
||||
},
|
||||
{
|
||||
path: "m/0'/1/2'/2/1000000000",
|
||||
extendedPublicKey: "kpub2P2AsWHaXgzVWNTgRCNjq6F2G3gC94DbbrnFW1mkVMurHbCR6MTkNcZaN4keKYBRgaDHv7912pcCSi5NLuchu6L2878JZqsRFPrWduDKq9i",
|
||||
extendedPrivateKey: "kprv6A2pTzkghKSCHtPDKAqjTxJHi1qhjbVkEdrehdN8w2NsQnsGYp9VppF6WowaHvfiqP71gdphDk982aVUpVwdutWG9LsJRQDJDfsVNMbtSap",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
||||
version: BitcoinMainnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
|
||||
extendedPrivateKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
|
||||
},
|
||||
{
|
||||
path: "m/0",
|
||||
extendedPublicKey: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
|
||||
extendedPrivateKey: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
|
||||
},
|
||||
{
|
||||
path: "m/0/2147483647'",
|
||||
extendedPublicKey: "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
|
||||
extendedPrivateKey: "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
|
||||
},
|
||||
{
|
||||
path: "m/0/2147483647'/1",
|
||||
extendedPublicKey: "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
|
||||
extendedPrivateKey: "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
|
||||
},
|
||||
{
|
||||
path: "m/0/2147483647'/1/2147483646'",
|
||||
extendedPublicKey: "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
|
||||
extendedPrivateKey: "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
|
||||
},
|
||||
{
|
||||
path: "m/0/2147483647'/1/2147483646'/2",
|
||||
extendedPublicKey: "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
|
||||
extendedPrivateKey: "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
||||
version: KaspaMainnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "kpub2C2CKMtB3F5r3wjbXwWLFJma1qYbiwYu6ExiV29srXigVgCRjXVqMgJceejBAcFkKg31vPRGcnPCzdDL9VA1fAG67ykFHmvSsmRNqKZg1po",
|
||||
extendedPrivateKey: "kprv5y2qurMHCsXYqTf8RuyKtApqToi7KUq3j237gdkGJCBhcssHBzBaosz8oLmx7z2ojdeiG4CQrWZqZr24mUnuWaapvktoS6pvNXmkszbHsFE",
|
||||
},
|
||||
{
|
||||
path: "m/0",
|
||||
extendedPublicKey: "kpub2FHwb5a8XFuvaDKtfitDK7B6NoHrRv3BeQi5eqBKvwaeBeeQJnquWWssE7h4xhGBXzXBncR21sEB9ne22drRzkvNQ2UvC84q1FY3GVzjZr1",
|
||||
extendedPrivateKey: "kprv62JbBa3EgtMdMjFRZhMCwyEMpmTN2TKLHBnUrSmiNc3fJrKFmFXexiZPNr3km3se9HtYA4c9HfyxvMetKmHxSokDvwJrpazfVwgKFEAdr1L",
|
||||
},
|
||||
{
|
||||
path: "m/0/2147483647'",
|
||||
extendedPublicKey: "kpub2GSzqgbeuA62k5Y56AsrnSremYWkyQCsjZncaE66agM2dwsrvgGDiafTqVwBiRsHKWSjSTGdK5empTWMoYLYiuNzw76yYrKqsdoe7KSjW9n",
|
||||
extendedPrivateKey: "kprv63TeSB4m4nXjXbTbz9LrRJuvDWgGZwV2NLs1mqgV2Lp3m9YiP8wyAnLyzCXjVJc83XDRw5onLgV5MbPf48u627BnMfYCb6ivHj1r1gJwAAq",
|
||||
},
|
||||
{
|
||||
path: "m/0/2147483647'/1",
|
||||
extendedPublicKey: "kpub2KFyFhab4oPDqhDD9q2RkPnt75PG5b8941HURHkRtZhUJmk2EBnvcV3qgJ8KWJZZuguHH6MrxCxbuFmNiSmVzEquXPJpmPm3oQUbMkjZU7h",
|
||||
extendedPrivateKey: "kprv66GcrC3hERpvdD8k3oVRPFr9Z3Ymg8QHgnMscuLpLEAVRyQsgeUg4gjMpzjMX1opMUa8gNtAkEAHgJAp72RU2b15VS51SChJmXSaVHSHVgJ",
|
||||
},
|
||||
{
|
||||
path: "m/0/2147483647'/1/2147483646'",
|
||||
extendedPublicKey: "kpub2LS1AfWwgCLw8eSotJqy7uV51ord8Zke5i1Mx1SqjKxim84xKriw91QwJxFphg61s8Yv5bRZzpHTYtvmQKt1hbYMoHdKKgrTfdZAtem6FS7",
|
||||
extendedPrivateKey: "kprv67Sem9z3qpndvANLnHJxkmYLTn28j72niV5m9d3EAzRjtKjonKQgbD6TThTk9SC6u3rpzCfA8bjsVRGBcyKxiRgFKNcKaQiw77T6Z6V751r",
|
||||
},
|
||||
{
|
||||
path: "m/0/2147483647'/1/2147483646'/2",
|
||||
extendedPublicKey: "kpub2Mo386jTCNfAsudhcNyf6es3QsPjNtfijsdFMnoLN7pJqKQXVVehKaMwPML6qFSiPBm9MWvytXJT3KzGERZv1rPwSTTQG49CLvkMZGaHgA1",
|
||||
extendedPrivateKey: "kprv68ogibCZN16sfRZEWMSejWvJrqZEyRwsNeheZQPionHKxX5NwxLSmn3TY78kJTHAwMiZGxHyahaZXy9hMHhBmQQy8E7pdpreoUnedk17vmK",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",
|
||||
version: BitcoinMainnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13",
|
||||
extendedPrivateKey: "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6",
|
||||
},
|
||||
{
|
||||
path: "m/0'",
|
||||
extendedPublicKey: "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y",
|
||||
extendedPrivateKey: "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",
|
||||
version: KaspaMainnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "kpub2C2CKMtB3F5r31Bm4L18TJ2btUshUoiAoajGtKBS8DoUTvRhPfjoJwY98eG9zCqPVknskPJH1TD4RvrEzCCT5VvEFDeU2LNHfUw5MkWwVFF",
|
||||
extendedPrivateKey: "kprv5y2qurMHCsXYpX7HxJU86A5sLT3D5LzKSMog5vmpZtGVb86Yr8RYm9DfHLW851G8pLKTpytWkwJYvVdNzuwLJ465T3TSdYAtfFS7Xx2owSo",
|
||||
},
|
||||
{
|
||||
path: "m/0'",
|
||||
extendedPublicKey: "kpub2EPQ4KiJicTCEYHAHULdWYnGaqV5df85D4yDhYsH4XiqiwZ9yAWfPQKrSN6fiZS8h8HiXM41rQQZ4PnavS8dekCAvKbMaBs69fHz2AFgp7S",
|
||||
extendedPrivateKey: "kprv61Q3epBQtEtu24ChBSod9QqY2oebECQDqr3cuATfWCBrr9E1RdCQqc1Nb6rUQxb4GUxsqvgPQfw1a3GXa8X63pHhtBNVNhShnGPmVHW2UAU",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",
|
||||
version: KaspaTestnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "ktub1vi816jr1DomD4Qct6C1n3qZ98kvP4TCgq21QUVkJ1xTADT74WfStAQAv5NA5ACsLYRLiAEs2YMz91LoWXh6YY6bFd6BidfhFE7dxhSVK7H",
|
||||
extendedPrivateKey: "ktrv5himbbCxArFTzaL9n4f1Qutpb6vRybjMKc6Qc668jgRUHR7xWyMCLN5h4mc89xdcf7wvnkq6n2TUda7wXFRym6GSTSuAKqUJEzcg8smnbs9",
|
||||
},
|
||||
{
|
||||
path: "m/0'",
|
||||
extendedPublicKey: "ktub1y5Kk4ZygbB7QbW27EXWqJbDqVNJXus76KFxDiBbEKspREaZe1SJxdBtDoCfoWocXuvBV7zbsVZUmUH9SmdH7nNXvj35GVAVjQUYd41EFiN",
|
||||
extendedPrivateKey: "ktrv5k5yLZ35rDcpC7RZ1CzWUAeVHTXp8T9Fj6LMRKmyfzLqYSFR6U84QpsQNXxUVuxY7GbLohcyRm5wH7m66U1jWrU4tapD4zk7N1aL6C3rnp7",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",
|
||||
version: KaspaDevnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "kdub4zZ57oeuxodRE6ZjEtX6KnUpww7hSaHbRg745QUF8GSe3Y6EKZa6tkjuJvCw8cTYni4dWNgh3tr5HoER7gTzpEyQcM4VNJmxyu9H2k8C3SL",
|
||||
extendedPrivateKey: "kdrv8mZiiJ828S581cVG8rz5xeY6PuHD37Zk4TBTH24dZvufAjm5n2FrLxRRTcSuDQtJ7HbDayGvoNwZnN1Z8QCt2o9FpAsTyWaZyfeKCvnUDsn",
|
||||
},
|
||||
{
|
||||
path: "m/0'",
|
||||
extendedPublicKey: "kdub52vGrmV3eAzmRdf8U2rbP3EVeHj5bRhVqALzteA64aN1JZDgu4LxyDXcce3Sry4Hz5ZUHLSRtr3ZvGAm3vQBPVFMHT1NvAGmU5WBhDEnN5e",
|
||||
extendedPrivateKey: "kdrv8ovvTFx9ooSUD9afN1Kb1uHm6FtbBxyeTwRQ6FkUWEq2RktYMX2iRRD8mNoFZNDDZSEdbv4oT7a2RuehhcndnZLtFJnWifrP6gbyAN52eWX",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",
|
||||
version: KaspaSimnetPrivate,
|
||||
paths: []testPath{
|
||||
{
|
||||
path: "m",
|
||||
extendedPublicKey: "ksub8Dp9tLuSVjDpAnKQ1sEyyZfrLiYhUQ7jd5TD92tgeX7mTQdyCwZYDNuhiN2WC23BwhGKgKvoJaWSPERp5gSuV4h2QEt1JwHyrSCjoEf5VHR",
|
||||
extendedPrivateKey: "ksrv3h87jvsCMMY9HKHc8DaPaA1jmh2mK4vq3mrRrWrPEFKNcvPCaYjx2tHqYCMLkgu1qonn9ScvgKMgeUG5pkgVdEgB579ytyAQBLrnezdttFb",
|
||||
},
|
||||
{
|
||||
path: "m/0'",
|
||||
extendedPublicKey: "ksub8GBMdJjaB6bANKQoF1aV2pRX35A5dFXe2Zh9xGaXaq38iRmRnSLQHqhR25s1vNdw94mATHgY9Xhw1hNA1vP64Jxy5LptrnnnLcZeTbX9Sng",
|
||||
extendedPrivateKey: "ksrv3jVKUthL2iuVUrP1MMutdQmQU3e9TvLjTG6NfkYEAZEjswWfA3Wp7M5Yqxhh6eDwHxSCAPQoL3z9J1uEPyGFNzsoWF52e8SDJMpScP8xuRw",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, vector := range testVectors {
|
||||
seed, err := hex.DecodeString(vector.seed)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeString: %+v", err)
|
||||
}
|
||||
|
||||
masterKey, err := NewMaster(seed, vector.version)
|
||||
if err != nil {
|
||||
t.Fatalf("NewMaster: %+v", err)
|
||||
}
|
||||
|
||||
for j, path := range vector.paths {
|
||||
extendedPrivateKey, err := masterKey.DeriveFromPath(path.path)
|
||||
if err != nil {
|
||||
t.Fatalf("Path: %+v", err)
|
||||
}
|
||||
|
||||
if extendedPrivateKey.String() != path.extendedPrivateKey {
|
||||
t.Fatalf("Test (%d, %d): expected extended private key %s but got %s", i, j, path.extendedPrivateKey, extendedPrivateKey.String())
|
||||
}
|
||||
|
||||
decodedExtendedPrivateKey, err := DeserializeExtendedKey(extendedPrivateKey.String())
|
||||
if err != nil {
|
||||
t.Fatalf("DeserializeExtendedKey: %+v", err)
|
||||
}
|
||||
|
||||
if extendedPrivateKey.String() != decodedExtendedPrivateKey.String() {
|
||||
t.Fatalf("Test (%d, %d): deserializing and serializing the extended private key didn't preserve the data", i, j)
|
||||
}
|
||||
|
||||
extendedPublicKey, err := extendedPrivateKey.Public()
|
||||
if err != nil {
|
||||
t.Fatalf("Public: %+v", err)
|
||||
}
|
||||
|
||||
if extendedPublicKey.String() != path.extendedPublicKey {
|
||||
t.Fatalf("Test (%d, %d): expected extended public key %s but got %s", i, j, path.extendedPublicKey, extendedPublicKey.String())
|
||||
}
|
||||
|
||||
decodedExtendedPublicKey, err := DeserializeExtendedKey(extendedPublicKey.String())
|
||||
if err != nil {
|
||||
t.Fatalf("DeserializeExtendedPublicKey: %+v", err)
|
||||
}
|
||||
|
||||
if extendedPublicKey.String() != decodedExtendedPublicKey.String() {
|
||||
t.Fatalf("Test (%d, %d): deserializing and serializing the ext pub didn't preserve the data", i, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtendedKey_DeriveFromPath checks that path that derive from extended private key and extended
|
||||
// public key lead to the same public keys.
|
||||
func TestExtendedKey_DeriveFromPath(t *testing.T) {
|
||||
r := rand.New(rand.NewSource(0))
|
||||
seed, err := GenerateSeed()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateSeed: %+v", err)
|
||||
}
|
||||
|
||||
master, err := NewMaster(seed, KaspaMainnetPrivate)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateSeed: %+v", err)
|
||||
}
|
||||
|
||||
masterPublic, err := master.Public()
|
||||
if err != nil {
|
||||
t.Fatalf("Public: %+v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
numIndexes := 1 + r.Intn(100)
|
||||
indexes := make([]string, numIndexes)
|
||||
for i := 0; i < numIndexes; i++ {
|
||||
index := r.Intn(hardenedIndexStart)
|
||||
indexes[i] = strconv.Itoa(int(index))
|
||||
}
|
||||
|
||||
indexesStr := strings.Join(indexes, "/")
|
||||
pathPrivate := "m/" + indexesStr
|
||||
pathPublic := "M/" + indexesStr
|
||||
|
||||
extendedPrivateKey, err := master.DeriveFromPath(pathPrivate)
|
||||
if err != nil {
|
||||
t.Fatalf("Path: %+v", err)
|
||||
}
|
||||
|
||||
extendedPublicKeyFromPrivateKey, err := extendedPrivateKey.Public()
|
||||
if err != nil {
|
||||
t.Fatalf("Public: %+v", err)
|
||||
}
|
||||
|
||||
extendedPublicKey, err := masterPublic.DeriveFromPath(pathPublic)
|
||||
if err != nil {
|
||||
t.Fatalf("Path: %+v", err)
|
||||
}
|
||||
|
||||
if extendedPublicKeyFromPrivateKey.String() != extendedPublicKey.String() {
|
||||
t.Fatalf("Path gives different result from private and public master keys")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPublicParentPublicChildDerivation was copied and modified from https://github.com/tyler-smith/go-bip32/blob/master/bip32_test.go
|
||||
func TestPublicParentPublicChildDerivation(t *testing.T) {
|
||||
// Generated using https://iancoleman.github.io/bip39/
|
||||
// Root key:
|
||||
// xprv9s21ZrQH143K2Cfj4mDZBcEecBmJmawReGwwoAou2zZzG45bM6cFPJSvobVTCB55L6Ld2y8RzC61CpvadeAnhws3CHsMFhNjozBKGNgucYm
|
||||
// Derivation Path m/44'/60'/0'/0:
|
||||
// xprv9zy5o7z1GMmYdaeQdmabWFhUf52Ytbpe3G5hduA4SghboqWe7aDGWseN8BJy1GU72wPjkCbBE1hvbXYqpCecAYdaivxjNnBoSNxwYD4wHpW
|
||||
// xpub6DxSCdWu6jKqr4isjo7bsPeDD6s3J4YVQV1JSHZg12Eagdqnf7XX4fxqyW2sLhUoFWutL7tAELU2LiGZrEXtjVbvYptvTX5Eoa4Mamdjm9u
|
||||
extendedMasterPublic, err := DeserializeExtendedKey("xpub6DxSCdWu6jKqr4isjo7bsPeDD6s3J4YVQV1JSHZg12Eagdqnf7XX4fxqyW2sLhUoFWutL7tAELU2LiGZrEXtjVbvYptvTX5Eoa4Mamdjm9u")
|
||||
if err != nil {
|
||||
t.Fatalf("DeserializeExtendedPublicKey: %+v", err)
|
||||
}
|
||||
|
||||
type testChildKey struct {
|
||||
pathFragment uint32
|
||||
privKey string
|
||||
pubKey string
|
||||
hexPubKey string
|
||||
}
|
||||
|
||||
expectedChildren := []testChildKey{
|
||||
{pathFragment: 0, hexPubKey: "0243187e1a2ba9ba824f5f81090650c8f4faa82b7baf93060d10b81f4b705afd46"},
|
||||
{pathFragment: 1, hexPubKey: "023790d11eb715c4320d8e31fba3a09b700051dc2cdbcce03f44b11c274d1e220b"},
|
||||
{pathFragment: 2, hexPubKey: "0302c5749c3c75cea234878ae3f4d8f65b75d584bcd7ed0943b016d6f6b59a2bad"},
|
||||
{pathFragment: 3, hexPubKey: "03f0440c94e5b14ea5b15875934597afff541bec287c6e65dc1102cafc07f69699"},
|
||||
{pathFragment: 4, hexPubKey: "026419d0d8996707605508ac44c5871edc7fe206a79ef615b74f2eea09c5852e2b"},
|
||||
{pathFragment: 5, hexPubKey: "02f63c6f195eea98bdb163c4a094260dea71d264b21234bed4df3899236e6c2298"},
|
||||
{pathFragment: 6, hexPubKey: "02d74709cd522081064858f393d009ead5a0ecd43ede3a1f57befcc942025cb5f9"},
|
||||
{pathFragment: 7, hexPubKey: "03e54bb92630c943d38bbd8a4a2e65fca7605e672d30a0e545a7198cbb60729ceb"},
|
||||
{pathFragment: 8, hexPubKey: "027e9d5acd14d39c4938697fba388cd2e8f31fc1c5dc02fafb93a10a280de85199"},
|
||||
{pathFragment: 9, hexPubKey: "02a167a9f0d57468fb6abf2f3f7967e2cadf574314753a06a9ef29bc76c54638d2"},
|
||||
|
||||
{pathFragment: 100, hexPubKey: "020db9ba00ddf68428e3f5bfe54252bbcd75b21e42f51bf3bfc4172bf0e5fa7905"},
|
||||
{pathFragment: 101, hexPubKey: "0299e3790956570737d6164e6fcda5a3daa304065ca95ba46bc73d436b84f34d46"},
|
||||
{pathFragment: 102, hexPubKey: "0202e0732c4c5d2b1036af173640e01957998cfd4f9cdaefab6ffe76eb869e2c59"},
|
||||
{pathFragment: 103, hexPubKey: "03d050adbd996c0c5d737ff638402dfbb8c08e451fef10e6d62fb57887c1ac6cb2"},
|
||||
{pathFragment: 104, hexPubKey: "038d466399e2d68b4b16043ad4d88893b3b2f84fc443368729a973df1e66f4f530"},
|
||||
{pathFragment: 105, hexPubKey: "034811e2f0c8c50440c08c2c9799b99c911c036e877e8325386ff61723ae3ffdce"},
|
||||
{pathFragment: 106, hexPubKey: "026339fd5842921888e711a6ba9104a5f0c94cc0569855273cf5faefdfbcd3cc29"},
|
||||
{pathFragment: 107, hexPubKey: "02833705c1069fab2aa92c6b0dac27807290d72e9f52378d493ac44849ca003b22"},
|
||||
{pathFragment: 108, hexPubKey: "032d2639bde1eb7bdf8444bd4f6cc26a9d1bdecd8ea15fac3b992c3da68d9d1df5"},
|
||||
{pathFragment: 109, hexPubKey: "02479c6d4a64b93a2f4343aa862c938fbc658c99219dd7bebb4830307cbd76c9e9"},
|
||||
}
|
||||
|
||||
for i, child := range expectedChildren {
|
||||
extendedPublicKey, err := extendedMasterPublic.Child(child.pathFragment)
|
||||
if err != nil {
|
||||
t.Fatalf("Child: %+v", err)
|
||||
}
|
||||
|
||||
publicKey, err := extendedPublicKey.PublicKey()
|
||||
if err != nil {
|
||||
t.Fatalf("PublicKey: %+v", err)
|
||||
}
|
||||
|
||||
pubKeyBytes, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
t.Fatalf("Serialize: %+v", err)
|
||||
}
|
||||
|
||||
pubKeyHex := hex.EncodeToString(pubKeyBytes[:])
|
||||
if child.hexPubKey != pubKeyHex {
|
||||
t.Fatalf("Test #%d: expected public key %s but got %s", i, child.hexPubKey, pubKeyHex)
|
||||
}
|
||||
}
|
||||
}
|
||||
152
cmd/kaspawallet/libkaspawallet/bip32/child_key_derivation.go
Normal file
152
cmd/kaspawallet/libkaspawallet/bip32/child_key_derivation.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package bip32
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const hardenedIndexStart = 0x80000000
|
||||
|
||||
// NewMaster returns a new extended private key based on the given seed and version
|
||||
func NewMaster(seed []byte, version [4]byte) (*ExtendedKey, error) {
|
||||
mac := newHMACWriter([]byte("Bitcoin seed"))
|
||||
mac.InfallibleWrite(seed)
|
||||
I := mac.Sum(nil)
|
||||
|
||||
var iL, iR [32]byte
|
||||
copy(iL[:], I[:32])
|
||||
copy(iR[:], I[32:])
|
||||
|
||||
privateKey, err := secp256k1.DeserializeECDSAPrivateKeyFromSlice(iL[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ExtendedKey{
|
||||
privateKey: privateKey,
|
||||
Version: version,
|
||||
Depth: 0,
|
||||
ParentFingerprint: [4]byte{},
|
||||
ChildNumber: 0,
|
||||
ChainCode: iR,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isHardened(i uint32) bool {
|
||||
return i >= hardenedIndexStart
|
||||
}
|
||||
|
||||
// Child return the i'th derived child of extKey.
|
||||
func (extKey *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
|
||||
I, err := extKey.calcI(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var iL, iR [32]byte
|
||||
copy(iL[:], I[:32])
|
||||
copy(iR[:], I[32:])
|
||||
|
||||
fingerPrint, err := extKey.calcFingerprint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
childExt := &ExtendedKey{
|
||||
Version: extKey.Version,
|
||||
Depth: extKey.Depth + 1,
|
||||
ParentFingerprint: fingerPrint,
|
||||
ChildNumber: i,
|
||||
ChainCode: iR,
|
||||
}
|
||||
|
||||
if extKey.privateKey != nil {
|
||||
childExt.privateKey, err = privateKeyAdd(extKey.privateKey, iL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
publicKey, err := extKey.PublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
childExt.publicKey, err = pointAdd(publicKey, iL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return childExt, nil
|
||||
}
|
||||
|
||||
func (extKey *ExtendedKey) calcFingerprint() ([4]byte, error) {
|
||||
publicKey, err := extKey.PublicKey()
|
||||
if err != nil {
|
||||
return [4]byte{}, err
|
||||
}
|
||||
|
||||
serializedPoint, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
return [4]byte{}, err
|
||||
}
|
||||
|
||||
hash := hash160(serializedPoint[:])
|
||||
var fingerprint [4]byte
|
||||
copy(fingerprint[:], hash[:4])
|
||||
return fingerprint, nil
|
||||
}
|
||||
|
||||
func privateKeyAdd(k *secp256k1.ECDSAPrivateKey, tweak [32]byte) (*secp256k1.ECDSAPrivateKey, error) {
|
||||
kCopy := *k
|
||||
err := kCopy.Add(tweak)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &kCopy, nil
|
||||
}
|
||||
|
||||
func (extKey *ExtendedKey) calcI(i uint32) ([]byte, error) {
|
||||
if isHardened(i) && !extKey.IsPrivate() {
|
||||
return nil, errors.Errorf("Cannot calculate hardened child for public key")
|
||||
}
|
||||
|
||||
mac := newHMACWriter(extKey.ChainCode[:])
|
||||
if isHardened(i) {
|
||||
mac.InfallibleWrite([]byte{0x00})
|
||||
mac.InfallibleWrite(extKey.privateKey.Serialize()[:])
|
||||
} else {
|
||||
publicKey, err := extKey.PublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializedPublicKey, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error serializing public key")
|
||||
}
|
||||
|
||||
mac.InfallibleWrite(serializedPublicKey[:])
|
||||
}
|
||||
|
||||
mac.InfallibleWrite(serializeUint32(i))
|
||||
return mac.Sum(nil), nil
|
||||
}
|
||||
|
||||
func serializeUint32(v uint32) []byte {
|
||||
serialized := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(serialized, v)
|
||||
return serialized
|
||||
}
|
||||
|
||||
func pointAdd(point *secp256k1.ECDSAPublicKey, tweak [32]byte) (*secp256k1.ECDSAPublicKey, error) {
|
||||
pointCopy := *point
|
||||
err := pointCopy.Add(tweak)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pointCopy, nil
|
||||
}
|
||||
104
cmd/kaspawallet/libkaspawallet/bip32/extended_key.go
Normal file
104
cmd/kaspawallet/libkaspawallet/bip32/extended_key.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package bip32
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ExtendedKey is a bip32 extended key
|
||||
type ExtendedKey struct {
|
||||
privateKey *secp256k1.ECDSAPrivateKey
|
||||
publicKey *secp256k1.ECDSAPublicKey
|
||||
Version [4]byte
|
||||
Depth uint8
|
||||
ParentFingerprint [4]byte
|
||||
ChildNumber uint32
|
||||
ChainCode [32]byte
|
||||
}
|
||||
|
||||
// PrivateKey returns the ECDSA private key associated with the extended key
|
||||
func (extKey *ExtendedKey) PrivateKey() *secp256k1.ECDSAPrivateKey {
|
||||
return extKey.privateKey
|
||||
}
|
||||
|
||||
// PublicKey returns the ECDSA public key associated with the extended key
|
||||
func (extKey *ExtendedKey) PublicKey() (*secp256k1.ECDSAPublicKey, error) {
|
||||
if extKey.publicKey != nil {
|
||||
return extKey.publicKey, nil
|
||||
}
|
||||
|
||||
publicKey, err := extKey.privateKey.ECDSAPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extKey.publicKey = publicKey
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
// IsPrivate returns whether the extended key is private
|
||||
func (extKey *ExtendedKey) IsPrivate() bool {
|
||||
return extKey.privateKey != nil
|
||||
}
|
||||
|
||||
// Public returns public version of the extended key
|
||||
func (extKey *ExtendedKey) Public() (*ExtendedKey, error) {
|
||||
if !extKey.IsPrivate() {
|
||||
return extKey, nil
|
||||
}
|
||||
|
||||
publicKey, err := extKey.PublicKey()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error calculating publicKey")
|
||||
}
|
||||
|
||||
version, err := toPublicVersion(extKey.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ExtendedKey{
|
||||
publicKey: publicKey,
|
||||
Version: version,
|
||||
Depth: extKey.Depth,
|
||||
ParentFingerprint: extKey.ParentFingerprint,
|
||||
ChildNumber: extKey.ChildNumber,
|
||||
ChainCode: extKey.ChainCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeriveFromPath returns the extended key derived from the given path
|
||||
func (extKey *ExtendedKey) DeriveFromPath(pathString string) (*ExtendedKey, error) {
|
||||
path, err := parsePath(pathString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return extKey.path(path)
|
||||
}
|
||||
|
||||
func (extKey *ExtendedKey) path(path *path) (*ExtendedKey, error) {
|
||||
descendantExtKey := extKey
|
||||
for _, index := range path.indexes {
|
||||
var err error
|
||||
descendantExtKey, err = descendantExtKey.Child(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if path.isPublic {
|
||||
return descendantExtKey.Public()
|
||||
}
|
||||
|
||||
return descendantExtKey, nil
|
||||
}
|
||||
|
||||
func (extKey *ExtendedKey) String() string {
|
||||
serialized, err := extKey.serialize()
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error serializing key"))
|
||||
}
|
||||
return base58.Encode(serialized)
|
||||
}
|
||||
58
cmd/kaspawallet/libkaspawallet/bip32/hash.go
Normal file
58
cmd/kaspawallet/libkaspawallet/bip32/hash.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package bip32
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func newHMACWriter(key []byte) hmacWriter {
|
||||
return hmacWriter{
|
||||
Hash: hmac.New(sha512.New, key),
|
||||
}
|
||||
}
|
||||
|
||||
type hmacWriter struct {
|
||||
hash.Hash
|
||||
}
|
||||
|
||||
func (hw hmacWriter) InfallibleWrite(p []byte) {
|
||||
_, err := hw.Write(p)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "writing to hmac should never fail"))
|
||||
}
|
||||
}
|
||||
|
||||
func calcChecksum(data []byte) []byte {
|
||||
return doubleSha256(data)[:checkSumLen]
|
||||
}
|
||||
|
||||
func doubleSha256(data []byte) []byte {
|
||||
inner := sha256.Sum256(data)
|
||||
outer := sha256.Sum256(inner[:])
|
||||
return outer[:]
|
||||
}
|
||||
|
||||
// validateChecksum validates that the last checkSumLen bytes of the
|
||||
// given data are its valid checksum.
|
||||
func validateChecksum(data []byte) error {
|
||||
checksum := data[len(data)-checkSumLen:]
|
||||
expectedChecksum := calcChecksum(data[:len(data)-checkSumLen])
|
||||
if !bytes.Equal(expectedChecksum, checksum) {
|
||||
return errors.Errorf("expected checksum %x but got %x", expectedChecksum, checksum)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hash160(data []byte) []byte {
|
||||
sha := sha256.New()
|
||||
ripe := ripemd160.New()
|
||||
sha.Write(data)
|
||||
ripe.Write(sha.Sum(nil))
|
||||
return ripe.Sum(nil)
|
||||
}
|
||||
60
cmd/kaspawallet/libkaspawallet/bip32/path.go
Normal file
60
cmd/kaspawallet/libkaspawallet/bip32/path.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package bip32
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type path struct {
|
||||
isPublic bool
|
||||
indexes []uint32
|
||||
}
|
||||
|
||||
func parsePath(pathString string) (*path, error) {
|
||||
parts := strings.Split(pathString, "/")
|
||||
isPublic := false
|
||||
switch parts[0] {
|
||||
case "m":
|
||||
isPublic = false
|
||||
case "M":
|
||||
isPublic = true
|
||||
default:
|
||||
return nil, errors.Errorf("%s is an invalid extended key type", parts[0])
|
||||
}
|
||||
|
||||
indexParts := parts[1:]
|
||||
indexes := make([]uint32, len(indexParts))
|
||||
for i, part := range indexParts {
|
||||
var err error
|
||||
indexes[i], err = parseIndex(part)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &path{
|
||||
isPublic: isPublic,
|
||||
indexes: indexes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseIndex(indexString string) (uint32, error) {
|
||||
const isHardenedSuffix = "'"
|
||||
isHardened := strings.HasSuffix(indexString, isHardenedSuffix)
|
||||
trimmedIndexString := strings.TrimSuffix(indexString, isHardenedSuffix)
|
||||
index, err := strconv.Atoi(trimmedIndexString)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if index >= hardenedIndexStart {
|
||||
return 0, errors.Errorf("max index value is %d but got %d", hardenedIndexStart, index)
|
||||
}
|
||||
|
||||
if isHardened {
|
||||
return uint32(index) + hardenedIndexStart, nil
|
||||
}
|
||||
|
||||
return uint32(index), nil
|
||||
}
|
||||
148
cmd/kaspawallet/libkaspawallet/bip32/serialization.go
Normal file
148
cmd/kaspawallet/libkaspawallet/bip32/serialization.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package bip32
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32/base58"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
versionSerializationLen = 4
|
||||
depthSerializationLen = 1
|
||||
fingerprintSerializationLen = 4
|
||||
childNumberSerializationLen = 4
|
||||
chainCodeSerializationLen = 32
|
||||
keySerializationLen = 33
|
||||
checkSumLen = 4
|
||||
)
|
||||
|
||||
const extendedKeySerializationLen = versionSerializationLen +
|
||||
depthSerializationLen +
|
||||
fingerprintSerializationLen +
|
||||
childNumberSerializationLen +
|
||||
chainCodeSerializationLen +
|
||||
keySerializationLen +
|
||||
checkSumLen
|
||||
|
||||
// DeserializeExtendedKey deserialized the given base58 string and returns an extended key
|
||||
func DeserializeExtendedKey(extKeyString string) (*ExtendedKey, error) {
|
||||
serializedBytes := base58.Decode(extKeyString)
|
||||
return deserializeExtendedPrivateKey(serializedBytes)
|
||||
}
|
||||
|
||||
func deserializeExtendedPrivateKey(serialized []byte) (*ExtendedKey, error) {
|
||||
if len(serialized) != extendedKeySerializationLen {
|
||||
return nil, errors.Errorf("key length must be %d bytes but got %d", extendedKeySerializationLen, len(serialized))
|
||||
}
|
||||
|
||||
err := validateChecksum(serialized)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extKey := &ExtendedKey{}
|
||||
|
||||
copy(extKey.Version[:], serialized[:versionSerializationLen])
|
||||
extKey.Depth = serialized[versionSerializationLen]
|
||||
copy(extKey.ParentFingerprint[:], serialized[versionSerializationLen+depthSerializationLen:])
|
||||
extKey.ChildNumber = binary.BigEndian.Uint32(
|
||||
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen:],
|
||||
)
|
||||
copy(
|
||||
extKey.ChainCode[:],
|
||||
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen:],
|
||||
)
|
||||
|
||||
isPrivate := isPrivateVersion(extKey.Version)
|
||||
if isPrivate {
|
||||
privateKeyPadding := serialized[versionSerializationLen+
|
||||
depthSerializationLen+
|
||||
fingerprintSerializationLen+
|
||||
childNumberSerializationLen+
|
||||
chainCodeSerializationLen]
|
||||
if privateKeyPadding != 0 {
|
||||
return nil, errors.Errorf("expected 0 padding for private key but got %d", privateKeyPadding)
|
||||
}
|
||||
|
||||
extKey.privateKey, err = secp256k1.DeserializeECDSAPrivateKeyFromSlice(serialized[versionSerializationLen+
|
||||
depthSerializationLen+
|
||||
fingerprintSerializationLen+
|
||||
childNumberSerializationLen+
|
||||
chainCodeSerializationLen+1 : versionSerializationLen+
|
||||
depthSerializationLen+
|
||||
fingerprintSerializationLen+
|
||||
childNumberSerializationLen+
|
||||
chainCodeSerializationLen+
|
||||
keySerializationLen])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
extKey.publicKey, err = secp256k1.DeserializeECDSAPubKey(serialized[versionSerializationLen+
|
||||
depthSerializationLen+
|
||||
fingerprintSerializationLen+
|
||||
childNumberSerializationLen+
|
||||
chainCodeSerializationLen : versionSerializationLen+
|
||||
depthSerializationLen+
|
||||
fingerprintSerializationLen+
|
||||
childNumberSerializationLen+
|
||||
chainCodeSerializationLen+
|
||||
keySerializationLen])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return extKey, nil
|
||||
}
|
||||
|
||||
func (extKey *ExtendedKey) serialize() ([]byte, error) {
|
||||
var serialized [extendedKeySerializationLen]byte
|
||||
copy(serialized[:], extKey.Version[:])
|
||||
serialized[versionSerializationLen] = extKey.Depth
|
||||
copy(serialized[versionSerializationLen+depthSerializationLen:], extKey.ParentFingerprint[:])
|
||||
binary.BigEndian.PutUint32(
|
||||
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen:],
|
||||
extKey.ChildNumber,
|
||||
)
|
||||
copy(
|
||||
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen:],
|
||||
extKey.ChainCode[:],
|
||||
)
|
||||
|
||||
if extKey.IsPrivate() {
|
||||
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen] = 0
|
||||
copy(
|
||||
serialized[versionSerializationLen+
|
||||
depthSerializationLen+
|
||||
fingerprintSerializationLen+
|
||||
childNumberSerializationLen+
|
||||
chainCodeSerializationLen+
|
||||
1:],
|
||||
extKey.privateKey.Serialize()[:],
|
||||
)
|
||||
} else {
|
||||
publicKey, err := extKey.PublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializedPublicKey, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
copy(
|
||||
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen:],
|
||||
serializedPublicKey[:],
|
||||
)
|
||||
}
|
||||
|
||||
checkSum := doubleSha256(serialized[:len(serialized)-checkSumLen])
|
||||
copy(
|
||||
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen+keySerializationLen:],
|
||||
checkSum,
|
||||
)
|
||||
return serialized[:], nil
|
||||
}
|
||||
137
cmd/kaspawallet/libkaspawallet/bip32/version.go
Normal file
137
cmd/kaspawallet/libkaspawallet/bip32/version.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package bip32
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
// BitcoinMainnetPrivate is the version that is used for
|
||||
// bitcoin mainnet bip32 private extended keys.
|
||||
// Ecnodes to xprv in base58.
|
||||
var BitcoinMainnetPrivate = [4]byte{
|
||||
0x04,
|
||||
0x88,
|
||||
0xad,
|
||||
0xe4,
|
||||
}
|
||||
|
||||
// BitcoinMainnetPublic is the version that is used for
|
||||
// bitcoin mainnet bip32 public extended keys.
|
||||
// Ecnodes to xpub in base58.
|
||||
var BitcoinMainnetPublic = [4]byte{
|
||||
0x04,
|
||||
0x88,
|
||||
0xb2,
|
||||
0x1e,
|
||||
}
|
||||
|
||||
// KaspaMainnetPrivate is the version that is used for
|
||||
// kaspa mainnet bip32 private extended keys.
|
||||
// Ecnodes to xprv in base58.
|
||||
var KaspaMainnetPrivate = [4]byte{
|
||||
0x03,
|
||||
0x8f,
|
||||
0x2e,
|
||||
0xf4,
|
||||
}
|
||||
|
||||
// KaspaMainnetPublic is the version that is used for
|
||||
// kaspa mainnet bip32 public extended keys.
|
||||
// Ecnodes to kpub in base58.
|
||||
var KaspaMainnetPublic = [4]byte{
|
||||
0x03,
|
||||
0x8f,
|
||||
0x33,
|
||||
0x2e,
|
||||
}
|
||||
|
||||
// KaspaTestnetPrivate is the version that is used for
|
||||
// kaspa testnet bip32 public extended keys.
|
||||
// Ecnodes to ktrv in base58.
|
||||
var KaspaTestnetPrivate = [4]byte{
|
||||
0x03,
|
||||
0x90,
|
||||
0x9e,
|
||||
0x07,
|
||||
}
|
||||
|
||||
// KaspaTestnetPublic is the version that is used for
|
||||
// kaspa testnet bip32 public extended keys.
|
||||
// Ecnodes to ktub in base58.
|
||||
var KaspaTestnetPublic = [4]byte{
|
||||
0x03,
|
||||
0x90,
|
||||
0xa2,
|
||||
0x41,
|
||||
}
|
||||
|
||||
// KaspaDevnetPrivate is the version that is used for
|
||||
// kaspa devnet bip32 public extended keys.
|
||||
// Ecnodes to kdrv in base58.
|
||||
var KaspaDevnetPrivate = [4]byte{
|
||||
0x03,
|
||||
0x8b,
|
||||
0x3d,
|
||||
0x80,
|
||||
}
|
||||
|
||||
// KaspaDevnetPublic is the version that is used for
|
||||
// kaspa devnet bip32 public extended keys.
|
||||
// Ecnodes to xdub in base58.
|
||||
var KaspaDevnetPublic = [4]byte{
|
||||
0x03,
|
||||
0x8b,
|
||||
0x41,
|
||||
0xba,
|
||||
}
|
||||
|
||||
// KaspaSimnetPrivate is the version that is used for
|
||||
// kaspa simnet bip32 public extended keys.
|
||||
// Ecnodes to ksrv in base58.
|
||||
var KaspaSimnetPrivate = [4]byte{
|
||||
0x03,
|
||||
0x90,
|
||||
0x42,
|
||||
0x42,
|
||||
}
|
||||
|
||||
// KaspaSimnetPublic is the version that is used for
|
||||
// kaspa simnet bip32 public extended keys.
|
||||
// Ecnodes to xsub in base58.
|
||||
var KaspaSimnetPublic = [4]byte{
|
||||
0x03,
|
||||
0x90,
|
||||
0x46,
|
||||
0x7d,
|
||||
}
|
||||
|
||||
func toPublicVersion(version [4]byte) ([4]byte, error) {
|
||||
switch version {
|
||||
case BitcoinMainnetPrivate:
|
||||
return BitcoinMainnetPublic, nil
|
||||
case KaspaMainnetPrivate:
|
||||
return KaspaMainnetPublic, nil
|
||||
case KaspaTestnetPrivate:
|
||||
return KaspaTestnetPublic, nil
|
||||
case KaspaDevnetPrivate:
|
||||
return KaspaDevnetPublic, nil
|
||||
case KaspaSimnetPrivate:
|
||||
return KaspaSimnetPublic, nil
|
||||
}
|
||||
|
||||
return [4]byte{}, errors.Errorf("unknown version %x", version)
|
||||
}
|
||||
|
||||
func isPrivateVersion(version [4]byte) bool {
|
||||
switch version {
|
||||
case BitcoinMainnetPrivate:
|
||||
return true
|
||||
case KaspaMainnetPrivate:
|
||||
return true
|
||||
case KaspaTestnetPrivate:
|
||||
return true
|
||||
case KaspaDevnetPrivate:
|
||||
return true
|
||||
case KaspaSimnetPrivate:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
82
cmd/kaspawallet/libkaspawallet/bip39.go
Normal file
82
cmd/kaspawallet/libkaspawallet/bip39.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package libkaspawallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
)
|
||||
|
||||
// CreateMnemonic creates a new bip-39 compatible mnemonic
|
||||
func CreateMnemonic() (string, error) {
|
||||
const bip39BitSize = 256
|
||||
entropy, _ := bip39.NewEntropy(bip39BitSize)
|
||||
return bip39.NewMnemonic(entropy)
|
||||
}
|
||||
|
||||
// Purpose and CoinType constants
|
||||
const (
|
||||
SingleSignerPurpose = 44
|
||||
// Note: this is not entirely compatible to BIP 45 since
|
||||
// BIP 45 doesn't have a coin type in its derivation path.
|
||||
MultiSigPurpose = 45
|
||||
// TODO: Register the coin type in https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
||||
CoinType = 111111
|
||||
)
|
||||
|
||||
func defaultPath(isMultisig bool) string {
|
||||
purpose := SingleSignerPurpose
|
||||
if isMultisig {
|
||||
purpose = MultiSigPurpose
|
||||
}
|
||||
|
||||
return fmt.Sprintf("m/%d'/%d'/0'", purpose, CoinType)
|
||||
}
|
||||
|
||||
// MasterPublicKeyFromMnemonic returns the master public key with the correct derivation for the given mnemonic.
|
||||
func MasterPublicKeyFromMnemonic(params *dagconfig.Params, mnemonic string, isMultisig bool) (string, error) {
|
||||
path := defaultPath(isMultisig)
|
||||
extendedKey, err := extendedKeyFromMnemonicAndPath(mnemonic, path, params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
extendedPublicKey, err := extendedKey.Public()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return extendedPublicKey.String(), nil
|
||||
}
|
||||
|
||||
func extendedKeyFromMnemonicAndPath(mnemonic string, path string, params *dagconfig.Params) (*bip32.ExtendedKey, error) {
|
||||
seed := bip39.NewSeed(mnemonic, "")
|
||||
version, err := versionFromParams(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
master, err := bip32.NewMasterWithPath(seed, version, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return master, nil
|
||||
}
|
||||
|
||||
func versionFromParams(params *dagconfig.Params) ([4]byte, error) {
|
||||
switch params.Name {
|
||||
case dagconfig.MainnetParams.Name:
|
||||
return bip32.KaspaMainnetPrivate, nil
|
||||
case dagconfig.TestnetParams.Name:
|
||||
return bip32.KaspaTestnetPrivate, nil
|
||||
case dagconfig.DevnetParams.Name:
|
||||
return bip32.KaspaDevnetPrivate, nil
|
||||
case dagconfig.SimnetParams.Name:
|
||||
return bip32.KaspaSimnetPrivate, nil
|
||||
}
|
||||
|
||||
return [4]byte{}, errors.Errorf("unknown network %s", params.Name)
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package libkaspawallet
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
@@ -71,23 +76,94 @@ func PublicKeyFromPrivateKey(privateKeyBytes []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func Address(params *dagconfig.Params, extendedPublicKeys []string, minimumSignatures uint32, path string, ecdsa bool) (util.Address, error) {
|
||||
sortPublicKeys(extendedPublicKeys)
|
||||
if uint32(len(extendedPublicKeys)) < 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)
|
||||
"provided public keys (%d)", minimumSignatures, len(extendedPublicKeys))
|
||||
}
|
||||
|
||||
redeemScript, err := multiSigRedeemScript(pubKeys, minimumSignatures, ecdsa)
|
||||
if len(extendedPublicKeys) == 1 {
|
||||
return p2pkAddress(params, extendedPublicKeys[0], path, ecdsa)
|
||||
}
|
||||
|
||||
redeemScript, err := multiSigRedeemScript(extendedPublicKeys, minimumSignatures, path, ecdsa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return util.NewAddressScriptHash(redeemScript, params.Prefix)
|
||||
}
|
||||
|
||||
func p2pkAddress(params *dagconfig.Params, extendedPublicKey string, path string, ecdsa bool) (util.Address, error) {
|
||||
extendedKey, err := bip32.DeserializeExtendedKey(extendedPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
derivedKey, err := extendedKey.DeriveFromPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, err := derivedKey.PublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ecdsa {
|
||||
serializedECDSAPublicKey, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return util.NewAddressPublicKeyECDSA(serializedECDSAPublicKey[:], params.Prefix)
|
||||
}
|
||||
|
||||
schnorrPublicKey, err := publicKey.ToSchnorr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializedSchnorrPublicKey, err := schnorrPublicKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return util.NewAddressPublicKey(serializedSchnorrPublicKey[:], params.Prefix)
|
||||
}
|
||||
|
||||
func sortPublicKeys(extendedPublicKeys []string) {
|
||||
sort.Slice(extendedPublicKeys, func(i, j int) bool {
|
||||
return strings.Compare(extendedPublicKeys[i], extendedPublicKeys[j]) < 0
|
||||
})
|
||||
}
|
||||
|
||||
func cosignerIndex(extendedPublicKey string, sortedExtendedPublicKeys []string) (uint32, error) {
|
||||
cosignerIndex := sort.SearchStrings(sortedExtendedPublicKeys, extendedPublicKey)
|
||||
if cosignerIndex == len(sortedExtendedPublicKeys) {
|
||||
return 0, errors.Errorf("couldn't find extended public key %s", extendedPublicKey)
|
||||
}
|
||||
|
||||
return uint32(cosignerIndex), nil
|
||||
}
|
||||
|
||||
// MinimumCosignerIndex returns the minimum index for the cosigner from the set of all extended public keys.
|
||||
func MinimumCosignerIndex(cosignerExtendedPublicKeys, allExtendedPublicKeys []string) (uint32, error) {
|
||||
allExtendedPublicKeysCopy := make([]string, len(allExtendedPublicKeys))
|
||||
copy(allExtendedPublicKeysCopy, allExtendedPublicKeys)
|
||||
sortPublicKeys(allExtendedPublicKeysCopy)
|
||||
|
||||
min := uint32(math.MaxUint32)
|
||||
for _, extendedPublicKey := range cosignerExtendedPublicKeys {
|
||||
cosignerIndex, err := cosignerIndex(extendedPublicKey, allExtendedPublicKeysCopy)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if cosignerIndex < min {
|
||||
min = cosignerIndex
|
||||
}
|
||||
}
|
||||
|
||||
return min, nil
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ type PartiallySignedInput struct {
|
||||
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"`
|
||||
DerivationPath string `protobuf:"bytes,5,opt,name=DerivationPath,proto3" json:"DerivationPath,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PartiallySignedInput) Reset() {
|
||||
@@ -151,13 +152,20 @@ func (x *PartiallySignedInput) GetPubKeySignaturePairs() []*PubKeySignaturePair
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *PartiallySignedInput) GetDerivationPath() string {
|
||||
if x != nil {
|
||||
return x.DerivationPath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
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"`
|
||||
ExtendedPubKey string `protobuf:"bytes,1,opt,name=extendedPubKey,proto3" json:"extendedPubKey,omitempty"`
|
||||
Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PubKeySignaturePair) Reset() {
|
||||
@@ -192,11 +200,11 @@ func (*PubKeySignaturePair) Descriptor() ([]byte, []int) {
|
||||
return file_wallet_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *PubKeySignaturePair) GetPubKey() []byte {
|
||||
func (x *PubKeySignaturePair) GetExtendedPubKey() string {
|
||||
if x != nil {
|
||||
return x.PubKey
|
||||
return x.ExtendedPubKey
|
||||
}
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PubKeySignaturePair) GetSignature() []byte {
|
||||
@@ -639,7 +647,7 @@ var file_wallet_proto_rawDesc = []byte{
|
||||
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,
|
||||
0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x22, 0xb4, 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,
|
||||
@@ -656,72 +664,75 @@ var file_wallet_proto_rawDesc = []byte{
|
||||
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,
|
||||
0x75, 0x72, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x44, 0x65, 0x72, 0x69,
|
||||
0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0e, 0x44, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68,
|
||||
0x22, 0x5b, 0x0a, 0x13, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74,
|
||||
0x75, 0x72, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x6e,
|
||||
0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, 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 (
|
||||
|
||||
@@ -13,10 +13,11 @@ message PartiallySignedInput{
|
||||
TransactionOutput prevOutput = 2;
|
||||
uint32 minimumSignatures = 3;
|
||||
repeated PubKeySignaturePair pubKeySignaturePairs = 4;
|
||||
string DerivationPath = 5;
|
||||
}
|
||||
|
||||
message PubKeySignaturePair{
|
||||
bytes pubKey = 1;
|
||||
string extendedPubKey = 1;
|
||||
bytes signature = 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,16 +21,16 @@ type PartiallySignedTransaction struct {
|
||||
// PartiallySignedInput represents an input signed
|
||||
// only by some of the relevant parties.
|
||||
type PartiallySignedInput struct {
|
||||
RedeeemScript []byte
|
||||
PrevOutput *externalapi.DomainTransactionOutput
|
||||
MinimumSignatures uint32
|
||||
PubKeySignaturePairs []*PubKeySignaturePair
|
||||
DerivationPath string
|
||||
}
|
||||
|
||||
// PubKeySignaturePair is a pair of public key and (potentially) its associated signature
|
||||
type PubKeySignaturePair struct {
|
||||
PubKey []byte
|
||||
Signature []byte
|
||||
ExtendedPublicKey string
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// DeserializePartiallySignedTransaction deserializes a byte slice into PartiallySignedTransaction.
|
||||
@@ -93,10 +93,10 @@ func partiallySignedInputFromProto(protoPartiallySignedInput *protoserialization
|
||||
}
|
||||
|
||||
return &PartiallySignedInput{
|
||||
RedeeemScript: protoPartiallySignedInput.RedeemScript,
|
||||
PrevOutput: output,
|
||||
MinimumSignatures: protoPartiallySignedInput.MinimumSignatures,
|
||||
PubKeySignaturePairs: pubKeySignaturePairs,
|
||||
DerivationPath: protoPartiallySignedInput.DerivationPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -107,24 +107,24 @@ func partiallySignedInputToProto(partiallySignedInput *PartiallySignedInput) *pr
|
||||
}
|
||||
|
||||
return &protoserialization.PartiallySignedInput{
|
||||
RedeemScript: partiallySignedInput.RedeeemScript,
|
||||
PrevOutput: transactionOutputToProto(partiallySignedInput.PrevOutput),
|
||||
MinimumSignatures: partiallySignedInput.MinimumSignatures,
|
||||
PubKeySignaturePairs: protoPairs,
|
||||
DerivationPath: partiallySignedInput.DerivationPath,
|
||||
}
|
||||
}
|
||||
|
||||
func pubKeySignaturePairFromProto(protoPubKeySignaturePair *protoserialization.PubKeySignaturePair) *PubKeySignaturePair {
|
||||
return &PubKeySignaturePair{
|
||||
PubKey: protoPubKeySignaturePair.PubKey,
|
||||
Signature: protoPubKeySignaturePair.Signature,
|
||||
ExtendedPublicKey: protoPubKeySignaturePair.ExtendedPubKey,
|
||||
Signature: protoPubKeySignaturePair.Signature,
|
||||
}
|
||||
}
|
||||
|
||||
func pubKeySignaturePairToProto(pubKeySignaturePair *PubKeySignaturePair) *protoserialization.PubKeySignaturePair {
|
||||
return &protoserialization.PubKeySignaturePair{
|
||||
PubKey: pubKeySignaturePair.PubKey,
|
||||
Signature: pubKeySignaturePair.Signature,
|
||||
ExtendedPubKey: pubKeySignaturePair.ExtendedPublicKey,
|
||||
Signature: pubKeySignaturePair.Signature,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +1,41 @@
|
||||
package libkaspawallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32"
|
||||
"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/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type signer interface {
|
||||
rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error)
|
||||
serializedPublicKey() ([]byte, error)
|
||||
}
|
||||
func rawTxInSignature(extendedKey *bip32.ExtendedKey, tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
sighashReusedValues *consensushashing.SighashReusedValues, ecdsa bool) ([]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) {
|
||||
privateKey := extendedKey.PrivateKey()
|
||||
if ecdsa {
|
||||
keyPair, err := secp256k1.DeserializeECDSAPrivateKeyFromSlice(privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error deserializing private key")
|
||||
}
|
||||
|
||||
return (*ecdsaSigner)(keyPair), nil
|
||||
return txscript.RawTxInSignatureECDSA(tx, idx, hashType, privateKey, sighashReusedValues)
|
||||
}
|
||||
|
||||
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKey)
|
||||
schnorrKeyPair, err := privateKey.ToSchnorr()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error deserializing private key")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*schnorrSigner)(keyPair), nil
|
||||
return txscript.RawTxInSignature(tx, idx, hashType, schnorrKeyPair, sighashReusedValues)
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
func Sign(params *dagconfig.Params, mnemonics []string, serializedPSTx []byte, ecdsa bool) ([]byte, error) {
|
||||
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(serializedPSTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, keyPair := range keyPairs {
|
||||
err = sign(keyPair, partiallySignedTransaction)
|
||||
for _, mnemonic := range mnemonics {
|
||||
err = sign(params, mnemonic, partiallySignedTransaction, ecdsa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,20 +44,15 @@ func Sign(privateKeys [][]byte, serializedPSTx []byte, ecdsa bool) ([]byte, erro
|
||||
return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
|
||||
}
|
||||
|
||||
func sign(keyPair signer, psTx *serialization.PartiallySignedTransaction) error {
|
||||
if isTransactionFullySigned(psTx) {
|
||||
func sign(params *dagconfig.Params, mnemonic string, partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) error {
|
||||
if isTransactionFullySigned(partiallySignedTransaction) {
|
||||
return nil
|
||||
}
|
||||
|
||||
serializedPublicKey, err := keyPair.serializedPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sighashReusedValues := &consensushashing.SighashReusedValues{}
|
||||
for i, partiallySignedInput := range psTx.PartiallySignedInputs {
|
||||
for i, partiallySignedInput := range partiallySignedTransaction.PartiallySignedInputs {
|
||||
prevOut := partiallySignedInput.PrevOutput
|
||||
psTx.Tx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(
|
||||
partiallySignedTransaction.Tx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(
|
||||
prevOut.Value,
|
||||
prevOut.ScriptPublicKey,
|
||||
false, // This is a fake value, because it's irrelevant for the signature
|
||||
@@ -125,10 +61,27 @@ func sign(keyPair signer, psTx *serialization.PartiallySignedTransaction) error
|
||||
}
|
||||
|
||||
signed := false
|
||||
for i, partiallySignedInput := range psTx.PartiallySignedInputs {
|
||||
for i, partiallySignedInput := range partiallySignedTransaction.PartiallySignedInputs {
|
||||
isMultisig := len(partiallySignedInput.PubKeySignaturePairs) > 1
|
||||
path := defaultPath(isMultisig)
|
||||
extendedKey, err := extendedKeyFromMnemonicAndPath(mnemonic, path, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
derivedKey, err := extendedKey.DeriveFromPath(partiallySignedInput.DerivationPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
derivedPublicKey, err := derivedKey.Public()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pair := range partiallySignedInput.PubKeySignaturePairs {
|
||||
if bytes.Equal(pair.PubKey, serializedPublicKey[:]) {
|
||||
pair.Signature, err = keyPair.rawTxInSignature(psTx.Tx, i, consensushashing.SigHashAll, sighashReusedValues)
|
||||
if pair.ExtendedPublicKey == derivedPublicKey.String() {
|
||||
pair.Signature, err = rawTxInSignature(derivedKey, partiallySignedTransaction.Tx, i, consensushashing.SigHashAll, sighashReusedValues, ecdsa)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package libkaspawallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Payment contains a recipient payment details
|
||||
@@ -18,22 +17,24 @@ type Payment struct {
|
||||
Amount uint64
|
||||
}
|
||||
|
||||
func sortPublicKeys(publicKeys [][]byte) {
|
||||
sort.Slice(publicKeys, func(i, j int) bool {
|
||||
return bytes.Compare(publicKeys[i], publicKeys[j]) < 0
|
||||
})
|
||||
// UTXO is a type that stores a UTXO and meta data
|
||||
// that is needed in order to sign it and create
|
||||
// transactions with it.
|
||||
type UTXO struct {
|
||||
Outpoint *externalapi.DomainOutpoint
|
||||
UTXOEntry externalapi.UTXOEntry
|
||||
DerivationPath string
|
||||
}
|
||||
|
||||
// CreateUnsignedTransaction creates an unsigned transaction
|
||||
func CreateUnsignedTransaction(
|
||||
pubKeys [][]byte,
|
||||
extendedPublicKeys []string,
|
||||
minimumSignatures uint32,
|
||||
ecdsa bool,
|
||||
payments []*Payment,
|
||||
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair) ([]byte, error) {
|
||||
selectedUTXOs []*UTXO) ([]byte, error) {
|
||||
|
||||
sortPublicKeys(pubKeys)
|
||||
unsignedTransaction, err := createUnsignedTransaction(pubKeys, minimumSignatures, ecdsa, payments, selectedUTXOs)
|
||||
sortPublicKeys(extendedPublicKeys)
|
||||
unsignedTransaction, err := createUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -41,13 +42,48 @@ func CreateUnsignedTransaction(
|
||||
return serialization.SerializePartiallySignedTransaction(unsignedTransaction)
|
||||
}
|
||||
|
||||
func multiSigRedeemScript(pubKeys [][]byte, minimumSignatures uint32, ecdsa bool) ([]byte, error) {
|
||||
func multiSigRedeemScript(extendedPublicKeys []string, minimumSignatures uint32, path string, ecdsa bool) ([]byte, error) {
|
||||
scriptBuilder := txscript.NewScriptBuilder()
|
||||
scriptBuilder.AddInt64(int64(minimumSignatures))
|
||||
for _, key := range pubKeys {
|
||||
scriptBuilder.AddData(key)
|
||||
for _, key := range extendedPublicKeys {
|
||||
extendedKey, err := bip32.DeserializeExtendedKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
derivedKey, err := extendedKey.DeriveFromPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, err := derivedKey.PublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serializedPublicKey []byte
|
||||
if ecdsa {
|
||||
serializedECDSAPublicKey, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serializedPublicKey = serializedECDSAPublicKey[:]
|
||||
} else {
|
||||
schnorrPublicKey, err := publicKey.ToSchnorr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializedSchnorrPublicKey, err := schnorrPublicKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serializedPublicKey = serializedSchnorrPublicKey[:]
|
||||
}
|
||||
|
||||
scriptBuilder.AddData(serializedPublicKey)
|
||||
}
|
||||
scriptBuilder.AddInt64(int64(len(pubKeys)))
|
||||
scriptBuilder.AddInt64(int64(len(extendedPublicKeys)))
|
||||
|
||||
if ecdsa {
|
||||
scriptBuilder.AddOp(txscript.OpCheckMultiSigECDSA)
|
||||
@@ -59,40 +95,40 @@ func multiSigRedeemScript(pubKeys [][]byte, minimumSignatures uint32, ecdsa bool
|
||||
}
|
||||
|
||||
func createUnsignedTransaction(
|
||||
pubKeys [][]byte,
|
||||
extendedPublicKeys []string,
|
||||
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
|
||||
}
|
||||
}
|
||||
selectedUTXOs []*UTXO) (*serialization.PartiallySignedTransaction, error) {
|
||||
|
||||
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 := make([]*serialization.PubKeySignaturePair, len(extendedPublicKeys))
|
||||
for i, extendedPublicKey := range extendedPublicKeys {
|
||||
extendedKey, err := bip32.DeserializeExtendedKey(extendedPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
derivedKey, err := extendedKey.DeriveFromPath(utxo.DerivationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
emptyPubKeySignaturePairs[i] = &serialization.PubKeySignaturePair{
|
||||
PubKey: pubKey,
|
||||
ExtendedPublicKey: derivedKey.String(),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
DerivationPath: utxo.DerivationPath,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +162,8 @@ func createUnsignedTransaction(
|
||||
}
|
||||
|
||||
// IsTransactionFullySigned returns whether the transaction is fully signed and ready to broadcast.
|
||||
func IsTransactionFullySigned(psTxBytes []byte) (bool, error) {
|
||||
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(psTxBytes)
|
||||
func IsTransactionFullySigned(partiallySignedTransactionBytes []byte) (bool, error) {
|
||||
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(partiallySignedTransactionBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -135,8 +171,8 @@ func IsTransactionFullySigned(psTxBytes []byte) (bool, error) {
|
||||
return isTransactionFullySigned(partiallySignedTransaction), nil
|
||||
}
|
||||
|
||||
func isTransactionFullySigned(psTx *serialization.PartiallySignedTransaction) bool {
|
||||
for _, input := range psTx.PartiallySignedInputs {
|
||||
func isTransactionFullySigned(partiallySignedTransaction *serialization.PartiallySignedTransaction) bool {
|
||||
for _, input := range partiallySignedTransaction.PartiallySignedInputs {
|
||||
numSignatures := 0
|
||||
for _, pair := range input.PubKeySignaturePairs {
|
||||
if pair.Signature != nil {
|
||||
@@ -152,18 +188,18 @@ func isTransactionFullySigned(psTx *serialization.PartiallySignedTransaction) bo
|
||||
|
||||
// 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)
|
||||
func ExtractTransaction(partiallySignedTransactionBytes []byte, ecdsa bool) (*externalapi.DomainTransaction, error) {
|
||||
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(partiallySignedTransactionBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return extractTransaction(partiallySignedTransaction)
|
||||
return extractTransaction(partiallySignedTransaction, ecdsa)
|
||||
}
|
||||
|
||||
func extractTransaction(psTx *serialization.PartiallySignedTransaction) (*externalapi.DomainTransaction, error) {
|
||||
for i, input := range psTx.PartiallySignedInputs {
|
||||
isMultisig := input.RedeeemScript != nil
|
||||
func extractTransaction(partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) (*externalapi.DomainTransaction, error) {
|
||||
for i, input := range partiallySignedTransaction.PartiallySignedInputs {
|
||||
isMultisig := len(input.PubKeySignaturePairs) > 1
|
||||
scriptBuilder := txscript.NewScriptBuilder()
|
||||
if isMultisig {
|
||||
signatureCount := 0
|
||||
@@ -177,13 +213,18 @@ func extractTransaction(psTx *serialization.PartiallySignedTransaction) (*extern
|
||||
return nil, errors.Errorf("missing %d signatures", input.MinimumSignatures-uint32(signatureCount))
|
||||
}
|
||||
|
||||
scriptBuilder.AddData(input.RedeeemScript)
|
||||
redeemScript, err := partiallySignedInputMultisigRedeemScript(input, ecdsa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scriptBuilder.AddData(redeemScript)
|
||||
sigScript, err := scriptBuilder.Script()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
psTx.Tx.Inputs[i].SignatureScript = sigScript
|
||||
partiallySignedTransaction.Tx.Inputs[i].SignatureScript = sigScript
|
||||
} else {
|
||||
if len(input.PubKeySignaturePairs) > 1 {
|
||||
return nil, errors.Errorf("Cannot sign on P2PK when len(input.PubKeySignaturePairs) > 1")
|
||||
@@ -199,8 +240,17 @@ func extractTransaction(psTx *serialization.PartiallySignedTransaction) (*extern
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
psTx.Tx.Inputs[i].SignatureScript = sigScript
|
||||
partiallySignedTransaction.Tx.Inputs[i].SignatureScript = sigScript
|
||||
}
|
||||
}
|
||||
return psTx.Tx, nil
|
||||
return partiallySignedTransaction.Tx, nil
|
||||
}
|
||||
|
||||
func partiallySignedInputMultisigRedeemScript(input *serialization.PartiallySignedInput, ecdsa bool) ([]byte, error) {
|
||||
extendedPublicKeys := make([]string, len(input.PubKeySignaturePairs))
|
||||
for i, pair := range input.PubKeySignaturePairs {
|
||||
extendedPublicKeys[i] = pair.ExtendedPublicKey
|
||||
}
|
||||
|
||||
return multiSigRedeemScript(extendedPublicKeys, input.MinimumSignatures, "m", ecdsa)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
|
||||
|
||||
func TestMultisig(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
params := &consensusConfig.Params
|
||||
forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) {
|
||||
consensusConfig.BlockCoinbaseMaturity = 0
|
||||
tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig")
|
||||
@@ -35,17 +36,24 @@ func TestMultisig(t *testing.T) {
|
||||
defer teardown(false)
|
||||
|
||||
const numKeys = 3
|
||||
privateKeys := make([][]byte, numKeys)
|
||||
publicKeys := make([][]byte, numKeys)
|
||||
mnemonics := make([]string, numKeys)
|
||||
publicKeys := make([]string, numKeys)
|
||||
for i := 0; i < numKeys; i++ {
|
||||
privateKeys[i], publicKeys[i], err = libkaspawallet.CreateKeyPair(ecdsa)
|
||||
var err error
|
||||
mnemonics[i], err = libkaspawallet.CreateMnemonic()
|
||||
if err != nil {
|
||||
t.Fatalf("CreateKeyPair: %+v", err)
|
||||
t.Fatalf("CreateMnemonic: %+v", err)
|
||||
}
|
||||
|
||||
publicKeys[i], err = libkaspawallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], true)
|
||||
if err != nil {
|
||||
t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
const minimumSignatures = 2
|
||||
address, err := libkaspawallet.Address(&consensusConfig.Params, publicKeys, minimumSignatures, ecdsa)
|
||||
path := "m/1/2/3"
|
||||
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, path, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("Address: %+v", err)
|
||||
}
|
||||
@@ -81,15 +89,18 @@ func TestMultisig(t *testing.T) {
|
||||
|
||||
block1Tx := block1.Transactions[0]
|
||||
block1TxOut := block1Tx.Outputs[0]
|
||||
selectedUTXOs := []*externalapi.OutpointAndUTXOEntryPair{{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||
Index: 0,
|
||||
selectedUTXOs := []*libkaspawallet.UTXO{
|
||||
{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
|
||||
DerivationPath: path,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
|
||||
}}
|
||||
}
|
||||
|
||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, ecdsa,
|
||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
||||
[]*libkaspawallet.Payment{{
|
||||
Address: address,
|
||||
Amount: 10,
|
||||
@@ -107,14 +118,14 @@ func TestMultisig(t *testing.T) {
|
||||
t.Fatalf("Transaction is not expected to be signed")
|
||||
}
|
||||
|
||||
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction)
|
||||
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction, ecdsa)
|
||||
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)
|
||||
signedTxStep1, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransaction, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
t.Fatalf("Sign: %+v", err)
|
||||
}
|
||||
|
||||
isFullySigned, err = libkaspawallet.IsTransactionFullySigned(signedTxStep1)
|
||||
@@ -126,22 +137,22 @@ func TestMultisig(t *testing.T) {
|
||||
t.Fatalf("Transaction is not expected to be fully signed")
|
||||
}
|
||||
|
||||
signedTxStep2, err := libkaspawallet.Sign(privateKeys[1:2], signedTxStep1, ecdsa)
|
||||
signedTxStep2, err := libkaspawallet.Sign(params, mnemonics[1:2], signedTxStep1, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign: %+v", err)
|
||||
}
|
||||
|
||||
extractedSignedTxStep2, err := libkaspawallet.ExtractTransaction(signedTxStep2)
|
||||
extractedSignedTxStep2, err := libkaspawallet.ExtractTransaction(signedTxStep2, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractTransaction: %+v", err)
|
||||
}
|
||||
|
||||
signedTxOneStep, err := libkaspawallet.Sign(privateKeys[:2], unsignedTransaction, ecdsa)
|
||||
signedTxOneStep, err := libkaspawallet.Sign(params, mnemonics[:2], unsignedTransaction, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign: %+v", err)
|
||||
}
|
||||
|
||||
extractedSignedTxOneStep, err := libkaspawallet.ExtractTransaction(signedTxOneStep)
|
||||
extractedSignedTxOneStep, err := libkaspawallet.ExtractTransaction(signedTxOneStep, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractTransaction: %+v", err)
|
||||
}
|
||||
@@ -170,6 +181,7 @@ func TestMultisig(t *testing.T) {
|
||||
|
||||
func TestP2PK(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
params := &consensusConfig.Params
|
||||
forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) {
|
||||
consensusConfig.BlockCoinbaseMaturity = 0
|
||||
tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig")
|
||||
@@ -179,17 +191,24 @@ func TestP2PK(t *testing.T) {
|
||||
defer teardown(false)
|
||||
|
||||
const numKeys = 1
|
||||
privateKeys := make([][]byte, numKeys)
|
||||
publicKeys := make([][]byte, numKeys)
|
||||
mnemonics := make([]string, numKeys)
|
||||
publicKeys := make([]string, numKeys)
|
||||
for i := 0; i < numKeys; i++ {
|
||||
privateKeys[i], publicKeys[i], err = libkaspawallet.CreateKeyPair(ecdsa)
|
||||
var err error
|
||||
mnemonics[i], err = libkaspawallet.CreateMnemonic()
|
||||
if err != nil {
|
||||
t.Fatalf("CreateKeyPair: %+v", err)
|
||||
t.Fatalf("CreateMnemonic: %+v", err)
|
||||
}
|
||||
|
||||
publicKeys[i], err = libkaspawallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], false)
|
||||
if err != nil {
|
||||
t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
const minimumSignatures = 1
|
||||
address, err := libkaspawallet.Address(&consensusConfig.Params, publicKeys, minimumSignatures, ecdsa)
|
||||
path := "m/1/2/3"
|
||||
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, path, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("Address: %+v", err)
|
||||
}
|
||||
@@ -231,16 +250,18 @@ func TestP2PK(t *testing.T) {
|
||||
|
||||
block1Tx := block1.Transactions[0]
|
||||
block1TxOut := block1Tx.Outputs[0]
|
||||
selectedUTXOs := []*externalapi.OutpointAndUTXOEntryPair{{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||
Index: 0,
|
||||
selectedUTXOs := []*libkaspawallet.UTXO{
|
||||
{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
|
||||
DerivationPath: path,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
|
||||
}}
|
||||
}
|
||||
|
||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
||||
ecdsa,
|
||||
[]*libkaspawallet.Payment{{
|
||||
Address: address,
|
||||
Amount: 10,
|
||||
@@ -258,17 +279,17 @@ func TestP2PK(t *testing.T) {
|
||||
t.Fatalf("Transaction is not expected to be signed")
|
||||
}
|
||||
|
||||
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction)
|
||||
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction, ecdsa)
|
||||
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)
|
||||
signedTx, err := libkaspawallet.Sign(params, mnemonics, unsignedTransaction, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
t.Fatalf("Sign: %+v", err)
|
||||
}
|
||||
|
||||
tx, err := libkaspawallet.ExtractTransaction(signedTx)
|
||||
tx, err := libkaspawallet.ExtractTransaction(signedTx, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractTransaction: %+v", err)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ func main() {
|
||||
err = showAddress(config.(*showAddressConfig))
|
||||
case dumpUnencryptedDataSubCmd:
|
||||
err = dumpUnencryptedData(config.(*dumpUnencryptedDataConfig))
|
||||
case startDaemonSubCmd:
|
||||
err = startDaemon(config.(*startDaemonConfig))
|
||||
default:
|
||||
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
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"
|
||||
)
|
||||
@@ -23,149 +17,49 @@ func send(conf *sendConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
toAddress, err := util.DecodeAddress(conf.ToAddress, conf.ActiveNetParams.Prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
if len(keysFile.ExtendedPublicKeys) > len(keysFile.EncryptedMnemonics) {
|
||||
return errors.Errorf("Cannot use 'send' command for multisig wallet without all of the keys")
|
||||
}
|
||||
|
||||
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tearDown()
|
||||
|
||||
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
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
|
||||
sendAmountSompi := uint64(conf.SendAmount * util.SompiPerKaspa)
|
||||
|
||||
const feePerInput = 1000
|
||||
selectedUTXOs, changeSompi, err := selectUTXOs(utxos, sendAmountSompi, feePerInput)
|
||||
createUnsignedTransactionResponse, err := daemonClient.CreateUnsignedTransaction(ctx, &pb.CreateUnsignedTransactionRequest{
|
||||
Address: conf.ToAddress,
|
||||
Amount: sendAmountSompi,
|
||||
})
|
||||
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)
|
||||
mnemonics, err := keysFile.DecryptMnemonics(conf.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKeys, err := keysFile.DecryptPrivateKeys()
|
||||
signedTransaction, err := libkaspawallet.Sign(conf.NetParams(), mnemonics, createUnsignedTransactionResponse.UnsignedTransaction, keysFile.ECDSA)
|
||||
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)
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel2()
|
||||
broadcastResponse, err := daemonClient.Broadcast(ctx2, &pb.BroadcastRequest{
|
||||
Transaction: signedTransaction,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Transaction was sent successfully")
|
||||
fmt.Printf("Transaction ID: \t%s\n", transactionID)
|
||||
fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchSpendableUTXOs(params *dagconfig.Params, client *rpcclient.RPCClient, address string) ([]*appmessage.UTXOsByAddressesEntry, error) {
|
||||
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{address})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockDAGInfo, err := client.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spendableUTXOs := make([]*appmessage.UTXOsByAddressesEntry, 0)
|
||||
for _, entry := range getUTXOsByAddressesResponse.Entries {
|
||||
if !isUTXOSpendable(entry, blockDAGInfo.VirtualDAAScore, params.BlockCoinbaseMaturity) {
|
||||
continue
|
||||
}
|
||||
spendableUTXOs = append(spendableUTXOs, entry)
|
||||
}
|
||||
return spendableUTXOs, nil
|
||||
}
|
||||
|
||||
func selectUTXOs(utxos []*appmessage.UTXOsByAddressesEntry, spendAmount uint64, feePerInput uint64) (
|
||||
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair, changeSompi uint64, err error) {
|
||||
|
||||
selectedUTXOs = []*externalapi.OutpointAndUTXOEntryPair{}
|
||||
totalValue := uint64(0)
|
||||
|
||||
for _, utxo := range utxos {
|
||||
txID, err := transactionid.FromString(utxo.Outpoint.TransactionID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
rpcUTXOEntry := utxo.UTXOEntry
|
||||
scriptPublicKeyScript, err := hex.DecodeString(rpcUTXOEntry.ScriptPublicKey.Script)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
scriptPublicKey := &externalapi.ScriptPublicKey{
|
||||
Script: scriptPublicKeyScript,
|
||||
Version: rpcUTXOEntry.ScriptPublicKey.Version,
|
||||
}
|
||||
|
||||
utxoEntry := utxopkg.NewUTXOEntry(rpcUTXOEntry.Amount, scriptPublicKey, rpcUTXOEntry.IsCoinbase, rpcUTXOEntry.BlockDAAScore)
|
||||
selectedUTXOs = append(selectedUTXOs, &externalapi.OutpointAndUTXOEntryPair{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *txID,
|
||||
Index: utxo.Outpoint.Index,
|
||||
},
|
||||
UTXOEntry: utxoEntry,
|
||||
})
|
||||
totalValue += utxo.UTXOEntry.Amount
|
||||
|
||||
fee := feePerInput * uint64(len(selectedUTXOs))
|
||||
totalSpend := spendAmount + fee
|
||||
if totalValue >= totalSpend {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fee := feePerInput * uint64(len(selectedUTXOs))
|
||||
totalSpend := spendAmount + fee
|
||||
if totalValue < totalSpend {
|
||||
return nil, 0, errors.Errorf("Insufficient funds for send: %f required, while only %f available",
|
||||
float64(totalSpend)/util.SompiPerKaspa, float64(totalValue)/util.SompiPerKaspa)
|
||||
}
|
||||
|
||||
return selectedUTXOs, totalValue - totalSpend, nil
|
||||
}
|
||||
|
||||
func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
|
||||
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error submitting transaction")
|
||||
}
|
||||
return submitTransactionResponse.TransactionID, nil
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
)
|
||||
|
||||
func showAddress(conf *showAddressConfig) error {
|
||||
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
|
||||
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tearDown()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
|
||||
response, err := daemonClient.GetReceiveAddress(ctx, &pb.GetReceiveAddressRequest{})
|
||||
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)
|
||||
fmt.Printf("Address:\n%s\n", response.Address)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
)
|
||||
@@ -14,22 +13,22 @@ func sign(conf *signConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
psTxBytes, err := hex.DecodeString(conf.Transaction)
|
||||
partiallySignedTransaction, err := hex.DecodeString(conf.Transaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKeys, err := keysFile.DecryptPrivateKeys()
|
||||
privateKeys, err := keysFile.DecryptMnemonics(conf.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedPSTxBytes, err := libkaspawallet.Sign(privateKeys, psTxBytes, keysFile.ECDSA)
|
||||
updatedPartiallySignedTransaction, err := libkaspawallet.Sign(conf.NetParams(), privateKeys, partiallySignedTransaction, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPSTxBytes)
|
||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPartiallySignedTransaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -40,6 +39,6 @@ func sign(conf *signConfig) error {
|
||||
fmt.Println("Successfully signed transaction")
|
||||
}
|
||||
|
||||
fmt.Printf("Transaction: %x\n", updatedPSTxBytes)
|
||||
fmt.Printf("Transaction: %x\n", updatedPartiallySignedTransaction)
|
||||
return nil
|
||||
}
|
||||
|
||||
7
cmd/kaspawallet/start_daemon.go
Normal file
7
cmd/kaspawallet/start_daemon.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
|
||||
|
||||
func startDaemon(conf *startDaemonConfig) error {
|
||||
return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile)
|
||||
}
|
||||
18
cmd/kaspawallet/utils/readline.go
Normal file
18
cmd/kaspawallet/utils/readline.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ReadLine reads one line from the given reader with trimmed white space.
|
||||
func ReadLine(reader *bufio.Reader) (string, error) {
|
||||
line, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(line)), nil
|
||||
}
|
||||
@@ -226,7 +226,7 @@ func (s *consensus) GetBlockAcceptanceData(blockHash *externalapi.DomainHash) (e
|
||||
return s.acceptanceDataStore.Get(s.databaseContext, stagingArea, blockHash)
|
||||
}
|
||||
|
||||
func (s *consensus) GetHashesBetween(lowHash, highHash *externalapi.DomainHash, maxBlocks uint64) (
|
||||
func (s *consensus) GetHashesBetween(lowHash, highHash *externalapi.DomainHash, maxBlueScoreDifference uint64) (
|
||||
hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) {
|
||||
|
||||
s.lock.Lock()
|
||||
@@ -243,7 +243,7 @@ func (s *consensus) GetHashesBetween(lowHash, highHash *externalapi.DomainHash,
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return s.syncManager.GetHashesBetween(stagingArea, lowHash, highHash, maxBlocks)
|
||||
return s.syncManager.GetHashesBetween(stagingArea, lowHash, highHash, maxBlueScoreDifference)
|
||||
}
|
||||
|
||||
func (s *consensus) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
@@ -535,3 +535,10 @@ func (s *consensus) Anticone(blockHash *externalapi.DomainHash) ([]*externalapi.
|
||||
|
||||
return s.dagTraversalManager.Anticone(stagingArea, blockHash)
|
||||
}
|
||||
|
||||
func (s *consensus) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return s.difficultyManager.EstimateNetworkHashesPerSecond(startHash, windowSize)
|
||||
}
|
||||
|
||||
@@ -314,7 +314,6 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
||||
syncManager := syncmanager.New(
|
||||
dbManager,
|
||||
genesisHash,
|
||||
config.MergeSetSizeLimit,
|
||||
dagTraversalManager,
|
||||
dagTopologyManager,
|
||||
ghostdagManager,
|
||||
|
||||
@@ -13,7 +13,7 @@ type Consensus interface {
|
||||
GetBlockRelations(blockHash *DomainHash) (parents []*DomainHash, selectedParent *DomainHash, children []*DomainHash, err error)
|
||||
GetBlockAcceptanceData(blockHash *DomainHash) (AcceptanceData, error)
|
||||
|
||||
GetHashesBetween(lowHash, highHash *DomainHash, maxBlocks uint64) (hashes []*DomainHash, actualHighHash *DomainHash, err error)
|
||||
GetHashesBetween(lowHash, highHash *DomainHash, maxBlueScoreDifference uint64) (hashes []*DomainHash, actualHighHash *DomainHash, err error)
|
||||
GetMissingBlockBodyHashes(highHash *DomainHash) ([]*DomainHash, error)
|
||||
GetPruningPointUTXOs(expectedPruningPointHash *DomainHash, fromOutpoint *DomainOutpoint, limit int) ([]*OutpointAndUTXOEntryPair, error)
|
||||
GetVirtualUTXOs(expectedVirtualParents []*DomainHash, fromOutpoint *DomainOutpoint, limit int) ([]*OutpointAndUTXOEntryPair, error)
|
||||
@@ -33,4 +33,5 @@ type Consensus interface {
|
||||
IsInSelectedParentChainOf(blockHashA *DomainHash, blockHashB *DomainHash) (bool, error)
|
||||
GetHeadersSelectedTip() (*DomainHash, error)
|
||||
Anticone(blockHash *DomainHash) ([]*DomainHash, error)
|
||||
EstimateNetworkHashesPerSecond(startHash *DomainHash, windowSize int) (uint64, error)
|
||||
}
|
||||
|
||||
@@ -67,13 +67,3 @@ func (bgd *BlockGHOSTDAGData) MergeSetReds() []*externalapi.DomainHash {
|
||||
func (bgd *BlockGHOSTDAGData) BluesAnticoneSizes() map[externalapi.DomainHash]KType {
|
||||
return bgd.bluesAnticoneSizes
|
||||
}
|
||||
|
||||
// MergeSet returns the whole MergeSet of the block (equivalent to MergeSetBlues+MergeSetReds)
|
||||
func (bgd *BlockGHOSTDAGData) MergeSet() []*externalapi.DomainHash {
|
||||
mergeSet := make([]*externalapi.DomainHash, len(bgd.mergeSetBlues)+len(bgd.mergeSetReds))
|
||||
copy(mergeSet, bgd.mergeSetBlues)
|
||||
if len(bgd.mergeSetReds) > 0 {
|
||||
copy(mergeSet[len(bgd.mergeSetBlues):], bgd.mergeSetReds)
|
||||
}
|
||||
return mergeSet
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package model
|
||||
|
||||
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// DifficultyManager provides a method to resolve the
|
||||
// difficulty value of a block
|
||||
type DifficultyManager interface {
|
||||
StageDAADataAndReturnRequiredDifficulty(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (uint32, error)
|
||||
RequiredDifficulty(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (uint32, error)
|
||||
EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error)
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ type GHOSTDAGManager interface {
|
||||
ChooseSelectedParent(stagingArea *StagingArea, blockHashes ...*externalapi.DomainHash) (*externalapi.DomainHash, error)
|
||||
Less(blockHashA *externalapi.DomainHash, ghostdagDataA *BlockGHOSTDAGData,
|
||||
blockHashB *externalapi.DomainHash, ghostdagDataB *BlockGHOSTDAGData) bool
|
||||
GetSortedMergeSet(stagingArea *StagingArea, current *externalapi.DomainHash) ([]*externalapi.DomainHash, error)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
// SyncManager exposes functions to support sync between kaspad nodes
|
||||
type SyncManager interface {
|
||||
GetHashesBetween(stagingArea *StagingArea, lowHash, highHash *externalapi.DomainHash, maxBlocks uint64) (
|
||||
GetHashesBetween(stagingArea *StagingArea, lowHash, highHash *externalapi.DomainHash, maxBlueScoreDifference uint64) (
|
||||
hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error)
|
||||
GetMissingBlockBodyHashes(stagingArea *StagingArea, highHash *externalapi.DomainHash) (
|
||||
[]*externalapi.DomainHash, error)
|
||||
|
||||
@@ -147,7 +147,7 @@ func (v *blockValidator) checkMergeSizeLimit(stagingArea *model.StagingArea, has
|
||||
return err
|
||||
}
|
||||
|
||||
mergeSetSize := len(ghostdagData.MergeSet())
|
||||
mergeSetSize := len(ghostdagData.MergeSetBlues()) + len(ghostdagData.MergeSetReds())
|
||||
|
||||
if uint64(mergeSetSize) > v.mergeSetSizeLimit {
|
||||
return errors.Wrapf(ruleerrors.ErrViolatingMergeLimit,
|
||||
|
||||
@@ -312,3 +312,7 @@ func (dm *mocDifficultyManager) StageDAADataAndReturnRequiredDifficulty(stagingA
|
||||
|
||||
return dm.testDifficulty, nil
|
||||
}
|
||||
|
||||
func (dm *mocDifficultyManager) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -39,8 +39,9 @@ func (c *coinbaseManager) ExpectedCoinbaseTransaction(stagingArea *model.Staging
|
||||
}
|
||||
|
||||
txOuts := make([]*externalapi.DomainTransactionOutput, 0, len(ghostdagData.MergeSetBlues()))
|
||||
for i, blue := range ghostdagData.MergeSetBlues() {
|
||||
txOut, hasReward, err := c.coinbaseOutputForBlueBlock(stagingArea, blue, acceptanceData[i], daaAddedBlocksSet)
|
||||
acceptanceDataMap := acceptanceDataFromArrayToMap(acceptanceData)
|
||||
for _, blue := range ghostdagData.MergeSetBlues() {
|
||||
txOut, hasReward, err := c.coinbaseOutputForBlueBlock(stagingArea, blue, acceptanceDataMap[*blue], daaAddedBlocksSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -120,10 +121,10 @@ func (c *coinbaseManager) coinbaseOutputForRewardFromRedBlocks(stagingArea *mode
|
||||
ghostdagData *model.BlockGHOSTDAGData, acceptanceData externalapi.AcceptanceData, daaAddedBlocksSet hashset.HashSet,
|
||||
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransactionOutput, bool, error) {
|
||||
|
||||
acceptanceDataMap := acceptanceDataFromArrayToMap(acceptanceData)
|
||||
totalReward := uint64(0)
|
||||
mergeSetBluesCount := len(ghostdagData.MergeSetBlues())
|
||||
for i, red := range ghostdagData.MergeSetReds() {
|
||||
reward, err := c.calcMergedBlockReward(stagingArea, red, acceptanceData[mergeSetBluesCount+i], daaAddedBlocksSet)
|
||||
for _, red := range ghostdagData.MergeSetReds() {
|
||||
reward, err := c.calcMergedBlockReward(stagingArea, red, acceptanceDataMap[*red], daaAddedBlocksSet)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -141,6 +142,14 @@ func (c *coinbaseManager) coinbaseOutputForRewardFromRedBlocks(stagingArea *mode
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func acceptanceDataFromArrayToMap(acceptanceData externalapi.AcceptanceData) map[externalapi.DomainHash]*externalapi.BlockAcceptanceData {
|
||||
acceptanceDataMap := make(map[externalapi.DomainHash]*externalapi.BlockAcceptanceData, len(acceptanceData))
|
||||
for _, blockAcceptanceData := range acceptanceData {
|
||||
acceptanceDataMap[*blockAcceptanceData.BlockHash] = blockAcceptanceData
|
||||
}
|
||||
return acceptanceDataMap
|
||||
}
|
||||
|
||||
// calcBlockSubsidy returns the subsidy amount a block at the provided blue score
|
||||
// should have. This is mainly used for determining how much the coinbase for
|
||||
// newly generated blocks awards as well as validating the coinbase for blocks
|
||||
|
||||
@@ -129,7 +129,8 @@ func (csm *consensusStateManager) calculateNewTips(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("The current tips are: %s", currentTips)
|
||||
log.Debugf("The number of tips is: %d", len(currentTips))
|
||||
log.Tracef("The current tips are: %s", currentTips)
|
||||
|
||||
newTipParents, err := csm.dagTopologyManager.Parents(stagingArea, newTipHash)
|
||||
if err != nil {
|
||||
@@ -151,7 +152,8 @@ func (csm *consensusStateManager) calculateNewTips(
|
||||
newTips = append(newTips, currentTip)
|
||||
}
|
||||
}
|
||||
log.Debugf("The calculated new tips are: %s", newTips)
|
||||
log.Debugf("The new number of tips is: %d", len(newTips))
|
||||
log.Tracef("The new tips are: %s", newTips)
|
||||
|
||||
return newTips, nil
|
||||
}
|
||||
|
||||
@@ -61,8 +61,7 @@ func (csm *consensusStateManager) calculatePastUTXOAndAcceptanceDataWithSelected
|
||||
}
|
||||
|
||||
log.Debugf("Applying blue blocks to the selected parent past UTXO of block %s", blockHash)
|
||||
acceptanceData, utxoDiff, err := csm.applyMergeSetBlocks(
|
||||
stagingArea, blockHash, selectedParentPastUTXO, blockGHOSTDAGData, daaScore)
|
||||
acceptanceData, utxoDiff, err := csm.applyMergeSetBlocks(stagingArea, blockHash, selectedParentPastUTXO, daaScore)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -136,13 +135,16 @@ func (csm *consensusStateManager) restorePastUTXO(
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) applyMergeSetBlocks(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
|
||||
selectedParentPastUTXODiff externalapi.UTXODiff, ghostdagData *model.BlockGHOSTDAGData, daaScore uint64) (
|
||||
selectedParentPastUTXODiff externalapi.UTXODiff, daaScore uint64) (
|
||||
externalapi.AcceptanceData, externalapi.MutableUTXODiff, error) {
|
||||
|
||||
log.Debugf("applyMergeSetBlocks start for block %s", blockHash)
|
||||
defer log.Debugf("applyMergeSetBlocks end for block %s", blockHash)
|
||||
|
||||
mergeSetHashes := ghostdagData.MergeSet()
|
||||
mergeSetHashes, err := csm.ghostdagManager.GetSortedMergeSet(stagingArea, blockHash)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Debugf("Merge set for block %s is %v", blockHash, mergeSetHashes)
|
||||
mergeSetBlocks, err := csm.blockStore.Blocks(csm.databaseContext, stagingArea, mergeSetHashes)
|
||||
if err != nil {
|
||||
@@ -266,8 +268,7 @@ func (csm *consensusStateManager) maybeAcceptTransaction(stagingArea *model.Stag
|
||||
return true, accumulatedMassAfter, nil
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) checkTransactionMass(
|
||||
transaction *externalapi.DomainTransaction, accumulatedMassBefore uint64) (
|
||||
func (csm *consensusStateManager) checkTransactionMass(transaction *externalapi.DomainTransaction, accumulatedMassBefore uint64) (
|
||||
isAccepted bool, accumulatedMassAfter uint64) {
|
||||
|
||||
transactionID := consensushashing.TransactionID(transaction)
|
||||
|
||||
@@ -2,6 +2,7 @@ package consensusstatemanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/math"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
@@ -34,7 +35,16 @@ func (csm *consensusStateManager) pickVirtualParents(stagingArea *model.StagingA
|
||||
}
|
||||
log.Debugf("The selected parent of the virtual is: %s", virtualSelectedParent)
|
||||
|
||||
candidates := candidatesHeap.ToSlice()
|
||||
// Limit to maxBlockParents*3 candidates, that way we don't go over thousands of tips when the network isn't healthy.
|
||||
// There's no specific reason for a factor of 3, and its not a consensus rule, just an estimation saying we probably
|
||||
// don't want to consider and calculate 3 times the amount of candidates for the set of parents.
|
||||
maxCandidates := int(csm.maxBlockParents) * 3
|
||||
candidateAllocationSize := math.MinInt(maxCandidates, candidatesHeap.Len())
|
||||
candidates := make([]*externalapi.DomainHash, 0, candidateAllocationSize)
|
||||
for len(candidates) < maxCandidates && candidatesHeap.Len() > 0 {
|
||||
candidates = append(candidates, candidatesHeap.Pop())
|
||||
}
|
||||
|
||||
// prioritize half the blocks with highest blueWork and half with lowest, so the network will merge splits faster.
|
||||
if len(candidates) >= int(csm.maxBlockParents) {
|
||||
// We already have the selectedParent, so we're left with csm.maxBlockParents-1.
|
||||
@@ -45,12 +55,6 @@ func (csm *consensusStateManager) pickVirtualParents(stagingArea *model.StagingA
|
||||
end--
|
||||
}
|
||||
}
|
||||
// Limit to maxBlockParents*3 candidates, that way we don't go over thousands of tips when the network isn't healthy.
|
||||
// There's no specific reason for a factor of 3, and its not a consensus rule, just an estimation saying we probably
|
||||
// don't want to consider and calculate 3 times the amount of candidates for the set of parents.
|
||||
if len(candidates) > int(csm.maxBlockParents)*3 {
|
||||
candidates = candidates[:int(csm.maxBlockParents)*3]
|
||||
}
|
||||
|
||||
selectedVirtualParents := []*externalapi.DomainHash{virtualSelectedParent}
|
||||
mergeSetSize := uint64(1) // starts counting from 1 because selectedParent is already in the mergeSet
|
||||
|
||||
@@ -2,11 +2,12 @@ package consensusstatemanager_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
|
||||
@@ -157,14 +158,15 @@ func TestDoubleSpends(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestTransactionAcceptance checks that blue blocks transactions are favoured above
|
||||
// red blocks transactions, and that the block reward is paid only for blue blocks.
|
||||
// TestTransactionAcceptance checks that block transactions are accepted correctly when the merge set is sorted topologically.
|
||||
// DAG diagram:
|
||||
// genesis <- blockA <- blockB <- blockC <- ..(chain of k-blocks).. lastBlockInChain <- blockD <- blockE <- blockF
|
||||
// ^ ^ |
|
||||
// | redBlock <------------------------ blueChildOfRedBlock <--------------------
|
||||
func TestTransactionAcceptance(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
stagingArea := model.NewStagingArea()
|
||||
|
||||
consensusConfig.BlockCoinbaseMaturity = 0
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
testConsensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestTransactionAcceptance")
|
||||
if err != nil {
|
||||
@@ -172,221 +174,199 @@ func TestTransactionAcceptance(t *testing.T) {
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
fundingBlock1Hash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
|
||||
blockHashA, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating fundingBlock1: %+v", err)
|
||||
t.Fatalf("Error creating blockA: %+v", err)
|
||||
}
|
||||
|
||||
fundingBlock2Hash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{fundingBlock1Hash}, nil, nil)
|
||||
blockHashB, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashA}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating fundingBlock2: %+v", err)
|
||||
t.Fatalf("Error creating blockB: %+v", err)
|
||||
}
|
||||
|
||||
// Generate fundingBlock3 to pay for fundingBlock2
|
||||
fundingBlock3Hash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{fundingBlock2Hash}, nil, nil)
|
||||
blockHashC, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashB}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating fundingBlock3: %+v", err)
|
||||
t.Fatalf("Error creating blockC: %+v", err)
|
||||
}
|
||||
|
||||
// Add a chain of K blocks above fundingBlock3 so we'll
|
||||
// Add a chain of K blocks above blockC so we'll
|
||||
// be able to mine a red block on top of it.
|
||||
tipHash := fundingBlock3Hash
|
||||
chainTipHash := blockHashC
|
||||
for i := model.KType(0); i < consensusConfig.K; i++ {
|
||||
var err error
|
||||
tipHash, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
|
||||
chainTipHash, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{chainTipHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating fundingBlock1: %+v", err)
|
||||
t.Fatalf("Error creating a block: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
fundingBlock2, err := testConsensus.GetBlock(fundingBlock2Hash)
|
||||
lastBlockInChain := chainTipHash
|
||||
blockC, err := testConsensus.GetBlock(blockHashC)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting fundingBlock: %+v", err)
|
||||
t.Fatalf("Error getting blockC: %+v", err)
|
||||
}
|
||||
|
||||
fundingTransaction1 := fundingBlock2.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
|
||||
fundingBlock3, err := testConsensus.GetBlock(fundingBlock3Hash)
|
||||
fees := uint64(1)
|
||||
transactionFromBlockC := blockC.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
// transactionFromRedBlock is spending TransactionFromBlockC.
|
||||
transactionFromRedBlock, err := testutils.CreateTransaction(transactionFromBlockC, fees)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting fundingBlock: %+v", err)
|
||||
t.Fatalf("Error creating a transactionFromRedBlock: %+v", err)
|
||||
}
|
||||
|
||||
fundingTransaction2 := fundingBlock3.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
|
||||
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction1, 1)
|
||||
transactionFromRedBlockInput0UTXOEntry, err := testConsensus.ConsensusStateStore().
|
||||
UTXOByOutpoint(testConsensus.DatabaseContext(), stagingArea, &transactionFromRedBlock.Inputs[0].PreviousOutpoint)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating spendingTransaction1: %+v", err)
|
||||
t.Fatalf("Error getting UTXOEntry for transactionFromRedBlockInput: %s", err)
|
||||
}
|
||||
spendingTransaction1UTXOEntry, err := testConsensus.ConsensusStateStore().
|
||||
UTXOByOutpoint(testConsensus.DatabaseContext(), stagingArea, &spendingTransaction1.Inputs[0].PreviousOutpoint)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting UTXOEntry for spendingTransaction1: %s", err)
|
||||
}
|
||||
|
||||
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction2, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating spendingTransaction1: %+v", err)
|
||||
}
|
||||
spendingTransaction2UTXOEntry, err := testConsensus.ConsensusStateStore().
|
||||
UTXOByOutpoint(testConsensus.DatabaseContext(), stagingArea, &spendingTransaction2.Inputs[0].PreviousOutpoint)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting UTXOEntry for spendingTransaction2: %s", err)
|
||||
}
|
||||
|
||||
redHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{fundingBlock3Hash}, nil,
|
||||
[]*externalapi.DomainTransaction{spendingTransaction1, spendingTransaction2})
|
||||
redHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashC}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionFromRedBlock})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating redBlock: %+v", err)
|
||||
}
|
||||
|
||||
blueScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{1}, Version: 0}
|
||||
blueHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: blueScriptPublicKey,
|
||||
ExtraData: nil,
|
||||
},
|
||||
[]*externalapi.DomainTransaction{spendingTransaction1})
|
||||
transactionFromBlueChildOfRedBlock, err := testutils.CreateTransaction(transactionFromRedBlock, fees)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blue: %+v", err)
|
||||
t.Fatalf("Error creating transactionFromBlueChildOfRedBlock: %+v", err)
|
||||
}
|
||||
|
||||
// Mining two blocks so tipHash will definitely be the selected tip.
|
||||
tipHash, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
|
||||
transactionFromBlueChildOfRedBlockInput0UTXOEntry, err := testConsensus.ConsensusStateStore().
|
||||
UTXOByOutpoint(testConsensus.DatabaseContext(), stagingArea, &transactionFromBlueChildOfRedBlock.Inputs[0].PreviousOutpoint)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating tip: %+v", err)
|
||||
t.Fatalf("Error getting UTXOEntry for transactionFromBlueChildOfRedBlockInput: %s", err)
|
||||
}
|
||||
|
||||
finalTipSelectedParentScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{3}, Version: 0}
|
||||
finalTipSelectedParentHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{tipHash},
|
||||
blueChildOfRedBlockScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{3}, Version: 0}
|
||||
// The blueChildOfRedBlock contains a transaction that spent an output from the red block.
|
||||
hashBlueChildOfRedBlock, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{lastBlockInChain, redHash},
|
||||
&externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: finalTipSelectedParentScriptPublicKey,
|
||||
ScriptPublicKey: blueChildOfRedBlockScriptPublicKey,
|
||||
ExtraData: nil,
|
||||
}, []*externalapi.DomainTransaction{transactionFromBlueChildOfRedBlock})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blueChildOfRedBlock: %+v", err)
|
||||
}
|
||||
|
||||
// K blocks minded between blockC and blockD.
|
||||
blockHashD, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{lastBlockInChain}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockD : %+v", err)
|
||||
}
|
||||
blockEScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{4}, Version: 0}
|
||||
blockHashE, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashD},
|
||||
&externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: blockEScriptPublicKey,
|
||||
ExtraData: nil,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating tip: %+v", err)
|
||||
t.Fatalf("Error creating blockE: %+v", err)
|
||||
}
|
||||
|
||||
finalTipScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{4}, Version: 0}
|
||||
finalTipHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{finalTipSelectedParentHash, redHash, blueHash},
|
||||
blockFScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{5}, Version: 0}
|
||||
blockHashF, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashE, hashBlueChildOfRedBlock},
|
||||
&externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: finalTipScriptPublicKey,
|
||||
ScriptPublicKey: blockFScriptPublicKey,
|
||||
ExtraData: nil,
|
||||
},
|
||||
nil)
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating finalTip: %+v", err)
|
||||
t.Fatalf("Error creating blockF: %+v", err)
|
||||
}
|
||||
|
||||
acceptanceData, err := testConsensus.AcceptanceDataStore().Get(testConsensus.DatabaseContext(), stagingArea, finalTipHash)
|
||||
acceptanceData, err := testConsensus.AcceptanceDataStore().Get(testConsensus.DatabaseContext(), stagingArea, blockHashF)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting acceptance data: %+v", err)
|
||||
}
|
||||
|
||||
finalTipSelectedParent, err := testConsensus.GetBlock(finalTipSelectedParentHash)
|
||||
blueChildOfRedBlock, err := testConsensus.GetBlock(hashBlueChildOfRedBlock)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting finalTipSelectedParent: %+v", err)
|
||||
t.Fatalf("Error getting blueChildOfRedBlock: %+v", err)
|
||||
}
|
||||
|
||||
blue, err := testConsensus.GetBlock(blueHash)
|
||||
blockE, err := testConsensus.GetBlock(blockHashE)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting blue: %+v", err)
|
||||
t.Fatalf("Error getting blockE: %+v", err)
|
||||
}
|
||||
|
||||
red, err := testConsensus.GetBlock(redHash)
|
||||
redBlock, err := testConsensus.GetBlock(redHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting red: %+v", err)
|
||||
t.Fatalf("Error getting redBlock: %+v", err)
|
||||
}
|
||||
|
||||
// We expect spendingTransaction1 to be accepted by the blue block and not by the red one, because
|
||||
// blue blocks in the merge set should always be ordered before red blocks in the merge set.
|
||||
// We also expect spendingTransaction2 to be accepted by the red because nothing conflicts it.
|
||||
blockF, err := testConsensus.GetBlock(blockHashF)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting blockF: %+v", err)
|
||||
}
|
||||
updatedDAAScoreVirtualBlock := 25
|
||||
//We expect the second transaction in the "blue block" (blueChildOfRedBlock) to be accepted because the merge set is ordered topologically
|
||||
//and the red block is ordered topologically before the "blue block" so the input is known in the UTXOSet.
|
||||
expectedAcceptanceData := externalapi.AcceptanceData{
|
||||
{
|
||||
BlockHash: finalTipSelectedParentHash,
|
||||
BlockHash: blockHashE,
|
||||
TransactionAcceptanceData: []*externalapi.TransactionAcceptanceData{
|
||||
{
|
||||
Transaction: finalTipSelectedParent.Transactions[0],
|
||||
Transaction: blockE.Transactions[0],
|
||||
Fee: 0,
|
||||
IsAccepted: true,
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
BlockHash: blueHash,
|
||||
TransactionAcceptanceData: []*externalapi.TransactionAcceptanceData{
|
||||
{
|
||||
Transaction: blue.Transactions[0],
|
||||
Fee: 0,
|
||||
IsAccepted: false,
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{},
|
||||
},
|
||||
{
|
||||
Transaction: spendingTransaction1,
|
||||
Fee: 1,
|
||||
IsAccepted: true,
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{spendingTransaction1UTXOEntry},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
BlockHash: redHash,
|
||||
TransactionAcceptanceData: []*externalapi.TransactionAcceptanceData{
|
||||
{
|
||||
Transaction: red.Transactions[0],
|
||||
{ //Coinbase transaction outputs are added to the UTXO-set only if they are in the selected parent chain,
|
||||
// and this block isn't.
|
||||
Transaction: redBlock.Transactions[0],
|
||||
Fee: 0,
|
||||
IsAccepted: false,
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{},
|
||||
},
|
||||
{
|
||||
Transaction: spendingTransaction1,
|
||||
Fee: 0,
|
||||
IsAccepted: false,
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{},
|
||||
},
|
||||
{
|
||||
Transaction: spendingTransaction2,
|
||||
Fee: 1,
|
||||
Transaction: redBlock.Transactions[1],
|
||||
Fee: fees,
|
||||
IsAccepted: true,
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{spendingTransaction2UTXOEntry},
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{transactionFromRedBlockInput0UTXOEntry},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
BlockHash: hashBlueChildOfRedBlock,
|
||||
TransactionAcceptanceData: []*externalapi.TransactionAcceptanceData{
|
||||
{ //Coinbase transaction outputs are added to the UTXO-set only if they are in the selected parent chain,
|
||||
// and this block isn't.
|
||||
Transaction: blueChildOfRedBlock.Transactions[0],
|
||||
Fee: 0,
|
||||
IsAccepted: false,
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{},
|
||||
},
|
||||
{ // The DAAScore was calculated by the virtual block pov. The DAAScore has changed since more blocks were added to the DAG.
|
||||
// So we will change the DAAScore in the UTXOEntryInput to the updated virtual DAAScore.
|
||||
Transaction: blueChildOfRedBlock.Transactions[1],
|
||||
Fee: fees,
|
||||
IsAccepted: true,
|
||||
TransactionInputUTXOEntries: []externalapi.UTXOEntry{
|
||||
utxo.NewUTXOEntry(transactionFromBlueChildOfRedBlockInput0UTXOEntry.Amount(),
|
||||
transactionFromBlueChildOfRedBlockInput0UTXOEntry.ScriptPublicKey(),
|
||||
transactionFromBlueChildOfRedBlockInput0UTXOEntry.IsCoinbase(), uint64(updatedDAAScoreVirtualBlock))},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !acceptanceData.Equal(expectedAcceptanceData) {
|
||||
t.Fatalf("The acceptance data is not the expected acceptance data")
|
||||
}
|
||||
|
||||
finalTip, err := testConsensus.GetBlock(finalTipHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting finalTip: %+v", err)
|
||||
}
|
||||
|
||||
// We expect the coinbase transaction to pay reward for the selected parent, the
|
||||
// blue block, and bestow the red block reward to the merging block.
|
||||
// We expect the coinbase transaction to pay reward for the selected parent(block E), the
|
||||
// blueChildOfRedBlock, and bestow the red block reward to the merging block.
|
||||
expectedCoinbase := &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: nil,
|
||||
Outputs: []*externalapi.DomainTransactionOutput{
|
||||
{
|
||||
Value: 50 * constants.SompiPerKaspa,
|
||||
ScriptPublicKey: finalTipSelectedParentScriptPublicKey,
|
||||
ScriptPublicKey: blockEScriptPublicKey,
|
||||
},
|
||||
{
|
||||
Value: 50*constants.SompiPerKaspa + 1, // testutils.CreateTransaction pays a fee of 1 sompi
|
||||
ScriptPublicKey: blueScriptPublicKey,
|
||||
Value: 50*constants.SompiPerKaspa + fees, // testutils.CreateTransaction pays fees
|
||||
ScriptPublicKey: blueChildOfRedBlockScriptPublicKey,
|
||||
},
|
||||
{
|
||||
Value: 50*constants.SompiPerKaspa + 1,
|
||||
ScriptPublicKey: finalTipScriptPublicKey,
|
||||
Value: 50*constants.SompiPerKaspa + fees, // testutils.CreateTransaction pays fees
|
||||
ScriptPublicKey: blockFScriptPublicKey,
|
||||
},
|
||||
},
|
||||
LockTime: 0,
|
||||
SubnetworkID: subnetworks.SubnetworkIDCoinbase,
|
||||
Gas: 0,
|
||||
Payload: finalTip.Transactions[0].Payload,
|
||||
Payload: blockF.Transactions[0].Payload,
|
||||
}
|
||||
if !finalTip.Transactions[transactionhelper.CoinbaseTransactionIndex].Equal(expectedCoinbase) {
|
||||
if !blockF.Transactions[transactionhelper.CoinbaseTransactionIndex].Equal(expectedCoinbase) {
|
||||
t.Fatalf("Unexpected coinbase transaction")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -31,7 +31,7 @@ func (dm *difficultyManager) getDifficultyBlock(
|
||||
}
|
||||
|
||||
// blockWindow returns a blockWindow of the given size that contains the
|
||||
// blocks in the past of startindNode, the sorting is unspecified.
|
||||
// blocks in the past of startingNode, the sorting is unspecified.
|
||||
// If the number of blocks in the past of startingNode is less then windowSize,
|
||||
// the window will be padded by genesis blocks to achieve a size of windowSize.
|
||||
func (dm *difficultyManager) blockWindow(stagingArea *model.StagingArea, startingNode *externalapi.DomainHash, windowSize int) (blockWindow,
|
||||
|
||||
@@ -78,6 +78,9 @@ func (dm *difficultyManager) genesisBits(stagingArea *model.StagingArea) (uint32
|
||||
func (dm *difficultyManager) StageDAADataAndReturnRequiredDifficulty(
|
||||
stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (uint32, error) {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "StageDAADataAndReturnRequiredDifficulty")
|
||||
defer onEnd()
|
||||
|
||||
// Fetch window of dag.difficultyAdjustmentWindowSize + 1 so we can have dag.difficultyAdjustmentWindowSize block intervals
|
||||
targetsWindow, windowHashes, err := dm.blockWindow(stagingArea, blockHash, dm.difficultyAdjustmentWindowSize+1)
|
||||
if err != nil {
|
||||
@@ -164,9 +167,13 @@ func (dm *difficultyManager) calculateDaaScoreAndAddedBlocks(stagingArea *model.
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
mergeSetLength := len(ghostdagData.MergeSetBlues()) + len(ghostdagData.MergeSetReds())
|
||||
mergeSet := make(map[externalapi.DomainHash]struct{}, mergeSetLength)
|
||||
for _, hash := range ghostdagData.MergeSetBlues() {
|
||||
mergeSet[*hash] = struct{}{}
|
||||
}
|
||||
|
||||
mergeSet := make(map[externalapi.DomainHash]struct{}, len(ghostdagData.MergeSet()))
|
||||
for _, hash := range ghostdagData.MergeSet() {
|
||||
for _, hash := range ghostdagData.MergeSetReds() {
|
||||
mergeSet[*hash] = struct{}{}
|
||||
}
|
||||
|
||||
|
||||
74
domain/consensus/processes/difficultymanager/hashrate.go
Normal file
74
domain/consensus/processes/difficultymanager/hashrate.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package difficultymanager
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (dm *difficultyManager) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "EstimateNetworkHashesPerSecond")
|
||||
defer onEnd()
|
||||
|
||||
stagingArea := model.NewStagingArea()
|
||||
return dm.estimateNetworkHashesPerSecond(stagingArea, startHash, windowSize)
|
||||
}
|
||||
|
||||
func (dm *difficultyManager) estimateNetworkHashesPerSecond(stagingArea *model.StagingArea,
|
||||
startHash *externalapi.DomainHash, windowSize int) (uint64, error) {
|
||||
|
||||
const minWindowSize = 1000
|
||||
if windowSize < minWindowSize {
|
||||
return 0, errors.Errorf("windowSize must be equal to or greater than %d", minWindowSize)
|
||||
}
|
||||
|
||||
blockWindow, windowHashes, err := dm.blockWindow(stagingArea, startHash, windowSize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// return 0 if no blocks had been mined yet
|
||||
if len(windowHashes) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
minWindowTimestamp, maxWindowTimestamp, _, _ := blockWindow.minMaxTimestamps()
|
||||
if minWindowTimestamp == maxWindowTimestamp {
|
||||
return 0, errors.Errorf("min window timestamp is equal to the max window timestamp")
|
||||
}
|
||||
|
||||
firstHash := windowHashes[0]
|
||||
firstBlockGHOSTDAGData, err := dm.ghostdagStore.Get(dm.databaseContext, stagingArea, firstHash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
firstBlockBlueWork := firstBlockGHOSTDAGData.BlueWork()
|
||||
minWindowBlueWork := firstBlockBlueWork
|
||||
maxWindowBlueWork := firstBlockBlueWork
|
||||
for _, hash := range windowHashes[1:] {
|
||||
blockGHOSTDAGData, err := dm.ghostdagStore.Get(dm.databaseContext, stagingArea, hash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
blockBlueWork := blockGHOSTDAGData.BlueWork()
|
||||
if blockBlueWork.Cmp(minWindowBlueWork) < 0 {
|
||||
minWindowBlueWork = blockBlueWork
|
||||
}
|
||||
if blockBlueWork.Cmp(maxWindowBlueWork) > 0 {
|
||||
maxWindowBlueWork = blockBlueWork
|
||||
}
|
||||
}
|
||||
|
||||
windowsDiff := (maxWindowTimestamp - minWindowTimestamp) / 1000 // Divided by 1000 to convert milliseconds to seconds
|
||||
if windowsDiff == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
nominator := new(big.Int).Sub(maxWindowBlueWork, minWindowBlueWork)
|
||||
denominator := big.NewInt(windowsDiff)
|
||||
networkHashesPerSecondBigInt := new(big.Int).Div(nominator, denominator)
|
||||
return networkHashesPerSecondBigInt.Uint64(), nil
|
||||
}
|
||||
@@ -409,3 +409,7 @@ func (gh *ghostdagHelper) ChooseSelectedParent(stagingArea *model.StagingArea, b
|
||||
func (gh *ghostdagHelper) Less(blockHashA *externalapi.DomainHash, ghostdagDataA *model.BlockGHOSTDAGData, blockHashB *externalapi.DomainHash, ghostdagDataB *model.BlockGHOSTDAGData) bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (gh *ghostdagHelper) GetSortedMergeSet(*model.StagingArea, *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
@@ -82,3 +82,47 @@ func (gm *ghostdagManager) sortMergeSet(stagingArea *model.StagingArea, mergeSet
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSortedMergeSet return the merge set sorted in a toplogical order.
|
||||
func (gm *ghostdagManager) GetSortedMergeSet(stagingArea *model.StagingArea,
|
||||
current *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
|
||||
currentGhostdagData, err := gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, current)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blueMergeSet := currentGhostdagData.MergeSetBlues()
|
||||
redMergeSet := currentGhostdagData.MergeSetReds()
|
||||
sortedMergeSet := make([]*externalapi.DomainHash, 0, len(blueMergeSet)+len(redMergeSet))
|
||||
// If the current block is the genesis block:
|
||||
if len(blueMergeSet) == 0 {
|
||||
return sortedMergeSet, nil
|
||||
}
|
||||
selectedParent, blueMergeSet := blueMergeSet[0], blueMergeSet[1:]
|
||||
sortedMergeSet = append(sortedMergeSet, selectedParent)
|
||||
i, j := 0, 0
|
||||
for i < len(blueMergeSet) && j < len(redMergeSet) {
|
||||
currentBlue := blueMergeSet[i]
|
||||
currentBlueGhostdagData, err := gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, currentBlue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentRed := redMergeSet[j]
|
||||
currentRedGhostdagData, err := gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, currentRed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if gm.Less(currentBlue, currentBlueGhostdagData, currentRed, currentRedGhostdagData) {
|
||||
sortedMergeSet = append(sortedMergeSet, currentBlue)
|
||||
i++
|
||||
} else {
|
||||
sortedMergeSet = append(sortedMergeSet, currentRed)
|
||||
j++
|
||||
}
|
||||
}
|
||||
sortedMergeSet = append(sortedMergeSet, blueMergeSet[i:]...)
|
||||
sortedMergeSet = append(sortedMergeSet, redMergeSet[j:]...)
|
||||
|
||||
return sortedMergeSet, nil
|
||||
}
|
||||
|
||||
@@ -7,19 +7,11 @@ import (
|
||||
)
|
||||
|
||||
// antiPastHashesBetween returns the hashes of the blocks between the
|
||||
// lowHash's antiPast and highHash's antiPast, or up to `maxBlocks`, if non-zero.
|
||||
// lowHash's antiPast and highHash's antiPast, or up to
|
||||
// `maxBlueScoreDifference`, if non-zero.
|
||||
// The result excludes lowHash and includes highHash. If lowHash == highHash, returns nothing.
|
||||
// If maxBlocks != 0 then maxBlocks MUST be >= MergeSetSizeLimit + 1
|
||||
// because it returns blocks with MergeSet granularity,
|
||||
// so if MergeSet > maxBlocks, function will return nothing
|
||||
func (sm *syncManager) antiPastHashesBetween(stagingArea *model.StagingArea, lowHash, highHash *externalapi.DomainHash,
|
||||
maxBlocks uint64) (hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) {
|
||||
|
||||
// Sanity check, for debugging only
|
||||
if maxBlocks != 0 && maxBlocks < sm.mergeSetSizeLimit+1 {
|
||||
return nil, nil,
|
||||
errors.Errorf("maxBlocks (%d) MUST be >= MergeSetSizeLimit + 1 (%d)", maxBlocks, sm.mergeSetSizeLimit+1)
|
||||
}
|
||||
maxBlueScoreDifference uint64) (hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) {
|
||||
|
||||
// If lowHash is not in the selectedParentChain of highHash - SelectedChildIterator will fail.
|
||||
// Therefore, we traverse down lowHash's selectedParentChain until we reach a block that is in
|
||||
@@ -44,6 +36,23 @@ func (sm *syncManager) antiPastHashesBetween(stagingArea *model.StagingArea, low
|
||||
lowBlockGHOSTDAGData.BlueScore(), highBlockGHOSTDAGData.BlueScore())
|
||||
}
|
||||
|
||||
if maxBlueScoreDifference != 0 {
|
||||
// In order to get no more then maxBlueScoreDifference
|
||||
// blocks from the future of the lowHash (including itself),
|
||||
// we iterate the selected parent chain of the highNode and
|
||||
// stop once we reach
|
||||
// highBlockBlueScore-lowBlockBlueScore+1 <= maxBlueScoreDifference.
|
||||
// That stop point becomes the new highHash.
|
||||
// Using blueScore as an approximation is considered to be
|
||||
// fairly accurate because we presume that most DAG blocks are
|
||||
// blue.
|
||||
highHash, err = sm.findHighHashAccordingToMaxBlueScoreDifference(stagingArea,
|
||||
lowHash, highHash, maxBlueScoreDifference, highBlockGHOSTDAGData, lowBlockGHOSTDAGData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all hashes by concatenating the merge-sets of all blocks between highHash and lowHash
|
||||
blockHashes := []*externalapi.DomainHash{}
|
||||
iterator, err := sm.dagTraversalManager.SelectedChildIterator(stagingArea, highHash, lowHash)
|
||||
@@ -62,17 +71,11 @@ func (sm *syncManager) antiPastHashesBetween(stagingArea *model.StagingArea, low
|
||||
// Since the rest of the merge set is in the anticone of selectedParent, it's position in the list does not
|
||||
// matter, even though it's blue score is the highest, we can arbitrarily decide it comes first.
|
||||
// Therefore we first append the selectedParent, then the rest of blocks in ghostdag order.
|
||||
sortedMergeSet, err := sm.getSortedMergeSet(stagingArea, current)
|
||||
sortedMergeSet, err := sm.ghostdagManager.GetSortedMergeSet(stagingArea, current)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if maxBlocks != 0 && uint64(len(blockHashes)+len(sortedMergeSet)) > maxBlocks {
|
||||
break
|
||||
}
|
||||
|
||||
highHash = current
|
||||
|
||||
// append to blockHashes all blocks in sortedMergeSet which are not in the past of originalLowHash
|
||||
for _, blockHash := range sortedMergeSet {
|
||||
isInPastOfOriginalLowHash, err := sm.dagTopologyManager.IsAncestorOf(stagingArea, blockHash, originalLowHash)
|
||||
@@ -94,43 +97,34 @@ func (sm *syncManager) antiPastHashesBetween(stagingArea *model.StagingArea, low
|
||||
return blockHashes, highHash, nil
|
||||
}
|
||||
|
||||
func (sm *syncManager) getSortedMergeSet(stagingArea *model.StagingArea, current *externalapi.DomainHash) (
|
||||
[]*externalapi.DomainHash, error) {
|
||||
func (sm *syncManager) findHighHashAccordingToMaxBlueScoreDifference(stagingArea *model.StagingArea, lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash, maxBlueScoreDifference uint64, highBlockGHOSTDAGData *model.BlockGHOSTDAGData,
|
||||
lowBlockGHOSTDAGData *model.BlockGHOSTDAGData) (*externalapi.DomainHash, error) {
|
||||
|
||||
currentGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, current)
|
||||
if highBlockGHOSTDAGData.BlueScore()-lowBlockGHOSTDAGData.BlueScore() <= maxBlueScoreDifference {
|
||||
return highHash, nil
|
||||
}
|
||||
|
||||
iterator, err := sm.dagTraversalManager.SelectedChildIterator(stagingArea, highHash, lowHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blueMergeSet := currentGhostdagData.MergeSetBlues()
|
||||
redMergeSet := currentGhostdagData.MergeSetReds()
|
||||
sortedMergeSet := make([]*externalapi.DomainHash, 0, len(blueMergeSet)+len(redMergeSet))
|
||||
selectedParent, blueMergeSet := blueMergeSet[0], blueMergeSet[1:]
|
||||
sortedMergeSet = append(sortedMergeSet, selectedParent)
|
||||
i, j := 0, 0
|
||||
for i < len(blueMergeSet) && j < len(redMergeSet) {
|
||||
currentBlue := blueMergeSet[i]
|
||||
currentBlueGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, currentBlue)
|
||||
defer iterator.Close()
|
||||
for ok := iterator.First(); ok; ok = iterator.Next() {
|
||||
highHashCandidate, err := iterator.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentRed := redMergeSet[j]
|
||||
currentRedGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, currentRed)
|
||||
highBlockGHOSTDAGData, err = sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, highHashCandidate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sm.ghostdagManager.Less(currentBlue, currentBlueGhostdagData, currentRed, currentRedGhostdagData) {
|
||||
sortedMergeSet = append(sortedMergeSet, currentBlue)
|
||||
i++
|
||||
} else {
|
||||
sortedMergeSet = append(sortedMergeSet, currentRed)
|
||||
j++
|
||||
if highBlockGHOSTDAGData.BlueScore()-lowBlockGHOSTDAGData.BlueScore() > maxBlueScoreDifference {
|
||||
break
|
||||
}
|
||||
highHash = highHashCandidate
|
||||
}
|
||||
sortedMergeSet = append(sortedMergeSet, blueMergeSet[i:]...)
|
||||
sortedMergeSet = append(sortedMergeSet, redMergeSet[j:]...)
|
||||
|
||||
return sortedMergeSet, nil
|
||||
return highHash, nil
|
||||
}
|
||||
|
||||
func (sm *syncManager) findLowHashInHighHashSelectedParentChain(stagingArea *model.StagingArea,
|
||||
|
||||
@@ -21,15 +21,12 @@ type syncManager struct {
|
||||
blockStore model.BlockStore
|
||||
pruningStore model.PruningStore
|
||||
headersSelectedChainStore model.HeadersSelectedChainStore
|
||||
|
||||
mergeSetSizeLimit uint64
|
||||
}
|
||||
|
||||
// New instantiates a new SyncManager
|
||||
func New(
|
||||
databaseContext model.DBReader,
|
||||
genesisBlockHash *externalapi.DomainHash,
|
||||
mergeSetSizeLimit uint64,
|
||||
dagTraversalManager model.DAGTraversalManager,
|
||||
dagTopologyManager model.DAGTopologyManager,
|
||||
ghostdagManager model.GHOSTDAGManager,
|
||||
@@ -61,12 +58,12 @@ func New(
|
||||
}
|
||||
|
||||
func (sm *syncManager) GetHashesBetween(stagingArea *model.StagingArea, lowHash, highHash *externalapi.DomainHash,
|
||||
maxBlocks uint64) (hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) {
|
||||
maxBlueScoreDifference uint64) (hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "GetHashesBetween")
|
||||
defer onEnd()
|
||||
|
||||
return sm.antiPastHashesBetween(stagingArea, lowHash, highHash, maxBlocks)
|
||||
return sm.antiPastHashesBetween(stagingArea, lowHash, highHash, maxBlueScoreDifference)
|
||||
}
|
||||
|
||||
func (sm *syncManager) GetMissingBlockBodyHashes(stagingArea *model.StagingArea, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
|
||||
424
domain/consensus/timelock_CSV_test.go
Normal file
424
domain/consensus/timelock_CSV_test.go
Normal file
@@ -0,0 +1,424 @@
|
||||
package consensus_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||
"github.com/pkg/errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestCheckSequenceVerifyConditionedByBlockHeight verifies that locked output (by CSV script) is spendable
|
||||
// only after a certain number of blocks have been added relative to the time the UTXO was mined.
|
||||
// CSV - check sequence verify.
|
||||
func TestCheckSequenceVerifyConditionedByBlockHeight(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
consensusConfig.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
testConsensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckSequenceVerifyConditionedByBlockHeight")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
blockAHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{testConsensus.DAGParams().GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockA: %v", err)
|
||||
}
|
||||
blockBHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockAHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockB: %v", err)
|
||||
}
|
||||
blockCHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockBHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockC: %v", err)
|
||||
}
|
||||
blockC, err := testConsensus.GetBlock(blockCHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed getting blockC: %v", err)
|
||||
}
|
||||
fees := uint64(1)
|
||||
fundingTransaction, err := testutils.CreateTransaction(blockC.Transactions[transactionhelper.CoinbaseTransactionIndex], fees)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating foundingTransaction: %v", err)
|
||||
}
|
||||
blockDHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockCHash}, nil,
|
||||
[]*externalapi.DomainTransaction{fundingTransaction})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed creating blockD: %v", err)
|
||||
}
|
||||
//create a CSV script
|
||||
numOfBlocksToWait := int64(10)
|
||||
if numOfBlocksToWait > 0xffff {
|
||||
t.Fatalf("More than the maximum number of blocks allowed.")
|
||||
}
|
||||
redeemScriptCSV, err := createCheckSequenceVerifyScript(numOfBlocksToWait)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a script using createCheckSequenceVerifyScript: %v", err)
|
||||
}
|
||||
p2shScriptCSV, err := txscript.PayToScriptHashScript(redeemScriptCSV)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a pay-to-script-hash script : %v", err)
|
||||
}
|
||||
scriptPublicKeyCSV := externalapi.ScriptPublicKey{
|
||||
Version: constants.MaxScriptPublicKeyVersion,
|
||||
Script: p2shScriptCSV,
|
||||
}
|
||||
transactionWithLockedOutput, err := createTransactionWithLockedOutput(fundingTransaction, fees, &scriptPublicKeyCSV)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in createTransactionWithLockedOutput: %v", err)
|
||||
}
|
||||
// BlockE contains the locked output (locked by CSV).
|
||||
// This block should be valid since CSV script locked only the output.
|
||||
blockEHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockDHash}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionWithLockedOutput})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockE: %v", err)
|
||||
}
|
||||
// The 23-bit of sequence defines if it's conditioned by block height(set to 0) or by time (set to 1).
|
||||
sequenceFlag := 0
|
||||
// Create a transaction that tries to spend the locked output.
|
||||
transactionThatSpentTheLockedOutput, err := createTransactionThatSpentTheLockedOutput(transactionWithLockedOutput,
|
||||
fees, redeemScriptCSV, uint64(numOfBlocksToWait), sequenceFlag, blockEHash, &testConsensus)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating transactionThatSpentTheLockedOutput: %v", err)
|
||||
}
|
||||
// Add a block that contains a transaction that spends the locked output before the time, and therefore should be failed.
|
||||
_, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{blockEHash}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionThatSpentTheLockedOutput})
|
||||
if err == nil || !errors.Is(err, ruleerrors.ErrUnfinalizedTx) {
|
||||
t.Fatalf("Expected block to be invalid with err: %v, instead found: %v", ruleerrors.ErrUnfinalizedTx, err)
|
||||
}
|
||||
//Add x blocks to release the locked output, where x = 'numOfBlocksToWait'.
|
||||
tipHash := blockEHash
|
||||
for i := int64(0); i < numOfBlocksToWait; i++ {
|
||||
tipHash, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating tip: %v", err)
|
||||
}
|
||||
}
|
||||
// Tries to spend the output that should be no longer locked.
|
||||
_, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionThatSpentTheLockedOutput})
|
||||
if err != nil {
|
||||
t.Fatalf("The block should be valid since the output is not locked anymore. but got an error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestCheckSequenceVerifyConditionedByRelativeTime verifies that locked output (by CSV script) is spendable only after
|
||||
// the time is reached to the set target relative to the time the UTXO was mined (compared to the past median time).
|
||||
func TestCheckSequenceVerifyConditionedByRelativeTime(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
consensusConfig.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
testConsensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestCheckSequenceVerifyConditionedByRelativeTime")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
blockAHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{testConsensus.DAGParams().GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockA: %v", err)
|
||||
}
|
||||
blockBHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockAHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockB: %v", err)
|
||||
}
|
||||
blockCHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockBHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockC: %v", err)
|
||||
}
|
||||
blockC, err := testConsensus.GetBlock(blockCHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed getting blockC: %v", err)
|
||||
}
|
||||
fees := uint64(1)
|
||||
fundingTransaction, err := testutils.CreateTransaction(blockC.Transactions[transactionhelper.CoinbaseTransactionIndex], fees)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating foundingTransaction: %v", err)
|
||||
}
|
||||
blockDHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockCHash}, nil,
|
||||
[]*externalapi.DomainTransaction{fundingTransaction})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed creating blockD: %v", err)
|
||||
}
|
||||
//create a CSV script
|
||||
timeToWait := int64(14 * 1000)
|
||||
if timeToWait > 0xffff {
|
||||
t.Fatalf("More than the allowed time to set.")
|
||||
}
|
||||
redeemScriptCSV, err := createCheckSequenceVerifyScript(timeToWait)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a script using createCheckSequenceVerifyScript: %v", err)
|
||||
}
|
||||
p2shScriptCSV, err := txscript.PayToScriptHashScript(redeemScriptCSV)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a pay-to-script-hash script : %v", err)
|
||||
}
|
||||
scriptPublicKeyCSV := externalapi.ScriptPublicKey{
|
||||
Version: constants.MaxScriptPublicKeyVersion,
|
||||
Script: p2shScriptCSV,
|
||||
}
|
||||
transactionWithLockedOutput, err := createTransactionWithLockedOutput(fundingTransaction, fees, &scriptPublicKeyCSV)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in createTransactionWithLockedOutput: %v", err)
|
||||
}
|
||||
// BlockE contains the locked output (locked by CSV).
|
||||
// This block should be valid since CSV script locked only the output.
|
||||
blockEHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockDHash}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionWithLockedOutput})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockE: %v", err)
|
||||
}
|
||||
// The 23-bit of sequence defines if it's conditioned by block height(set to 0) or by time (set to 1).
|
||||
sequenceFlag := 1
|
||||
// Create a transaction that tries to spend the locked output.
|
||||
transactionThatSpentTheLockedOutput, err := createTransactionThatSpentTheLockedOutput(transactionWithLockedOutput,
|
||||
fees, redeemScriptCSV, uint64(timeToWait), sequenceFlag, blockEHash, &testConsensus)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating transactionThatSpentTheLockedOutput: %v", err)
|
||||
}
|
||||
// Add a block that contains a transaction that spends the locked output before the time, and therefore should be failed.
|
||||
_, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{blockEHash}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionThatSpentTheLockedOutput})
|
||||
if err == nil || !errors.Is(err, ruleerrors.ErrUnfinalizedTx) {
|
||||
t.Fatalf("Expected block to be invalid with err: %v, instead found: %v", ruleerrors.ErrUnfinalizedTx, err)
|
||||
}
|
||||
emptyCoinbase := externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}
|
||||
var tipHash *externalapi.DomainHash
|
||||
blockE, err := testConsensus.GetBlock(blockEHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get blockE: %v", err)
|
||||
}
|
||||
timeStampBlockE := blockE.Header.TimeInMilliseconds()
|
||||
stagingArea := model.NewStagingArea()
|
||||
// Make sure the time limitation has passed.
|
||||
lockTimeTarget := blockE.Header.TimeInMilliseconds() + timeToWait
|
||||
for i := int64(0); ; i++ {
|
||||
tipBlock, err := testConsensus.BuildBlock(&emptyCoinbase, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating tip using BuildBlock: %v", err)
|
||||
}
|
||||
blockHeader := tipBlock.Header.ToMutable()
|
||||
blockHeader.SetTimeInMilliseconds(timeStampBlockE + i*1000)
|
||||
tipBlock.Header = blockHeader.ToImmutable()
|
||||
_, err = testConsensus.ValidateAndInsertBlock(tipBlock)
|
||||
if err != nil {
|
||||
t.Fatalf("Error validating and inserting tip block: %v", err)
|
||||
}
|
||||
tipHash = consensushashing.BlockHash(tipBlock)
|
||||
pastMedianTime, err := testConsensus.PastMedianTimeManager().PastMedianTime(stagingArea, tipHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed getting pastMedianTime: %v", err)
|
||||
}
|
||||
if pastMedianTime > lockTimeTarget {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Tries to spend the output that should be no longer locked
|
||||
_, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionThatSpentTheLockedOutput})
|
||||
if err != nil {
|
||||
t.Fatalf("The block should be valid since the output is not locked anymore. but got an error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//TestRelativeTimeOnCheckSequenceVerify verifies that if the relative target is set to X blocks to wait, and the absolute height
|
||||
// will be X before adding all the blocks, then the output will remain locked.
|
||||
func TestRelativeTimeOnCheckSequenceVerify(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
consensusConfig.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
testConsensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestRelativeTimeOnCheckSequenceVerify")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
currentNumOfBlocks := int64(0)
|
||||
blockAHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{testConsensus.DAGParams().GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockA: %v", err)
|
||||
}
|
||||
currentNumOfBlocks++
|
||||
blockBHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockAHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockB: %v", err)
|
||||
}
|
||||
currentNumOfBlocks++
|
||||
blockCHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockBHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockC: %v", err)
|
||||
}
|
||||
currentNumOfBlocks++
|
||||
blockC, err := testConsensus.GetBlock(blockCHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed getting blockC: %v", err)
|
||||
}
|
||||
fees := uint64(1)
|
||||
fundingTransaction, err := testutils.CreateTransaction(blockC.Transactions[transactionhelper.CoinbaseTransactionIndex], fees)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating foundingTransaction: %v", err)
|
||||
}
|
||||
blockDHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockCHash}, nil,
|
||||
[]*externalapi.DomainTransaction{fundingTransaction})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed creating blockD: %v", err)
|
||||
}
|
||||
currentNumOfBlocks++
|
||||
//create a CSV script
|
||||
numOfBlocksToWait := int64(10)
|
||||
if numOfBlocksToWait > 0xffff {
|
||||
t.Fatalf("More than the max number of blocks that allowed to set.")
|
||||
}
|
||||
redeemScriptCSV, err := createCheckSequenceVerifyScript(numOfBlocksToWait)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a script using createCheckSequenceVerifyScript: %v", err)
|
||||
}
|
||||
p2shScriptCSV, err := txscript.PayToScriptHashScript(redeemScriptCSV)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a pay-to-script-hash script : %v", err)
|
||||
}
|
||||
scriptPublicKeyCSV := externalapi.ScriptPublicKey{
|
||||
Version: constants.MaxScriptPublicKeyVersion,
|
||||
Script: p2shScriptCSV,
|
||||
}
|
||||
transactionWithLockedOutput, err := createTransactionWithLockedOutput(fundingTransaction, fees, &scriptPublicKeyCSV)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in createTransactionWithLockedOutput: %v", err)
|
||||
}
|
||||
// BlockE contains the locked output (locked by CSV).
|
||||
// This block should be valid since CSV script locked only the output.
|
||||
blockEHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockDHash}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionWithLockedOutput})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating blockE: %v", err)
|
||||
}
|
||||
currentNumOfBlocks++
|
||||
// The 23-bit of sequence defines if it's conditioned by block height(set to 0) or by time (set to 1).
|
||||
sequenceFlag := 0
|
||||
// Create a transaction that tries to spend the locked output.
|
||||
transactionThatSpentTheLockedOutput, err := createTransactionThatSpentTheLockedOutput(transactionWithLockedOutput,
|
||||
fees, redeemScriptCSV, uint64(numOfBlocksToWait), sequenceFlag, blockEHash, &testConsensus)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating transactionThatSpentTheLockedOutput: %v", err)
|
||||
}
|
||||
// Mines blocks so the block height will be the same as the relative number(but not enough to reach the relative target)
|
||||
// and verify that the output is still locked.
|
||||
// For unlocked the output the blocks should be count from the block that contains the locked output and not as an absolute height.
|
||||
tipHash := blockEHash
|
||||
for currentNumOfBlocks == numOfBlocksToWait {
|
||||
tipHash, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating tip: %v", err)
|
||||
}
|
||||
currentNumOfBlocks++
|
||||
}
|
||||
_, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil,
|
||||
[]*externalapi.DomainTransaction{transactionThatSpentTheLockedOutput})
|
||||
if err == nil || !errors.Is(err, ruleerrors.ErrUnfinalizedTx) {
|
||||
t.Fatalf("Expected block to be invalid with err: %v, instead found: %v", ruleerrors.ErrUnfinalizedTx, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func createCheckSequenceVerifyScript(numOfBlocks int64) ([]byte, error) {
|
||||
scriptBuilder := txscript.NewScriptBuilder()
|
||||
scriptBuilder.AddOp(txscript.OpCheckSequenceVerify)
|
||||
scriptBuilder.AddInt64(numOfBlocks)
|
||||
scriptBuilder.AddOp(txscript.OpTrue)
|
||||
return scriptBuilder.Script()
|
||||
}
|
||||
|
||||
func createTransactionWithLockedOutput(txToSpend *externalapi.DomainTransaction, fee uint64,
|
||||
scriptPublicKeyCSV *externalapi.ScriptPublicKey) (*externalapi.DomainTransaction, error) {
|
||||
|
||||
_, redeemScript := testutils.OpTrueScript()
|
||||
signatureScript, err := txscript.PayToScriptHashSignatureScript(redeemScript, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input := &externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(txToSpend),
|
||||
Index: 0,
|
||||
},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
output := &externalapi.DomainTransactionOutput{
|
||||
ScriptPublicKey: scriptPublicKeyCSV,
|
||||
Value: txToSpend.Outputs[0].Value - fee,
|
||||
}
|
||||
return &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{input},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{output},
|
||||
Payload: []byte{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createTransactionThatSpentTheLockedOutput(txToSpend *externalapi.DomainTransaction, fee uint64,
|
||||
redeemScript []byte, lockTime uint64, sequenceFlag23Bit int, lockedOutputBlockHash *externalapi.DomainHash,
|
||||
testConsensus *testapi.TestConsensus) (*externalapi.DomainTransaction, error) {
|
||||
|
||||
// the 31bit is off since its relative timelock.
|
||||
sequence := uint64(0)
|
||||
sequence |= lockTime
|
||||
// conditioned by absolute time:
|
||||
if sequenceFlag23Bit == 1 {
|
||||
sequence |= 1 << 23
|
||||
lockedOutputBlock, err := (*testConsensus).GetBlock(lockedOutputBlockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lockTime += uint64(lockedOutputBlock.Header.TimeInMilliseconds())
|
||||
} else {
|
||||
// conditioned by block height:
|
||||
blockDAAScore, err := (*testConsensus).DAABlocksStore().DAAScore((*testConsensus).DatabaseContext(),
|
||||
model.NewStagingArea(), lockedOutputBlockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lockTime += blockDAAScore
|
||||
}
|
||||
signatureScript, err := txscript.PayToScriptHashSignatureScript(redeemScript, []byte{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scriptPublicKeyOutput, _ := testutils.OpTrueScript()
|
||||
input := &externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(txToSpend),
|
||||
Index: 0,
|
||||
},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: sequence,
|
||||
}
|
||||
output := &externalapi.DomainTransactionOutput{
|
||||
ScriptPublicKey: scriptPublicKeyOutput,
|
||||
Value: txToSpend.Outputs[0].Value - fee,
|
||||
}
|
||||
return &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{input},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{output},
|
||||
Payload: []byte{},
|
||||
LockTime: lockTime,
|
||||
}, nil
|
||||
}
|
||||
@@ -406,34 +406,49 @@ func (mp *mempool) removeTransactionsFromPool(txs []*consensusexternalapi.Domain
|
||||
return nil
|
||||
}
|
||||
|
||||
type transactionAndOutpoint struct {
|
||||
transaction *consensusexternalapi.DomainTransaction
|
||||
outpoint *consensusexternalapi.DomainOutpoint
|
||||
}
|
||||
|
||||
// removeTransactionAndItsChainedTransactions removes a transaction and all of its chained transaction from the mempool.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for writes).
|
||||
func (mp *mempool) removeTransactionAndItsChainedTransactions(tx *consensusexternalapi.DomainTransaction) error {
|
||||
txID := consensushashing.TransactionID(tx)
|
||||
func (mp *mempool) removeTransactionAndItsChainedTransactions(transaction *consensusexternalapi.DomainTransaction) error {
|
||||
transactionAndOutpointQueue := make([]*transactionAndOutpoint, 0)
|
||||
transactionAndOutpointQueue = appendTransactionToTransactionAndOutpointQueue(transactionAndOutpointQueue, transaction)
|
||||
// Remove any transactions which rely on this one.
|
||||
for i := uint32(0); i < uint32(len(tx.Outputs)); i++ {
|
||||
prevOut := consensusexternalapi.DomainOutpoint{TransactionID: *txID, Index: i}
|
||||
if txRedeemer, exists := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(prevOut); exists {
|
||||
err := mp.removeTransactionAndItsChainedTransactions(txRedeemer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for len(transactionAndOutpointQueue) > 0 {
|
||||
txAndOutpoint := transactionAndOutpointQueue[0]
|
||||
transactionAndOutpointQueue = transactionAndOutpointQueue[1:]
|
||||
if txRedeemer, exists := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(*txAndOutpoint.outpoint); exists {
|
||||
transactionAndOutpointQueue = appendTransactionToTransactionAndOutpointQueue(transactionAndOutpointQueue, txRedeemer)
|
||||
}
|
||||
|
||||
transactionID := txAndOutpoint.outpoint.TransactionID
|
||||
if _, exists := mp.chainedTransactions[transactionID]; exists {
|
||||
mp.removeChainTransaction(txAndOutpoint.transaction)
|
||||
}
|
||||
err := mp.cleanTransactionFromSets(txAndOutpoint.transaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, exists := mp.chainedTransactions[*txID]; exists {
|
||||
mp.removeChainTransaction(tx)
|
||||
}
|
||||
|
||||
err := mp.cleanTransactionFromSets(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendTransactionToTransactionAndOutpointQueue(queue []*transactionAndOutpoint,
|
||||
transaction *consensusexternalapi.DomainTransaction) []*transactionAndOutpoint {
|
||||
|
||||
transactionID := consensushashing.TransactionID(transaction)
|
||||
queueWithAddedTransactionAndOutpoint := queue
|
||||
for i := uint32(0); i < uint32(len(transaction.Outputs)); i++ {
|
||||
previousOutpoint := consensusexternalapi.DomainOutpoint{TransactionID: *transactionID, Index: i}
|
||||
queueWithAddedTransactionAndOutpoint = append(queueWithAddedTransactionAndOutpoint,
|
||||
&transactionAndOutpoint{transaction: transaction, outpoint: &previousOutpoint})
|
||||
}
|
||||
return queueWithAddedTransactionAndOutpoint
|
||||
}
|
||||
|
||||
// cleanTransactionFromSets removes the transaction from all mempool related transaction sets.
|
||||
// It assumes that any chained transaction is already cleaned from the mempool.
|
||||
//
|
||||
|
||||
28
go.mod
28
go.mod
@@ -3,18 +3,34 @@ module github.com/kaspanet/kaspad
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/aokoli/goutils v1.1.1 // indirect
|
||||
github.com/btcsuite/btcutil v1.0.2
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd
|
||||
github.com/btcsuite/winsvc v1.0.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/jrick/logrotate v1.0.0
|
||||
github.com/kaspanet/go-muhash v0.0.4
|
||||
github.com/kaspanet/go-secp256k1 v0.0.5
|
||||
github.com/kaspanet/go-secp256k1 v0.0.7
|
||||
github.com/kaspanet/protoc-gen-doc v1.4.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mwitkow/go-proto-validators v0.3.2 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
||||
google.golang.org/grpc v1.33.1
|
||||
google.golang.org/protobuf v1.25.0
|
||||
github.com/tyler-smith/go-bip39 v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea // indirect
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 // indirect
|
||||
google.golang.org/grpc v1.38.0
|
||||
google.golang.org/protobuf v1.26.0
|
||||
)
|
||||
|
||||
188
go.sum
188
go.sum
@@ -1,21 +1,69 @@
|
||||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
||||
github.com/aokoli/goutils v1.1.1 h1:/hA+Ywo3AxoDZY5ZMnkiEkUvkK4BPp927ax110KCqqg=
|
||||
github.com/aokoli/goutils v1.1.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
|
||||
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 h1:q1oJaUPdmpDm/VyXosjgPgr6wS7c5iV2p0PwJD73bUI=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.1 h1:4CF52PCseTFt4bE+Yk3dIpdVi7XWuPVMhPtm4FaIJPM=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.1/go.mod h1:txg5va2Qkip90uYoSKH+nkAAmXrb2j3iq4FLwdrCbXQ=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -26,86 +74,199 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7 h1:ux/56T2xqZO/3cP1I2F86qpeoYPCOzk+KF/UH/Ar+lk=
|
||||
github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||
github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kaspanet/go-muhash v0.0.4 h1:CQrm1RTJpQy+h4ZFjj9qq42K5fmA5QTGifzb47p4qWk=
|
||||
github.com/kaspanet/go-muhash v0.0.4/go.mod h1:10bPW5mO1vNHPSejaAh9ZTtLZE16jzEvgaP7f3Q5s/8=
|
||||
github.com/kaspanet/go-secp256k1 v0.0.5 h1:WQqb65tyr8amsBkj337BVH3PTVWCrmufb68aTGpK3mM=
|
||||
github.com/kaspanet/go-secp256k1 v0.0.5/go.mod h1:cFbxhxKkxqHX5eIwUGKARkph19PehipDPJejWB+H0jM=
|
||||
github.com/kaspanet/go-secp256k1 v0.0.7 h1:WHnrwopKB6ZeHSbdAwwxNhTqflm56XT1mM6LF4/OvOs=
|
||||
github.com/kaspanet/go-secp256k1 v0.0.7/go.mod h1:cFbxhxKkxqHX5eIwUGKARkph19PehipDPJejWB+H0jM=
|
||||
github.com/kaspanet/protoc-gen-doc v1.4.0 h1:d2U/0F9b8AVP6mCymHaHn3Ry+Zc8LXV0UGpfj+WS/w0=
|
||||
github.com/kaspanet/protoc-gen-doc v1.4.0/go.mod h1:0kD54MmtdrGv/V/z+24+WN3LS607NTvPbraKoxIMDPM=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/lyft/protoc-gen-star v0.5.1 h1:sImehRT+p7lW9n6R7MQc5hVgzWGEkDVZU4AsBQ4Isu8=
|
||||
github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=
|
||||
github.com/mwitkow/go-proto-validators v0.3.2 h1:qRlmpTzm2pstMKKzTdvwPCF5QfBNURSlAgN/R+qbKos=
|
||||
github.com/mwitkow/go-proto-validators v0.3.2/go.mod h1:ej0Qp0qMgHN/KtDyUt+Q1/tA7a5VarXUOUxD+oeD30w=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/pseudomuto/protokit v0.2.0 h1:hlnBDcy3YEDXH7kc9gV+NLaN0cDzhDvD1s7Y6FZ8RpM=
|
||||
github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.3.4 h1:8q6vk3hthlpb2SouZcnBVKboxWQWMDNF38bwholZrJc=
|
||||
github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
|
||||
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea h1:+WiDlPBBaO+h9vPNZi8uJ3k4BkKQB7Iow3aqwHVA5hI=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 h1:pc16UedxnxXXtGxHCSUhafAoVHQZ0yXl8ZelMH4EETc=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -114,8 +275,11 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
@@ -123,5 +287,11 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user