mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-03-17 13:54:56 +00:00
Compare commits
3 Commits
bignet-deb
...
v0.3.1-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c88869778d | ||
|
|
3fd647b291 | ||
|
|
2f255952b7 |
@@ -40,8 +40,10 @@ recommended that `GOPATH` is set to a directory in your home directory such as
|
|||||||
```bash
|
```bash
|
||||||
$ git clone https://github.com/kaspanet/kaspad $GOPATH/src/github.com/kaspanet/kaspad
|
$ git clone https://github.com/kaspanet/kaspad $GOPATH/src/github.com/kaspanet/kaspad
|
||||||
$ cd $GOPATH/src/github.com/kaspanet/kaspad
|
$ cd $GOPATH/src/github.com/kaspanet/kaspad
|
||||||
|
$ ./test.sh
|
||||||
$ go install . ./cmd/...
|
$ go install . ./cmd/...
|
||||||
```
|
```
|
||||||
|
`./test.sh` tests can be skipped, but some things might not run correctly on your system if tests fail.
|
||||||
|
|
||||||
- Kaspad (and utilities) should now be installed in `$GOPATH/bin`. If you did
|
- Kaspad (and utilities) should now be installed in `$GOPATH/bin`. If you did
|
||||||
not already add the bin directory to your system path during Go installation,
|
not already add the bin directory to your system path during Go installation,
|
||||||
|
|||||||
1347
addrmgr/addrmanager.go
Normal file
1347
addrmgr/addrmanager.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,30 +2,28 @@
|
|||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package addressmanager
|
package addrmgr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
"github.com/kaspanet/kaspad/config"
|
||||||
"io/ioutil"
|
"github.com/kaspanet/kaspad/dagconfig"
|
||||||
|
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
|
|
||||||
"github.com/kaspanet/kaspad/util/mstime"
|
|
||||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// naTest is used to describe a test to be performed against the NetAddressKey
|
// naTest is used to describe a test to be performed against the NetAddressKey
|
||||||
// method.
|
// method.
|
||||||
type naTest struct {
|
type naTest struct {
|
||||||
in appmessage.NetAddress
|
in wire.NetAddress
|
||||||
want AddressKey
|
want string
|
||||||
}
|
}
|
||||||
|
|
||||||
// naTests houses all of the tests to be performed against the NetAddressKey
|
// naTests houses all of the tests to be performed against the NetAddressKey
|
||||||
@@ -96,60 +94,36 @@ func addNaTests() {
|
|||||||
addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336")
|
addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336")
|
||||||
}
|
}
|
||||||
|
|
||||||
func addNaTest(ip string, port uint16, want AddressKey) {
|
func addNaTest(ip string, port uint16, want string) {
|
||||||
nip := net.ParseIP(ip)
|
nip := net.ParseIP(ip)
|
||||||
na := *appmessage.NewNetAddressIPPort(nip, port, appmessage.SFNodeNetwork)
|
na := *wire.NewNetAddressIPPort(nip, port, wire.SFNodeNetwork)
|
||||||
test := naTest{na, want}
|
test := naTest{na, want}
|
||||||
naTests = append(naTests, test)
|
naTests = append(naTests, test)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupFuncForTest(host string) ([]net.IP, error) {
|
func lookupFunc(host string) ([]net.IP, error) {
|
||||||
return nil, errors.New("not implemented")
|
return nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAddrManagerForTest(t *testing.T, testName string,
|
|
||||||
localSubnetworkID *subnetworkid.SubnetworkID) (addressManager *AddressManager, teardown func()) {
|
|
||||||
|
|
||||||
cfg := config.DefaultConfig()
|
|
||||||
cfg.SubnetworkID = localSubnetworkID
|
|
||||||
|
|
||||||
dbPath, err := ioutil.TempDir("", testName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating temporary directory: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
databaseContext, err := dbaccess.New(dbPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating db: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addressManager, err = New(cfg, databaseContext)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating address manager: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addressManager, func() {
|
|
||||||
err := databaseContext.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error closing the database: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStartStop(t *testing.T) {
|
func TestStartStop(t *testing.T) {
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestStartStop", nil)
|
n := New("teststartstop", lookupFunc, nil)
|
||||||
defer teardown()
|
n.Start()
|
||||||
err := amgr.Start()
|
err := n.Stop()
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Address Manager failed to start: %v", err)
|
|
||||||
}
|
|
||||||
err = amgr.Stop()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Address Manager failed to stop: %v", err)
|
t.Fatalf("Address Manager failed to stop: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddAddressByIP(t *testing.T) {
|
func TestAddAddressByIP(t *testing.T) {
|
||||||
|
originalActiveCfg := config.ActiveConfig()
|
||||||
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
fmtErr := errors.Errorf("")
|
fmtErr := errors.Errorf("")
|
||||||
addrErr := &net.AddrError{}
|
addrErr := &net.AddrError{}
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
@@ -174,10 +148,9 @@ func TestAddAddressByIP(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
|
amgr := New("testaddressbyip", nil, nil)
|
||||||
defer teardown()
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
err := AddAddressByIP(amgr, test.addrIP, nil)
|
err := amgr.AddAddressByIP(test.addrIP, nil)
|
||||||
if test.err != nil && err == nil {
|
if test.err != nil && err == nil {
|
||||||
t.Errorf("TestAddAddressByIP test %d failed expected an error and got none", i)
|
t.Errorf("TestAddAddressByIP test %d failed expected an error and got none", i)
|
||||||
continue
|
continue
|
||||||
@@ -195,44 +168,52 @@ func TestAddAddressByIP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAddLocalAddress(t *testing.T) {
|
func TestAddLocalAddress(t *testing.T) {
|
||||||
|
originalActiveCfg := config.ActiveConfig()
|
||||||
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
address appmessage.NetAddress
|
address wire.NetAddress
|
||||||
priority AddressPriority
|
priority AddressPriority
|
||||||
valid bool
|
valid bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
appmessage.NetAddress{IP: net.ParseIP("192.168.0.100")},
|
wire.NetAddress{IP: net.ParseIP("192.168.0.100")},
|
||||||
InterfacePrio,
|
InterfacePrio,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
appmessage.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
wire.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
||||||
InterfacePrio,
|
InterfacePrio,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
appmessage.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
wire.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
||||||
BoundPrio,
|
BoundPrio,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
appmessage.NetAddress{IP: net.ParseIP("::1")},
|
wire.NetAddress{IP: net.ParseIP("::1")},
|
||||||
InterfacePrio,
|
InterfacePrio,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
appmessage.NetAddress{IP: net.ParseIP("fe80::1")},
|
wire.NetAddress{IP: net.ParseIP("fe80::1")},
|
||||||
InterfacePrio,
|
InterfacePrio,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
appmessage.NetAddress{IP: net.ParseIP("2620:100::1")},
|
wire.NetAddress{IP: net.ParseIP("2620:100::1")},
|
||||||
InterfacePrio,
|
InterfacePrio,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestAddLocalAddress", nil)
|
amgr := New("testaddlocaladdress", nil, nil)
|
||||||
defer teardown()
|
|
||||||
for x, test := range tests {
|
for x, test := range tests {
|
||||||
result := amgr.AddLocalAddress(&test.address, test.priority)
|
result := amgr.AddLocalAddress(&test.address, test.priority)
|
||||||
if result == nil && !test.valid {
|
if result == nil && !test.valid {
|
||||||
@@ -249,22 +230,30 @@ func TestAddLocalAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAttempt(t *testing.T) {
|
func TestAttempt(t *testing.T) {
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestAttempt", nil)
|
originalActiveCfg := config.ActiveConfig()
|
||||||
defer teardown()
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
|
n := New("testattempt", lookupFunc, nil)
|
||||||
|
|
||||||
// Add a new address and get it
|
// Add a new address and get it
|
||||||
err := AddAddressByIP(amgr, someIP+":8333", nil)
|
err := n.AddAddressByIP(someIP+":8333", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Adding address failed: %v", err)
|
t.Fatalf("Adding address failed: %v", err)
|
||||||
}
|
}
|
||||||
ka := amgr.GetAddress()
|
ka := n.GetAddress()
|
||||||
|
|
||||||
if !ka.LastAttempt().IsZero() {
|
if !ka.LastAttempt().IsZero() {
|
||||||
t.Errorf("Address should not have attempts, but does")
|
t.Errorf("Address should not have attempts, but does")
|
||||||
}
|
}
|
||||||
|
|
||||||
na := ka.NetAddress()
|
na := ka.NetAddress()
|
||||||
amgr.Attempt(na)
|
n.Attempt(na)
|
||||||
|
|
||||||
if ka.LastAttempt().IsZero() {
|
if ka.LastAttempt().IsZero() {
|
||||||
t.Errorf("Address should have an attempt, but does not")
|
t.Errorf("Address should have an attempt, but does not")
|
||||||
@@ -272,20 +261,28 @@ func TestAttempt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConnected(t *testing.T) {
|
func TestConnected(t *testing.T) {
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestConnected", nil)
|
originalActiveCfg := config.ActiveConfig()
|
||||||
defer teardown()
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
|
n := New("testconnected", lookupFunc, nil)
|
||||||
|
|
||||||
// Add a new address and get it
|
// Add a new address and get it
|
||||||
err := AddAddressByIP(amgr, someIP+":8333", nil)
|
err := n.AddAddressByIP(someIP+":8333", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Adding address failed: %v", err)
|
t.Fatalf("Adding address failed: %v", err)
|
||||||
}
|
}
|
||||||
ka := amgr.GetAddress()
|
ka := n.GetAddress()
|
||||||
na := ka.NetAddress()
|
na := ka.NetAddress()
|
||||||
// make it an hour ago
|
// make it an hour ago
|
||||||
na.Timestamp = mstime.Now().Add(time.Hour * -1)
|
na.Timestamp = time.Unix(time.Now().Add(time.Hour*-1).Unix(), 0)
|
||||||
|
|
||||||
amgr.Connected(na)
|
n.Connected(na)
|
||||||
|
|
||||||
if !ka.NetAddress().Timestamp.After(na.Timestamp) {
|
if !ka.NetAddress().Timestamp.After(na.Timestamp) {
|
||||||
t.Errorf("Address should have a new timestamp, but does not")
|
t.Errorf("Address should have a new timestamp, but does not")
|
||||||
@@ -293,50 +290,66 @@ func TestConnected(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNeedMoreAddresses(t *testing.T) {
|
func TestNeedMoreAddresses(t *testing.T) {
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestNeedMoreAddresses", nil)
|
originalActiveCfg := config.ActiveConfig()
|
||||||
defer teardown()
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
|
n := New("testneedmoreaddresses", lookupFunc, nil)
|
||||||
addrsToAdd := 1500
|
addrsToAdd := 1500
|
||||||
b := amgr.NeedMoreAddresses()
|
b := n.NeedMoreAddresses()
|
||||||
if !b {
|
if !b {
|
||||||
t.Errorf("Expected that we need more addresses")
|
t.Errorf("Expected that we need more addresses")
|
||||||
}
|
}
|
||||||
addrs := make([]*appmessage.NetAddress, addrsToAdd)
|
addrs := make([]*wire.NetAddress, addrsToAdd)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < addrsToAdd; i++ {
|
for i := 0; i < addrsToAdd; i++ {
|
||||||
s := AddressKey(fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60))
|
s := fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60)
|
||||||
addrs[i], err = amgr.DeserializeNetAddress(s)
|
addrs[i], err = n.DeserializeNetAddress(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to turn %s into an address: %v", s, err)
|
t.Errorf("Failed to turn %s into an address: %v", s, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
srcAddr := appmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||||
|
|
||||||
amgr.AddAddresses(addrs, srcAddr, nil)
|
n.AddAddresses(addrs, srcAddr, nil)
|
||||||
numAddrs := amgr.TotalNumAddresses()
|
numAddrs := n.TotalNumAddresses()
|
||||||
if numAddrs > addrsToAdd {
|
if numAddrs > addrsToAdd {
|
||||||
t.Errorf("Number of addresses is too many %d vs %d", numAddrs, addrsToAdd)
|
t.Errorf("Number of addresses is too many %d vs %d", numAddrs, addrsToAdd)
|
||||||
}
|
}
|
||||||
|
|
||||||
b = amgr.NeedMoreAddresses()
|
b = n.NeedMoreAddresses()
|
||||||
if b {
|
if b {
|
||||||
t.Errorf("Expected that we don't need more addresses")
|
t.Errorf("Expected that we don't need more addresses")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGood(t *testing.T) {
|
func TestGood(t *testing.T) {
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestGood", nil)
|
originalActiveCfg := config.ActiveConfig()
|
||||||
defer teardown()
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
|
n := New("testgood", lookupFunc, nil)
|
||||||
addrsToAdd := 64 * 64
|
addrsToAdd := 64 * 64
|
||||||
addrs := make([]*appmessage.NetAddress, addrsToAdd)
|
addrs := make([]*wire.NetAddress, addrsToAdd)
|
||||||
subnetworkCount := 32
|
subnetworkCount := 32
|
||||||
subnetworkIDs := make([]*subnetworkid.SubnetworkID, subnetworkCount)
|
subnetworkIDs := make([]*subnetworkid.SubnetworkID, subnetworkCount)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < addrsToAdd; i++ {
|
for i := 0; i < addrsToAdd; i++ {
|
||||||
s := AddressKey(fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60))
|
s := fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60)
|
||||||
addrs[i], err = amgr.DeserializeNetAddress(s)
|
addrs[i], err = n.DeserializeNetAddress(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to turn %s into an address: %v", s, err)
|
t.Errorf("Failed to turn %s into an address: %v", s, err)
|
||||||
}
|
}
|
||||||
@@ -346,26 +359,26 @@ func TestGood(t *testing.T) {
|
|||||||
subnetworkIDs[i] = &subnetworkid.SubnetworkID{0xff - byte(i)}
|
subnetworkIDs[i] = &subnetworkid.SubnetworkID{0xff - byte(i)}
|
||||||
}
|
}
|
||||||
|
|
||||||
srcAddr := appmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||||
|
|
||||||
amgr.AddAddresses(addrs, srcAddr, nil)
|
n.AddAddresses(addrs, srcAddr, nil)
|
||||||
for i, addr := range addrs {
|
for i, addr := range addrs {
|
||||||
amgr.Good(addr, subnetworkIDs[i%subnetworkCount])
|
n.Good(addr, subnetworkIDs[i%subnetworkCount])
|
||||||
}
|
}
|
||||||
|
|
||||||
numAddrs := amgr.TotalNumAddresses()
|
numAddrs := n.TotalNumAddresses()
|
||||||
if numAddrs >= addrsToAdd {
|
if numAddrs >= addrsToAdd {
|
||||||
t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd)
|
t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd)
|
||||||
}
|
}
|
||||||
|
|
||||||
numCache := len(amgr.AddressCache(true, nil))
|
numCache := len(n.AddressCache(true, nil))
|
||||||
if numCache == 0 || numCache >= numAddrs/4 {
|
if numCache == 0 || numCache >= numAddrs/4 {
|
||||||
t.Errorf("Number of addresses in cache: got %d, want positive and less than %d",
|
t.Errorf("Number of addresses in cache: got %d, want positive and less than %d",
|
||||||
numCache, numAddrs/4)
|
numCache, numAddrs/4)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < subnetworkCount; i++ {
|
for i := 0; i < subnetworkCount; i++ {
|
||||||
numCache = len(amgr.AddressCache(false, subnetworkIDs[i]))
|
numCache = len(n.AddressCache(false, subnetworkIDs[i]))
|
||||||
if numCache == 0 || numCache >= numAddrs/subnetworkCount {
|
if numCache == 0 || numCache >= numAddrs/subnetworkCount {
|
||||||
t.Errorf("Number of addresses in subnetwork cache: got %d, want positive and less than %d",
|
t.Errorf("Number of addresses in subnetwork cache: got %d, want positive and less than %d",
|
||||||
numCache, numAddrs/4/subnetworkCount)
|
numCache, numAddrs/4/subnetworkCount)
|
||||||
@@ -374,18 +387,26 @@ func TestGood(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGoodChangeSubnetworkID(t *testing.T) {
|
func TestGoodChangeSubnetworkID(t *testing.T) {
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestGoodChangeSubnetworkID", nil)
|
originalActiveCfg := config.ActiveConfig()
|
||||||
defer teardown()
|
config.SetActiveConfig(&config.Config{
|
||||||
addr := appmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
|
n := New("test_good_change_subnetwork_id", lookupFunc, nil)
|
||||||
|
addr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||||
addrKey := NetAddressKey(addr)
|
addrKey := NetAddressKey(addr)
|
||||||
srcAddr := appmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||||
|
|
||||||
oldSubnetwork := subnetworkid.SubnetworkIDNative
|
oldSubnetwork := subnetworkid.SubnetworkIDNative
|
||||||
amgr.AddAddress(addr, srcAddr, oldSubnetwork)
|
n.AddAddress(addr, srcAddr, oldSubnetwork)
|
||||||
amgr.Good(addr, oldSubnetwork)
|
n.Good(addr, oldSubnetwork)
|
||||||
|
|
||||||
// make sure address was saved to addressIndex under oldSubnetwork
|
// make sure address was saved to addrIndex under oldSubnetwork
|
||||||
ka := amgr.knownAddress(addr)
|
ka := n.find(addr)
|
||||||
if ka == nil {
|
if ka == nil {
|
||||||
t.Fatalf("Address was not found after first time .Good called")
|
t.Fatalf("Address was not found after first time .Good called")
|
||||||
}
|
}
|
||||||
@@ -394,10 +415,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure address was added to correct bucket under oldSubnetwork
|
// make sure address was added to correct bucket under oldSubnetwork
|
||||||
bucket := amgr.subnetworkTriedAddresBucketArrays[*oldSubnetwork][amgr.triedAddressBucketIndex(addr)]
|
bucket := n.addrTried[*oldSubnetwork][n.getTriedBucket(addr)]
|
||||||
wasFound := false
|
wasFound := false
|
||||||
for _, ka := range bucket {
|
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||||
if NetAddressKey(ka.NetAddress()) == addrKey {
|
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||||
wasFound = true
|
wasFound = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,10 +428,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
|||||||
|
|
||||||
// now call .Good again with a different subnetwork
|
// now call .Good again with a different subnetwork
|
||||||
newSubnetwork := subnetworkid.SubnetworkIDRegistry
|
newSubnetwork := subnetworkid.SubnetworkIDRegistry
|
||||||
amgr.Good(addr, newSubnetwork)
|
n.Good(addr, newSubnetwork)
|
||||||
|
|
||||||
// make sure address was updated in addressIndex under newSubnetwork
|
// make sure address was updated in addrIndex under newSubnetwork
|
||||||
ka = amgr.knownAddress(addr)
|
ka = n.find(addr)
|
||||||
if ka == nil {
|
if ka == nil {
|
||||||
t.Fatalf("Address was not found after second time .Good called")
|
t.Fatalf("Address was not found after second time .Good called")
|
||||||
}
|
}
|
||||||
@@ -419,10 +440,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure address was removed from bucket under oldSubnetwork
|
// make sure address was removed from bucket under oldSubnetwork
|
||||||
bucket = amgr.subnetworkTriedAddresBucketArrays[*oldSubnetwork][amgr.triedAddressBucketIndex(addr)]
|
bucket = n.addrTried[*oldSubnetwork][n.getTriedBucket(addr)]
|
||||||
wasFound = false
|
wasFound = false
|
||||||
for _, ka := range bucket {
|
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||||
if NetAddressKey(ka.NetAddress()) == addrKey {
|
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||||
wasFound = true
|
wasFound = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,10 +452,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure address was added to correct bucket under newSubnetwork
|
// make sure address was added to correct bucket under newSubnetwork
|
||||||
bucket = amgr.subnetworkTriedAddresBucketArrays[*newSubnetwork][amgr.triedAddressBucketIndex(addr)]
|
bucket = n.addrTried[*newSubnetwork][n.getTriedBucket(addr)]
|
||||||
wasFound = false
|
wasFound = false
|
||||||
for _, ka := range bucket {
|
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||||
if NetAddressKey(ka.NetAddress()) == addrKey {
|
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||||
wasFound = true
|
wasFound = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,36 +465,44 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAddress(t *testing.T) {
|
func TestGetAddress(t *testing.T) {
|
||||||
|
originalActiveCfg := config.ActiveConfig()
|
||||||
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
localSubnetworkID := &subnetworkid.SubnetworkID{0xff}
|
localSubnetworkID := &subnetworkid.SubnetworkID{0xff}
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestGetAddress", localSubnetworkID)
|
n := New("testgetaddress", lookupFunc, localSubnetworkID)
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
// Get an address from an empty set (should error)
|
// Get an address from an empty set (should error)
|
||||||
if rv := amgr.GetAddress(); rv != nil {
|
if rv := n.GetAddress(); rv != nil {
|
||||||
t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil)
|
t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new address and get it
|
// Add a new address and get it
|
||||||
err := AddAddressByIP(amgr, someIP+":8332", localSubnetworkID)
|
err := n.AddAddressByIP(someIP+":8332", localSubnetworkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Adding address failed: %v", err)
|
t.Fatalf("Adding address failed: %v", err)
|
||||||
}
|
}
|
||||||
ka := amgr.GetAddress()
|
ka := n.GetAddress()
|
||||||
if ka == nil {
|
if ka == nil {
|
||||||
t.Fatalf("Did not get an address where there is one in the pool")
|
t.Fatalf("Did not get an address where there is one in the pool")
|
||||||
}
|
}
|
||||||
amgr.Attempt(ka.NetAddress())
|
n.Attempt(ka.NetAddress())
|
||||||
|
|
||||||
// Checks that we don't get it if we find that it has other subnetwork ID than expected.
|
// Checks that we don't get it if we find that it has other subnetwork ID than expected.
|
||||||
actualSubnetworkID := &subnetworkid.SubnetworkID{0xfe}
|
actualSubnetworkID := &subnetworkid.SubnetworkID{0xfe}
|
||||||
amgr.Good(ka.NetAddress(), actualSubnetworkID)
|
n.Good(ka.NetAddress(), actualSubnetworkID)
|
||||||
ka = amgr.GetAddress()
|
ka = n.GetAddress()
|
||||||
if ka != nil {
|
if ka != nil {
|
||||||
t.Errorf("Didn't expect to get an address because there shouldn't be any address from subnetwork ID %s or nil", localSubnetworkID)
|
t.Errorf("Didn't expect to get an address because there shouldn't be any address from subnetwork ID %s or nil", localSubnetworkID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks that the total number of addresses incremented although the new address is not full node or a partial node of the same subnetwork as the local node.
|
// Checks that the total number of addresses incremented although the new address is not full node or a partial node of the same subnetwork as the local node.
|
||||||
numAddrs := amgr.TotalNumAddresses()
|
numAddrs := n.TotalNumAddresses()
|
||||||
if numAddrs != 1 {
|
if numAddrs != 1 {
|
||||||
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
|
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
|
||||||
}
|
}
|
||||||
@@ -481,11 +510,11 @@ func TestGetAddress(t *testing.T) {
|
|||||||
// Now we repeat the same process, but now the address has the expected subnetwork ID.
|
// Now we repeat the same process, but now the address has the expected subnetwork ID.
|
||||||
|
|
||||||
// Add a new address and get it
|
// Add a new address and get it
|
||||||
err = AddAddressByIP(amgr, someIP+":8333", localSubnetworkID)
|
err = n.AddAddressByIP(someIP+":8333", localSubnetworkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Adding address failed: %v", err)
|
t.Fatalf("Adding address failed: %v", err)
|
||||||
}
|
}
|
||||||
ka = amgr.GetAddress()
|
ka = n.GetAddress()
|
||||||
if ka == nil {
|
if ka == nil {
|
||||||
t.Fatalf("Did not get an address where there is one in the pool")
|
t.Fatalf("Did not get an address where there is one in the pool")
|
||||||
}
|
}
|
||||||
@@ -495,11 +524,11 @@ func TestGetAddress(t *testing.T) {
|
|||||||
if !ka.SubnetworkID().IsEqual(localSubnetworkID) {
|
if !ka.SubnetworkID().IsEqual(localSubnetworkID) {
|
||||||
t.Errorf("Wrong Subnetwork ID: got %v, want %v", *ka.SubnetworkID(), localSubnetworkID)
|
t.Errorf("Wrong Subnetwork ID: got %v, want %v", *ka.SubnetworkID(), localSubnetworkID)
|
||||||
}
|
}
|
||||||
amgr.Attempt(ka.NetAddress())
|
n.Attempt(ka.NetAddress())
|
||||||
|
|
||||||
// Mark this as a good address and get it
|
// Mark this as a good address and get it
|
||||||
amgr.Good(ka.NetAddress(), localSubnetworkID)
|
n.Good(ka.NetAddress(), localSubnetworkID)
|
||||||
ka = amgr.GetAddress()
|
ka = n.GetAddress()
|
||||||
if ka == nil {
|
if ka == nil {
|
||||||
t.Fatalf("Did not get an address where there is one in the pool")
|
t.Fatalf("Did not get an address where there is one in the pool")
|
||||||
}
|
}
|
||||||
@@ -510,14 +539,23 @@ func TestGetAddress(t *testing.T) {
|
|||||||
t.Errorf("Wrong Subnetwork ID: got %v, want %v", ka.SubnetworkID(), localSubnetworkID)
|
t.Errorf("Wrong Subnetwork ID: got %v, want %v", ka.SubnetworkID(), localSubnetworkID)
|
||||||
}
|
}
|
||||||
|
|
||||||
numAddrs = amgr.TotalNumAddresses()
|
numAddrs = n.TotalNumAddresses()
|
||||||
if numAddrs != 2 {
|
if numAddrs != 2 {
|
||||||
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
|
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetBestLocalAddress(t *testing.T) {
|
func TestGetBestLocalAddress(t *testing.T) {
|
||||||
localAddrs := []appmessage.NetAddress{
|
originalActiveCfg := config.ActiveConfig()
|
||||||
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
|
localAddrs := []wire.NetAddress{
|
||||||
{IP: net.ParseIP("192.168.0.100")},
|
{IP: net.ParseIP("192.168.0.100")},
|
||||||
{IP: net.ParseIP("::1")},
|
{IP: net.ParseIP("::1")},
|
||||||
{IP: net.ParseIP("fe80::1")},
|
{IP: net.ParseIP("fe80::1")},
|
||||||
@@ -525,40 +563,48 @@ func TestGetBestLocalAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
remoteAddr appmessage.NetAddress
|
remoteAddr wire.NetAddress
|
||||||
want0 appmessage.NetAddress
|
want0 wire.NetAddress
|
||||||
want1 appmessage.NetAddress
|
want1 wire.NetAddress
|
||||||
want2 appmessage.NetAddress
|
want2 wire.NetAddress
|
||||||
want3 appmessage.NetAddress
|
want3 wire.NetAddress
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// Remote connection from public IPv4
|
// Remote connection from public IPv4
|
||||||
appmessage.NetAddress{IP: net.ParseIP("204.124.8.1")},
|
wire.NetAddress{IP: net.ParseIP("204.124.8.1")},
|
||||||
appmessage.NetAddress{IP: net.IPv4zero},
|
wire.NetAddress{IP: net.IPv4zero},
|
||||||
appmessage.NetAddress{IP: net.IPv4zero},
|
wire.NetAddress{IP: net.IPv4zero},
|
||||||
appmessage.NetAddress{IP: net.ParseIP("204.124.8.100")},
|
wire.NetAddress{IP: net.ParseIP("204.124.8.100")},
|
||||||
appmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
|
wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Remote connection from private IPv4
|
// Remote connection from private IPv4
|
||||||
appmessage.NetAddress{IP: net.ParseIP("172.16.0.254")},
|
wire.NetAddress{IP: net.ParseIP("172.16.0.254")},
|
||||||
appmessage.NetAddress{IP: net.IPv4zero},
|
wire.NetAddress{IP: net.IPv4zero},
|
||||||
appmessage.NetAddress{IP: net.IPv4zero},
|
wire.NetAddress{IP: net.IPv4zero},
|
||||||
appmessage.NetAddress{IP: net.IPv4zero},
|
wire.NetAddress{IP: net.IPv4zero},
|
||||||
appmessage.NetAddress{IP: net.IPv4zero},
|
wire.NetAddress{IP: net.IPv4zero},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Remote connection from public IPv6
|
// Remote connection from public IPv6
|
||||||
appmessage.NetAddress{IP: net.ParseIP("2602:100:abcd::102")},
|
wire.NetAddress{IP: net.ParseIP("2602:100:abcd::102")},
|
||||||
appmessage.NetAddress{IP: net.IPv6zero},
|
wire.NetAddress{IP: net.IPv6zero},
|
||||||
appmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
|
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||||
appmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
|
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||||
appmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
|
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||||
},
|
},
|
||||||
|
/* XXX
|
||||||
|
{
|
||||||
|
// Remote connection from Tor
|
||||||
|
wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43::100")},
|
||||||
|
wire.NetAddress{IP: net.IPv4zero},
|
||||||
|
wire.NetAddress{IP: net.ParseIP("204.124.8.100")},
|
||||||
|
wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
|
||||||
|
},
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestGetBestLocalAddress", nil)
|
amgr := New("testgetbestlocaladdress", nil, nil)
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
// Test against default when there's no address
|
// Test against default when there's no address
|
||||||
for x, test := range tests {
|
for x, test := range tests {
|
||||||
@@ -585,7 +631,7 @@ func TestGetBestLocalAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add a public IP to the list of local addresses.
|
// Add a public IP to the list of local addresses.
|
||||||
localAddr := appmessage.NetAddress{IP: net.ParseIP("204.124.8.100")}
|
localAddr := wire.NetAddress{IP: net.ParseIP("204.124.8.100")}
|
||||||
amgr.AddLocalAddress(&localAddr, InterfacePrio)
|
amgr.AddLocalAddress(&localAddr, InterfacePrio)
|
||||||
|
|
||||||
// Test against want2
|
// Test against want2
|
||||||
@@ -599,7 +645,7 @@ func TestGetBestLocalAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
// Add a Tor generated IP address
|
// Add a Tor generated IP address
|
||||||
localAddr = appmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
|
localAddr = wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
|
||||||
amgr.AddLocalAddress(&localAddr, ManualPrio)
|
amgr.AddLocalAddress(&localAddr, ManualPrio)
|
||||||
// Test against want3
|
// Test against want3
|
||||||
for x, test := range tests {
|
for x, test := range tests {
|
||||||
@@ -624,4 +670,5 @@ func TestNetAddressKey(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Package addressmanager implements concurrency safe Kaspa address manager.
|
Package addrmgr implements concurrency safe Kaspa address manager.
|
||||||
|
|
||||||
Address Manager Overview
|
Address Manager Overview
|
||||||
|
|
||||||
@@ -31,4 +31,4 @@ peers which no longer appear to be good peers as well as bias the selection
|
|||||||
toward known good peers. The general idea is to make a best effort at only
|
toward known good peers. The general idea is to make a best effort at only
|
||||||
providing usable addresses.
|
providing usable addresses.
|
||||||
*/
|
*/
|
||||||
package addressmanager
|
package addrmgr
|
||||||
25
addrmgr/internal_test.go
Normal file
25
addrmgr/internal_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// 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 addrmgr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TstKnownAddressIsBad(ka *KnownAddress) bool {
|
||||||
|
return ka.isBad()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TstKnownAddressChance(ka *KnownAddress) float64 {
|
||||||
|
return ka.chance()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TstNewKnownAddress(na *wire.NetAddress, attempts int,
|
||||||
|
lastattempt, lastsuccess time.Time, tried bool, refs int) *KnownAddress {
|
||||||
|
return &KnownAddress{na: na, attempts: attempts, lastattempt: lastattempt,
|
||||||
|
lastsuccess: lastsuccess, tried: tried, refs: refs}
|
||||||
|
}
|
||||||
@@ -2,35 +2,33 @@
|
|||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package addressmanager
|
package addrmgr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/util/mstime"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KnownAddress tracks information about a known network address that is used
|
// KnownAddress tracks information about a known network address that is used
|
||||||
// to determine how viable an address is.
|
// to determine how viable an address is.
|
||||||
type KnownAddress struct {
|
type KnownAddress struct {
|
||||||
netAddress *appmessage.NetAddress
|
na *wire.NetAddress
|
||||||
sourceAddress *appmessage.NetAddress
|
srcAddr *wire.NetAddress
|
||||||
attempts int
|
attempts int
|
||||||
lastAttempt mstime.Time
|
lastattempt time.Time
|
||||||
lastSuccess mstime.Time
|
lastsuccess time.Time
|
||||||
tried bool
|
tried bool
|
||||||
referenceCount int // reference count of new buckets
|
refs int // reference count of new buckets
|
||||||
subnetworkID *subnetworkid.SubnetworkID
|
subnetworkID *subnetworkid.SubnetworkID
|
||||||
isBanned bool
|
|
||||||
bannedTime mstime.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetAddress returns the underlying appmessage.NetAddress associated with the
|
// NetAddress returns the underlying wire.NetAddress associated with the
|
||||||
// known address.
|
// known address.
|
||||||
func (ka *KnownAddress) NetAddress() *appmessage.NetAddress {
|
func (ka *KnownAddress) NetAddress() *wire.NetAddress {
|
||||||
return ka.netAddress
|
return ka.na
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubnetworkID returns the subnetwork ID of the known address.
|
// SubnetworkID returns the subnetwork ID of the known address.
|
||||||
@@ -39,16 +37,16 @@ func (ka *KnownAddress) SubnetworkID() *subnetworkid.SubnetworkID {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LastAttempt returns the last time the known address was attempted.
|
// LastAttempt returns the last time the known address was attempted.
|
||||||
func (ka *KnownAddress) LastAttempt() mstime.Time {
|
func (ka *KnownAddress) LastAttempt() time.Time {
|
||||||
return ka.lastAttempt
|
return ka.lastattempt
|
||||||
}
|
}
|
||||||
|
|
||||||
// chance returns the selection probability for a known address. The priority
|
// chance returns the selection probability for a known address. The priority
|
||||||
// depends upon how recently the address has been seen, how recently it was last
|
// depends upon how recently the address has been seen, how recently it was last
|
||||||
// attempted and how often attempts to connect to it have failed.
|
// attempted and how often attempts to connect to it have failed.
|
||||||
func (ka *KnownAddress) chance() float64 {
|
func (ka *KnownAddress) chance() float64 {
|
||||||
now := mstime.Now()
|
now := time.Now()
|
||||||
lastAttempt := now.Sub(ka.lastAttempt)
|
lastAttempt := now.Sub(ka.lastattempt)
|
||||||
|
|
||||||
if lastAttempt < 0 {
|
if lastAttempt < 0 {
|
||||||
lastAttempt = 0
|
lastAttempt = 0
|
||||||
@@ -78,27 +76,27 @@ func (ka *KnownAddress) chance() float64 {
|
|||||||
// All addresses that meet these criteria are assumed to be worthless and not
|
// All addresses that meet these criteria are assumed to be worthless and not
|
||||||
// worth keeping hold of.
|
// worth keeping hold of.
|
||||||
func (ka *KnownAddress) isBad() bool {
|
func (ka *KnownAddress) isBad() bool {
|
||||||
if ka.lastAttempt.After(mstime.Now().Add(-1 * time.Minute)) {
|
if ka.lastattempt.After(time.Now().Add(-1 * time.Minute)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// From the future?
|
// From the future?
|
||||||
if ka.netAddress.Timestamp.After(mstime.Now().Add(10 * time.Minute)) {
|
if ka.na.Timestamp.After(time.Now().Add(10 * time.Minute)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Over a month old?
|
// Over a month old?
|
||||||
if ka.netAddress.Timestamp.Before(mstime.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
|
if ka.na.Timestamp.Before(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never succeeded?
|
// Never succeeded?
|
||||||
if ka.lastSuccess.IsZero() && ka.attempts >= numRetries {
|
if ka.lastsuccess.IsZero() && ka.attempts >= numRetries {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hasn't succeeded in too long?
|
// Hasn't succeeded in too long?
|
||||||
if !ka.lastSuccess.After(mstime.Now().Add(-1*minBadDays*time.Hour*24)) &&
|
if !ka.lastsuccess.After(time.Now().Add(-1*minBadDays*time.Hour*24)) &&
|
||||||
ka.attempts >= maxFailures {
|
ka.attempts >= maxFailures {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
114
addrmgr/knownaddress_test.go
Normal file
114
addrmgr/knownaddress_test.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
// 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 addrmgr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/addrmgr"
|
||||||
|
"github.com/kaspanet/kaspad/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChance(t *testing.T) {
|
||||||
|
now := time.Unix(time.Now().Unix(), 0)
|
||||||
|
var tests = []struct {
|
||||||
|
addr *addrmgr.KnownAddress
|
||||||
|
expected float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
//Test normal case
|
||||||
|
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||||
|
0, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||||
|
1.0,
|
||||||
|
}, {
|
||||||
|
//Test case in which lastseen < 0
|
||||||
|
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(20 * time.Second)},
|
||||||
|
0, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||||
|
1.0,
|
||||||
|
}, {
|
||||||
|
//Test case in which lastattempt < 0
|
||||||
|
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||||
|
0, time.Now().Add(30*time.Minute), time.Now(), false, 0),
|
||||||
|
1.0 * .01,
|
||||||
|
}, {
|
||||||
|
//Test case in which lastattempt < ten minutes
|
||||||
|
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||||
|
0, time.Now().Add(-5*time.Minute), time.Now(), false, 0),
|
||||||
|
1.0 * .01,
|
||||||
|
}, {
|
||||||
|
//Test case with several failed attempts.
|
||||||
|
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||||
|
2, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||||
|
1 / 1.5 / 1.5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := .0001
|
||||||
|
for i, test := range tests {
|
||||||
|
chance := addrmgr.TstKnownAddressChance(test.addr)
|
||||||
|
if math.Abs(test.expected-chance) >= err {
|
||||||
|
t.Errorf("case %d: got %f, expected %f", i, chance, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsBad(t *testing.T) {
|
||||||
|
now := time.Unix(time.Now().Unix(), 0)
|
||||||
|
future := now.Add(35 * time.Minute)
|
||||||
|
monthOld := now.Add(-43 * time.Hour * 24)
|
||||||
|
secondsOld := now.Add(-2 * time.Second)
|
||||||
|
minutesOld := now.Add(-27 * time.Minute)
|
||||||
|
hoursOld := now.Add(-5 * time.Hour)
|
||||||
|
zeroTime := time.Time{}
|
||||||
|
|
||||||
|
futureNa := &wire.NetAddress{Timestamp: future}
|
||||||
|
minutesOldNa := &wire.NetAddress{Timestamp: minutesOld}
|
||||||
|
monthOldNa := &wire.NetAddress{Timestamp: monthOld}
|
||||||
|
currentNa := &wire.NetAddress{Timestamp: secondsOld}
|
||||||
|
|
||||||
|
//Test addresses that have been tried in the last minute.
|
||||||
|
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||||
|
t.Errorf("test case 1: addresses that have been tried in the last minute are not bad.")
|
||||||
|
}
|
||||||
|
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||||
|
t.Errorf("test case 2: addresses that have been tried in the last minute are not bad.")
|
||||||
|
}
|
||||||
|
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||||
|
t.Errorf("test case 3: addresses that have been tried in the last minute are not bad.")
|
||||||
|
}
|
||||||
|
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, monthOld, true, 0)) {
|
||||||
|
t.Errorf("test case 4: addresses that have been tried in the last minute are not bad.")
|
||||||
|
}
|
||||||
|
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 2, secondsOld, secondsOld, true, 0)) {
|
||||||
|
t.Errorf("test case 5: addresses that have been tried in the last minute are not bad.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Test address that claims to be from the future.
|
||||||
|
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 0, minutesOld, hoursOld, true, 0)) {
|
||||||
|
t.Errorf("test case 6: addresses that claim to be from the future are bad.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Test address that has not been seen in over a month.
|
||||||
|
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 0, minutesOld, hoursOld, true, 0)) {
|
||||||
|
t.Errorf("test case 7: addresses more than a month old are bad.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//It has failed at least three times and never succeeded.
|
||||||
|
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 3, minutesOld, zeroTime, true, 0)) {
|
||||||
|
t.Errorf("test case 8: addresses that have never succeeded are bad.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//It has failed ten times in the last week
|
||||||
|
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 10, minutesOld, monthOld, true, 0)) {
|
||||||
|
t.Errorf("test case 9: addresses that have not succeeded in too long are bad.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Test an address that should work.
|
||||||
|
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 2, minutesOld, hoursOld, true, 0)) {
|
||||||
|
t.Errorf("test case 10: This should be a valid address.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package addressmanager
|
package addrmgr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
"github.com/kaspanet/kaspad/logger"
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
"github.com/kaspanet/kaspad/util/panics"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2,11 +2,14 @@
|
|||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package addressmanager
|
package addrmgr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/config"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -85,19 +88,19 @@ func ipNet(ip string, ones, bits int) net.IPNet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsIPv4 returns whether or not the given address is an IPv4 address.
|
// IsIPv4 returns whether or not the given address is an IPv4 address.
|
||||||
func IsIPv4(na *appmessage.NetAddress) bool {
|
func IsIPv4(na *wire.NetAddress) bool {
|
||||||
return na.IP.To4() != nil
|
return na.IP.To4() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLocal returns whether or not the given address is a local address.
|
// IsLocal returns whether or not the given address is a local address.
|
||||||
func IsLocal(na *appmessage.NetAddress) bool {
|
func IsLocal(na *wire.NetAddress) bool {
|
||||||
return na.IP.IsLoopback() || zero4Net.Contains(na.IP)
|
return na.IP.IsLoopback() || zero4Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC1918 returns whether or not the passed address is part of the IPv4
|
// IsRFC1918 returns whether or not the passed address is part of the IPv4
|
||||||
// private network address space as defined by RFC1918 (10.0.0.0/8,
|
// private network address space as defined by RFC1918 (10.0.0.0/8,
|
||||||
// 172.16.0.0/12, or 192.168.0.0/16).
|
// 172.16.0.0/12, or 192.168.0.0/16).
|
||||||
func IsRFC1918(na *appmessage.NetAddress) bool {
|
func IsRFC1918(na *wire.NetAddress) bool {
|
||||||
for _, rfc := range rfc1918Nets {
|
for _, rfc := range rfc1918Nets {
|
||||||
if rfc.Contains(na.IP) {
|
if rfc.Contains(na.IP) {
|
||||||
return true
|
return true
|
||||||
@@ -108,56 +111,56 @@ func IsRFC1918(na *appmessage.NetAddress) bool {
|
|||||||
|
|
||||||
// IsRFC2544 returns whether or not the passed address is part of the IPv4
|
// IsRFC2544 returns whether or not the passed address is part of the IPv4
|
||||||
// address space as defined by RFC2544 (198.18.0.0/15)
|
// address space as defined by RFC2544 (198.18.0.0/15)
|
||||||
func IsRFC2544(na *appmessage.NetAddress) bool {
|
func IsRFC2544(na *wire.NetAddress) bool {
|
||||||
return rfc2544Net.Contains(na.IP)
|
return rfc2544Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC3849 returns whether or not the passed address is part of the IPv6
|
// IsRFC3849 returns whether or not the passed address is part of the IPv6
|
||||||
// documentation range as defined by RFC3849 (2001:DB8::/32).
|
// documentation range as defined by RFC3849 (2001:DB8::/32).
|
||||||
func IsRFC3849(na *appmessage.NetAddress) bool {
|
func IsRFC3849(na *wire.NetAddress) bool {
|
||||||
return rfc3849Net.Contains(na.IP)
|
return rfc3849Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC3927 returns whether or not the passed address is part of the IPv4
|
// IsRFC3927 returns whether or not the passed address is part of the IPv4
|
||||||
// autoconfiguration range as defined by RFC3927 (169.254.0.0/16).
|
// autoconfiguration range as defined by RFC3927 (169.254.0.0/16).
|
||||||
func IsRFC3927(na *appmessage.NetAddress) bool {
|
func IsRFC3927(na *wire.NetAddress) bool {
|
||||||
return rfc3927Net.Contains(na.IP)
|
return rfc3927Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC3964 returns whether or not the passed address is part of the IPv6 to
|
// IsRFC3964 returns whether or not the passed address is part of the IPv6 to
|
||||||
// IPv4 encapsulation range as defined by RFC3964 (2002::/16).
|
// IPv4 encapsulation range as defined by RFC3964 (2002::/16).
|
||||||
func IsRFC3964(na *appmessage.NetAddress) bool {
|
func IsRFC3964(na *wire.NetAddress) bool {
|
||||||
return rfc3964Net.Contains(na.IP)
|
return rfc3964Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC4193 returns whether or not the passed address is part of the IPv6
|
// IsRFC4193 returns whether or not the passed address is part of the IPv6
|
||||||
// unique local range as defined by RFC4193 (FC00::/7).
|
// unique local range as defined by RFC4193 (FC00::/7).
|
||||||
func IsRFC4193(na *appmessage.NetAddress) bool {
|
func IsRFC4193(na *wire.NetAddress) bool {
|
||||||
return rfc4193Net.Contains(na.IP)
|
return rfc4193Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC4380 returns whether or not the passed address is part of the IPv6
|
// IsRFC4380 returns whether or not the passed address is part of the IPv6
|
||||||
// teredo tunneling over UDP range as defined by RFC4380 (2001::/32).
|
// teredo tunneling over UDP range as defined by RFC4380 (2001::/32).
|
||||||
func IsRFC4380(na *appmessage.NetAddress) bool {
|
func IsRFC4380(na *wire.NetAddress) bool {
|
||||||
return rfc4380Net.Contains(na.IP)
|
return rfc4380Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC4843 returns whether or not the passed address is part of the IPv6
|
// IsRFC4843 returns whether or not the passed address is part of the IPv6
|
||||||
// ORCHID range as defined by RFC4843 (2001:10::/28).
|
// ORCHID range as defined by RFC4843 (2001:10::/28).
|
||||||
func IsRFC4843(na *appmessage.NetAddress) bool {
|
func IsRFC4843(na *wire.NetAddress) bool {
|
||||||
return rfc4843Net.Contains(na.IP)
|
return rfc4843Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC4862 returns whether or not the passed address is part of the IPv6
|
// IsRFC4862 returns whether or not the passed address is part of the IPv6
|
||||||
// stateless address autoconfiguration range as defined by RFC4862 (FE80::/64).
|
// stateless address autoconfiguration range as defined by RFC4862 (FE80::/64).
|
||||||
func IsRFC4862(na *appmessage.NetAddress) bool {
|
func IsRFC4862(na *wire.NetAddress) bool {
|
||||||
return rfc4862Net.Contains(na.IP)
|
return rfc4862Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC5737 returns whether or not the passed address is part of the IPv4
|
// IsRFC5737 returns whether or not the passed address is part of the IPv4
|
||||||
// documentation address space as defined by RFC5737 (192.0.2.0/24,
|
// documentation address space as defined by RFC5737 (192.0.2.0/24,
|
||||||
// 198.51.100.0/24, 203.0.113.0/24)
|
// 198.51.100.0/24, 203.0.113.0/24)
|
||||||
func IsRFC5737(na *appmessage.NetAddress) bool {
|
func IsRFC5737(na *wire.NetAddress) bool {
|
||||||
for _, rfc := range rfc5737Net {
|
for _, rfc := range rfc5737Net {
|
||||||
if rfc.Contains(na.IP) {
|
if rfc.Contains(na.IP) {
|
||||||
return true
|
return true
|
||||||
@@ -169,19 +172,19 @@ func IsRFC5737(na *appmessage.NetAddress) bool {
|
|||||||
|
|
||||||
// IsRFC6052 returns whether or not the passed address is part of the IPv6
|
// IsRFC6052 returns whether or not the passed address is part of the IPv6
|
||||||
// well-known prefix range as defined by RFC6052 (64:FF9B::/96).
|
// well-known prefix range as defined by RFC6052 (64:FF9B::/96).
|
||||||
func IsRFC6052(na *appmessage.NetAddress) bool {
|
func IsRFC6052(na *wire.NetAddress) bool {
|
||||||
return rfc6052Net.Contains(na.IP)
|
return rfc6052Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC6145 returns whether or not the passed address is part of the IPv6 to
|
// IsRFC6145 returns whether or not the passed address is part of the IPv6 to
|
||||||
// IPv4 translated address range as defined by RFC6145 (::FFFF:0:0:0/96).
|
// IPv4 translated address range as defined by RFC6145 (::FFFF:0:0:0/96).
|
||||||
func IsRFC6145(na *appmessage.NetAddress) bool {
|
func IsRFC6145(na *wire.NetAddress) bool {
|
||||||
return rfc6145Net.Contains(na.IP)
|
return rfc6145Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRFC6598 returns whether or not the passed address is part of the IPv4
|
// IsRFC6598 returns whether or not the passed address is part of the IPv4
|
||||||
// shared address space specified by RFC6598 (100.64.0.0/10)
|
// shared address space specified by RFC6598 (100.64.0.0/10)
|
||||||
func IsRFC6598(na *appmessage.NetAddress) bool {
|
func IsRFC6598(na *wire.NetAddress) bool {
|
||||||
return rfc6598Net.Contains(na.IP)
|
return rfc6598Net.Contains(na.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +192,7 @@ func IsRFC6598(na *appmessage.NetAddress) bool {
|
|||||||
// considered invalid under the following circumstances:
|
// considered invalid under the following circumstances:
|
||||||
// IPv4: It is either a zero or all bits set address.
|
// IPv4: It is either a zero or all bits set address.
|
||||||
// IPv6: It is either a zero or RFC3849 documentation address.
|
// IPv6: It is either a zero or RFC3849 documentation address.
|
||||||
func IsValid(na *appmessage.NetAddress) bool {
|
func IsValid(na *wire.NetAddress) bool {
|
||||||
// IsUnspecified returns if address is 0, so only all bits set, and
|
// IsUnspecified returns if address is 0, so only all bits set, and
|
||||||
// RFC3849 need to be explicitly checked.
|
// RFC3849 need to be explicitly checked.
|
||||||
return na.IP != nil && !(na.IP.IsUnspecified() ||
|
return na.IP != nil && !(na.IP.IsUnspecified() ||
|
||||||
@@ -199,8 +202,8 @@ func IsValid(na *appmessage.NetAddress) bool {
|
|||||||
// IsRoutable returns whether or not the passed address is routable over
|
// IsRoutable returns whether or not the passed address is routable over
|
||||||
// the public internet. This is true as long as the address is valid and is not
|
// the public internet. This is true as long as the address is valid and is not
|
||||||
// in any reserved ranges.
|
// in any reserved ranges.
|
||||||
func (am *AddressManager) IsRoutable(na *appmessage.NetAddress) bool {
|
func IsRoutable(na *wire.NetAddress) bool {
|
||||||
if am.cfg.NetParams().AcceptUnroutable {
|
if config.ActiveConfig().NetParams().AcceptUnroutable {
|
||||||
return !IsLocal(na)
|
return !IsLocal(na)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,11 +217,11 @@ func (am *AddressManager) IsRoutable(na *appmessage.NetAddress) bool {
|
|||||||
// of. This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string
|
// of. This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string
|
||||||
// "local" for a local address, and the string "unroutable" for an unroutable
|
// "local" for a local address, and the string "unroutable" for an unroutable
|
||||||
// address.
|
// address.
|
||||||
func (am *AddressManager) GroupKey(na *appmessage.NetAddress) string {
|
func GroupKey(na *wire.NetAddress) string {
|
||||||
if IsLocal(na) {
|
if IsLocal(na) {
|
||||||
return "local"
|
return "local"
|
||||||
}
|
}
|
||||||
if !am.IsRoutable(na) {
|
if !IsRoutable(na) {
|
||||||
return "unroutable"
|
return "unroutable"
|
||||||
}
|
}
|
||||||
if IsIPv4(na) {
|
if IsIPv4(na) {
|
||||||
@@ -2,21 +2,32 @@
|
|||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package addressmanager
|
package addrmgr_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
"github.com/kaspanet/kaspad/config"
|
||||||
|
"github.com/kaspanet/kaspad/dagconfig"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/addrmgr"
|
||||||
|
"github.com/kaspanet/kaspad/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestIPTypes ensures the various functions which determine the type of an IP
|
// TestIPTypes ensures the various functions which determine the type of an IP
|
||||||
// address based on RFCs work as intended.
|
// address based on RFCs work as intended.
|
||||||
func TestIPTypes(t *testing.T) {
|
func TestIPTypes(t *testing.T) {
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
|
originalActiveCfg := config.ActiveConfig()
|
||||||
defer teardown()
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
type ipTest struct {
|
type ipTest struct {
|
||||||
in appmessage.NetAddress
|
in wire.NetAddress
|
||||||
rfc1918 bool
|
rfc1918 bool
|
||||||
rfc2544 bool
|
rfc2544 bool
|
||||||
rfc3849 bool
|
rfc3849 bool
|
||||||
@@ -39,7 +50,7 @@ func TestIPTypes(t *testing.T) {
|
|||||||
rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598,
|
rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598,
|
||||||
local, valid, routable bool) ipTest {
|
local, valid, routable bool) ipTest {
|
||||||
nip := net.ParseIP(ip)
|
nip := net.ParseIP(ip)
|
||||||
na := *appmessage.NewNetAddressIPPort(nip, 16111, appmessage.SFNodeNetwork)
|
na := *wire.NewNetAddressIPPort(nip, 16111, wire.SFNodeNetwork)
|
||||||
test := ipTest{na, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380,
|
test := ipTest{na, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380,
|
||||||
rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, local, valid, routable}
|
rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, local, valid, routable}
|
||||||
return test
|
return test
|
||||||
@@ -88,55 +99,55 @@ func TestIPTypes(t *testing.T) {
|
|||||||
|
|
||||||
t.Logf("Running %d tests", len(tests))
|
t.Logf("Running %d tests", len(tests))
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
if rv := IsRFC1918(&test.in); rv != test.rfc1918 {
|
if rv := addrmgr.IsRFC1918(&test.in); rv != test.rfc1918 {
|
||||||
t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918)
|
t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC3849(&test.in); rv != test.rfc3849 {
|
if rv := addrmgr.IsRFC3849(&test.in); rv != test.rfc3849 {
|
||||||
t.Errorf("IsRFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849)
|
t.Errorf("IsRFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC3927(&test.in); rv != test.rfc3927 {
|
if rv := addrmgr.IsRFC3927(&test.in); rv != test.rfc3927 {
|
||||||
t.Errorf("IsRFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927)
|
t.Errorf("IsRFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC3964(&test.in); rv != test.rfc3964 {
|
if rv := addrmgr.IsRFC3964(&test.in); rv != test.rfc3964 {
|
||||||
t.Errorf("IsRFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964)
|
t.Errorf("IsRFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC4193(&test.in); rv != test.rfc4193 {
|
if rv := addrmgr.IsRFC4193(&test.in); rv != test.rfc4193 {
|
||||||
t.Errorf("IsRFC4193 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4193)
|
t.Errorf("IsRFC4193 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4193)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC4380(&test.in); rv != test.rfc4380 {
|
if rv := addrmgr.IsRFC4380(&test.in); rv != test.rfc4380 {
|
||||||
t.Errorf("IsRFC4380 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4380)
|
t.Errorf("IsRFC4380 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4380)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC4843(&test.in); rv != test.rfc4843 {
|
if rv := addrmgr.IsRFC4843(&test.in); rv != test.rfc4843 {
|
||||||
t.Errorf("IsRFC4843 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4843)
|
t.Errorf("IsRFC4843 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4843)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC4862(&test.in); rv != test.rfc4862 {
|
if rv := addrmgr.IsRFC4862(&test.in); rv != test.rfc4862 {
|
||||||
t.Errorf("IsRFC4862 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4862)
|
t.Errorf("IsRFC4862 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4862)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC6052(&test.in); rv != test.rfc6052 {
|
if rv := addrmgr.IsRFC6052(&test.in); rv != test.rfc6052 {
|
||||||
t.Errorf("isRFC6052 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6052)
|
t.Errorf("isRFC6052 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6052)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsRFC6145(&test.in); rv != test.rfc6145 {
|
if rv := addrmgr.IsRFC6145(&test.in); rv != test.rfc6145 {
|
||||||
t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6145)
|
t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6145)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsLocal(&test.in); rv != test.local {
|
if rv := addrmgr.IsLocal(&test.in); rv != test.local {
|
||||||
t.Errorf("IsLocal %s\n got: %v want: %v", test.in.IP, rv, test.local)
|
t.Errorf("IsLocal %s\n got: %v want: %v", test.in.IP, rv, test.local)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := IsValid(&test.in); rv != test.valid {
|
if rv := addrmgr.IsValid(&test.in); rv != test.valid {
|
||||||
t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid)
|
t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv := amgr.IsRoutable(&test.in); rv != test.routable {
|
if rv := addrmgr.IsRoutable(&test.in); rv != test.routable {
|
||||||
t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable)
|
t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,8 +156,14 @@ func TestIPTypes(t *testing.T) {
|
|||||||
// TestGroupKey tests the GroupKey function to ensure it properly groups various
|
// TestGroupKey tests the GroupKey function to ensure it properly groups various
|
||||||
// IP addresses.
|
// IP addresses.
|
||||||
func TestGroupKey(t *testing.T) {
|
func TestGroupKey(t *testing.T) {
|
||||||
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
|
originalActiveCfg := config.ActiveConfig()
|
||||||
defer teardown()
|
config.SetActiveConfig(&config.Config{
|
||||||
|
Flags: &config.Flags{
|
||||||
|
NetworkFlags: config.NetworkFlags{
|
||||||
|
ActiveNetParams: &dagconfig.SimnetParams},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer config.SetActiveConfig(originalActiveCfg)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -195,8 +212,8 @@ func TestGroupKey(t *testing.T) {
|
|||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
nip := net.ParseIP(test.ip)
|
nip := net.ParseIP(test.ip)
|
||||||
na := *appmessage.NewNetAddressIPPort(nip, 8333, appmessage.SFNodeNetwork)
|
na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork)
|
||||||
if key := amgr.GroupKey(&na); key != test.expected {
|
if key := addrmgr.GroupKey(&na); key != test.expected {
|
||||||
t.Errorf("TestGroupKey #%d (%s): unexpected group key "+
|
t.Errorf("TestGroupKey #%d (%s): unexpected group key "+
|
||||||
"- got '%s', want '%s'", i, test.name,
|
"- got '%s', want '%s'", i, test.name,
|
||||||
key, test.expected)
|
key, test.expected)
|
||||||
248
app/app.go
248
app/app.go
@@ -1,248 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
|
|
||||||
"github.com/kaspanet/kaspad/domain/mempool"
|
|
||||||
"github.com/kaspanet/kaspad/domain/mining"
|
|
||||||
"github.com/kaspanet/kaspad/domain/txscript"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
|
|
||||||
"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/rpc"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/os/signal"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
// App is a wrapper for all the kaspad services
|
|
||||||
type App struct {
|
|
||||||
cfg *config.Config
|
|
||||||
rpcServer *rpc.Server
|
|
||||||
addressManager *addressmanager.AddressManager
|
|
||||||
protocolManager *protocol.Manager
|
|
||||||
connectionManager *connmanager.ConnectionManager
|
|
||||||
netAdapter *netadapter.NetAdapter
|
|
||||||
|
|
||||||
started, shutdown int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start launches all the kaspad services.
|
|
||||||
func (a *App) Start() {
|
|
||||||
// Already started?
|
|
||||||
if atomic.AddInt32(&a.started, 1) != 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("Starting kaspad")
|
|
||||||
|
|
||||||
err := a.protocolManager.Start()
|
|
||||||
if err != nil {
|
|
||||||
panics.Exit(log, fmt.Sprintf("Error starting the p2p protocol: %+v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
a.maybeSeedFromDNS()
|
|
||||||
|
|
||||||
a.connectionManager.Start()
|
|
||||||
|
|
||||||
if !a.cfg.DisableRPC {
|
|
||||||
a.rpcServer.Start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop gracefully shuts down all the kaspad services.
|
|
||||||
func (a *App) Stop() {
|
|
||||||
// Make sure this only happens once.
|
|
||||||
if atomic.AddInt32(&a.shutdown, 1) != 1 {
|
|
||||||
log.Infof("Kaspad is already in the process of shutting down")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Warnf("Kaspad shutting down")
|
|
||||||
|
|
||||||
a.connectionManager.Stop()
|
|
||||||
|
|
||||||
err := a.protocolManager.Stop()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error stopping the p2p protocol: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown the RPC server if it's not disabled.
|
|
||||||
if !a.cfg.DisableRPC {
|
|
||||||
err := a.rpcServer.Stop()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error stopping rpcServer: %+v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.addressManager.Stop()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error stopping address manager: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new App instance configured to listen on addr for the
|
|
||||||
// kaspa network type specified by dagParams. Use start to begin accepting
|
|
||||||
// connections from peers.
|
|
||||||
func New(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{}) (*App, error) {
|
|
||||||
indexManager, acceptanceIndex := setupIndexes(cfg)
|
|
||||||
|
|
||||||
sigCache := txscript.NewSigCache(cfg.SigCacheMaxSize)
|
|
||||||
|
|
||||||
// Create a new block DAG instance with the appropriate configuration.
|
|
||||||
dag, err := setupDAG(cfg, databaseContext, interrupt, sigCache, indexManager)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
txMempool := setupMempool(cfg, dag, sigCache)
|
|
||||||
|
|
||||||
netAdapter, err := netadapter.NewNetAdapter(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
addressManager, err := addressmanager.New(cfg, databaseContext)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
connectionManager, err := connmanager.New(cfg, netAdapter, addressManager)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
protocolManager, err := protocol.NewManager(cfg, dag, netAdapter, addressManager, txMempool, connectionManager)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rpcServer, err := setupRPC(
|
|
||||||
cfg, dag, txMempool, sigCache, acceptanceIndex, connectionManager, addressManager, protocolManager)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &App{
|
|
||||||
cfg: cfg,
|
|
||||||
rpcServer: rpcServer,
|
|
||||||
protocolManager: protocolManager,
|
|
||||||
connectionManager: connectionManager,
|
|
||||||
netAdapter: netAdapter,
|
|
||||||
addressManager: addressManager,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) maybeSeedFromDNS() {
|
|
||||||
if !a.cfg.DisableDNSSeed {
|
|
||||||
dnsseed.SeedFromDNS(a.cfg.NetParams(), a.cfg.DNSSeed, appmessage.SFNodeNetwork, 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, addresses[0], nil)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func setupDAG(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{},
|
|
||||||
sigCache *txscript.SigCache, indexManager blockdag.IndexManager) (*blockdag.BlockDAG, error) {
|
|
||||||
|
|
||||||
dag, err := blockdag.New(&blockdag.Config{
|
|
||||||
Interrupt: interrupt,
|
|
||||||
DatabaseContext: databaseContext,
|
|
||||||
DAGParams: cfg.NetParams(),
|
|
||||||
TimeSource: blockdag.NewTimeSource(),
|
|
||||||
SigCache: sigCache,
|
|
||||||
IndexManager: indexManager,
|
|
||||||
SubnetworkID: cfg.SubnetworkID,
|
|
||||||
})
|
|
||||||
return dag, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupIndexes(cfg *config.Config) (blockdag.IndexManager, *indexers.AcceptanceIndex) {
|
|
||||||
// Create indexes if needed.
|
|
||||||
var indexes []indexers.Indexer
|
|
||||||
var acceptanceIndex *indexers.AcceptanceIndex
|
|
||||||
if cfg.AcceptanceIndex {
|
|
||||||
log.Info("acceptance index is enabled")
|
|
||||||
acceptanceIndex = indexers.NewAcceptanceIndex()
|
|
||||||
indexes = append(indexes, acceptanceIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an index manager if any of the optional indexes are enabled.
|
|
||||||
if len(indexes) < 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
indexManager := indexers.NewManager(indexes)
|
|
||||||
return indexManager, acceptanceIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupMempool(cfg *config.Config, dag *blockdag.BlockDAG, sigCache *txscript.SigCache) *mempool.TxPool {
|
|
||||||
mempoolConfig := mempool.Config{
|
|
||||||
Policy: mempool.Policy{
|
|
||||||
AcceptNonStd: cfg.RelayNonStd,
|
|
||||||
MaxOrphanTxs: cfg.MaxOrphanTxs,
|
|
||||||
MaxOrphanTxSize: config.DefaultMaxOrphanTxSize,
|
|
||||||
MinRelayTxFee: cfg.MinRelayTxFee,
|
|
||||||
MaxTxVersion: 1,
|
|
||||||
},
|
|
||||||
CalcSequenceLockNoLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
|
|
||||||
return dag.CalcSequenceLockNoLock(tx, utxoSet)
|
|
||||||
},
|
|
||||||
SigCache: sigCache,
|
|
||||||
DAG: dag,
|
|
||||||
}
|
|
||||||
|
|
||||||
return mempool.New(&mempoolConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupRPC(cfg *config.Config,
|
|
||||||
dag *blockdag.BlockDAG,
|
|
||||||
txMempool *mempool.TxPool,
|
|
||||||
sigCache *txscript.SigCache,
|
|
||||||
acceptanceIndex *indexers.AcceptanceIndex,
|
|
||||||
connectionManager *connmanager.ConnectionManager,
|
|
||||||
addressManager *addressmanager.AddressManager,
|
|
||||||
protocolManager *protocol.Manager) (*rpc.Server, error) {
|
|
||||||
|
|
||||||
if !cfg.DisableRPC {
|
|
||||||
policy := mining.Policy{
|
|
||||||
BlockMaxMass: cfg.BlockMaxMass,
|
|
||||||
}
|
|
||||||
blockTemplateGenerator := mining.NewBlkTmplGenerator(&policy, txMempool, dag, sigCache)
|
|
||||||
|
|
||||||
rpcServer, err := rpc.NewRPCServer(cfg, dag, txMempool, acceptanceIndex, blockTemplateGenerator,
|
|
||||||
connectionManager, addressManager, protocolManager)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal process shutdown when the RPC server requests it.
|
|
||||||
spawn("setupRPC-handleShutdownRequest", func() {
|
|
||||||
<-rpcServer.RequestedProcessShutdown()
|
|
||||||
signal.ShutdownRequestChannel <- struct{}{}
|
|
||||||
})
|
|
||||||
|
|
||||||
return rpcServer, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// P2PNodeID returns the network ID associated with this App
|
|
||||||
func (a *App) P2PNodeID() *id.ID {
|
|
||||||
return a.netAdapter.ID()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddressManager returns the AddressManager associated with this App
|
|
||||||
func (a *App) AddressManager() *addressmanager.AddressManager {
|
|
||||||
return a.addressManager
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type baseMessage struct {
|
|
||||||
messageNumber uint64
|
|
||||||
receivedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *baseMessage) MessageNumber() uint64 {
|
|
||||||
return b.messageNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *baseMessage) SetMessageNumber(messageNumber uint64) {
|
|
||||||
b.messageNumber = messageNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *baseMessage) ReceivedAt() time.Time {
|
|
||||||
return b.receivedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *baseMessage) SetReceivedAt(receivedAt time.Time) {
|
|
||||||
b.receivedAt = receivedAt
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MaxMessagePayload is the maximum bytes a message can be regardless of other
|
|
||||||
// individual limits imposed by messages themselves.
|
|
||||||
const MaxMessagePayload = (1024 * 1024 * 32) // 32MB
|
|
||||||
|
|
||||||
// MessageCommand is a number in the header of a message that represents its type.
|
|
||||||
type MessageCommand uint32
|
|
||||||
|
|
||||||
func (cmd MessageCommand) String() string {
|
|
||||||
cmdString, ok := MessageCommandToString[cmd]
|
|
||||||
if !ok {
|
|
||||||
cmdString = "unknown command"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s [code %d]", cmdString, uint8(cmd))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands used in kaspa message headers which describe the type of message.
|
|
||||||
const (
|
|
||||||
CmdVersion MessageCommand = iota
|
|
||||||
CmdVerAck
|
|
||||||
CmdRequestAddresses
|
|
||||||
CmdAddresses
|
|
||||||
CmdRequestIBDBlocks
|
|
||||||
CmdBlock
|
|
||||||
CmdTx
|
|
||||||
CmdPing
|
|
||||||
CmdPong
|
|
||||||
CmdRequestBlockLocator
|
|
||||||
CmdBlockLocator
|
|
||||||
CmdSelectedTip
|
|
||||||
CmdRequestSelectedTip
|
|
||||||
CmdInvRelayBlock
|
|
||||||
CmdRequestRelayBlocks
|
|
||||||
CmdInvTransaction
|
|
||||||
CmdRequestTransactions
|
|
||||||
CmdIBDBlock
|
|
||||||
CmdRequestNextIBDBlocks
|
|
||||||
CmdDoneIBDBlocks
|
|
||||||
CmdTransactionNotFound
|
|
||||||
CmdReject
|
|
||||||
)
|
|
||||||
|
|
||||||
// MessageCommandToString maps all MessageCommands to their string representation
|
|
||||||
var MessageCommandToString = map[MessageCommand]string{
|
|
||||||
CmdVersion: "Version",
|
|
||||||
CmdVerAck: "VerAck",
|
|
||||||
CmdRequestAddresses: "RequestAddresses",
|
|
||||||
CmdAddresses: "Addresses",
|
|
||||||
CmdRequestIBDBlocks: "RequestBlocks",
|
|
||||||
CmdBlock: "Block",
|
|
||||||
CmdTx: "Tx",
|
|
||||||
CmdPing: "Ping",
|
|
||||||
CmdPong: "Pong",
|
|
||||||
CmdRequestBlockLocator: "RequestBlockLocator",
|
|
||||||
CmdBlockLocator: "BlockLocator",
|
|
||||||
CmdSelectedTip: "SelectedTip",
|
|
||||||
CmdRequestSelectedTip: "RequestSelectedTip",
|
|
||||||
CmdInvRelayBlock: "InvRelayBlock",
|
|
||||||
CmdRequestRelayBlocks: "RequestRelayBlocks",
|
|
||||||
CmdInvTransaction: "InvTransaction",
|
|
||||||
CmdRequestTransactions: "RequestTransactions",
|
|
||||||
CmdIBDBlock: "IBDBlock",
|
|
||||||
CmdRequestNextIBDBlocks: "RequestNextIBDBlocks",
|
|
||||||
CmdDoneIBDBlocks: "DoneIBDBlocks",
|
|
||||||
CmdTransactionNotFound: "TransactionNotFound",
|
|
||||||
CmdReject: "Reject",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message is an interface that describes a kaspa message. A type that
|
|
||||||
// implements Message has complete control over the representation of its data
|
|
||||||
// and may therefore contain additional or fewer fields than those which
|
|
||||||
// are used directly in the protocol encoded message.
|
|
||||||
type Message interface {
|
|
||||||
Command() MessageCommand
|
|
||||||
MessageNumber() uint64
|
|
||||||
SetMessageNumber(index uint64)
|
|
||||||
ReceivedAt() time.Time
|
|
||||||
SetReceivedAt(receivedAt time.Time)
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
// 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 appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MaxAddressesPerMsg is the maximum number of addresses that can be in a single
|
|
||||||
// kaspa Addresses message (MsgAddresses).
|
|
||||||
const MaxAddressesPerMsg = 1000
|
|
||||||
|
|
||||||
// MsgAddresses implements the Message interface and represents a kaspa
|
|
||||||
// Addresses message. It is used to provide a list of known active peers on the
|
|
||||||
// network. An active peer is considered one that has transmitted a message
|
|
||||||
// within the last 3 hours. Nodes which have not transmitted in that time
|
|
||||||
// frame should be forgotten. Each message is limited to a maximum number of
|
|
||||||
// addresses, which is currently 1000. As a result, multiple messages must
|
|
||||||
// be used to relay the full list.
|
|
||||||
//
|
|
||||||
// Use the AddAddress function to build up the list of known addresses when
|
|
||||||
// sending an Addresses message to another peer.
|
|
||||||
type MsgAddresses struct {
|
|
||||||
baseMessage
|
|
||||||
IncludeAllSubnetworks bool
|
|
||||||
SubnetworkID *subnetworkid.SubnetworkID
|
|
||||||
AddrList []*NetAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAddress adds a known active peer to the message.
|
|
||||||
func (msg *MsgAddresses) AddAddress(na *NetAddress) error {
|
|
||||||
if len(msg.AddrList)+1 > MaxAddressesPerMsg {
|
|
||||||
str := fmt.Sprintf("too many addresses in message [max %d]",
|
|
||||||
MaxAddressesPerMsg)
|
|
||||||
return messageError("MsgAddresses.AddAddress", str)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.AddrList = append(msg.AddrList, na)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAddresses adds multiple known active peers to the message.
|
|
||||||
func (msg *MsgAddresses) AddAddresses(netAddrs ...*NetAddress) error {
|
|
||||||
for _, na := range netAddrs {
|
|
||||||
err := msg.AddAddress(na)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearAddresses removes all addresses from the message.
|
|
||||||
func (msg *MsgAddresses) ClearAddresses() {
|
|
||||||
msg.AddrList = []*NetAddress{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgAddresses) Command() MessageCommand {
|
|
||||||
return CmdAddresses
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgAddresses returns a new kaspa Addresses message that conforms to the
|
|
||||||
// Message interface. See MsgAddresses for details.
|
|
||||||
func NewMsgAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgAddresses {
|
|
||||||
return &MsgAddresses{
|
|
||||||
IncludeAllSubnetworks: includeAllSubnetworks,
|
|
||||||
SubnetworkID: subnetworkID,
|
|
||||||
AddrList: make([]*NetAddress, 0, MaxAddressesPerMsg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestAddresses tests the MsgAddresses API.
|
|
||||||
func TestAddresses(t *testing.T) {
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(3)
|
|
||||||
msg := NewMsgAddresses(false, nil)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgAddresses: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure NetAddresses are added properly.
|
|
||||||
tcpAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
|
||||||
na := NewNetAddress(tcpAddr, SFNodeNetwork)
|
|
||||||
err := msg.AddAddress(na)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("AddAddress: %v", err)
|
|
||||||
}
|
|
||||||
if msg.AddrList[0] != na {
|
|
||||||
t.Errorf("AddAddress: wrong address added - got %v, want %v",
|
|
||||||
spew.Sprint(msg.AddrList[0]), spew.Sprint(na))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the address list is cleared properly.
|
|
||||||
msg.ClearAddresses()
|
|
||||||
if len(msg.AddrList) != 0 {
|
|
||||||
t.Errorf("ClearAddresses: address list is not empty - "+
|
|
||||||
"got %v [%v], want %v", len(msg.AddrList),
|
|
||||||
spew.Sprint(msg.AddrList[0]), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure adding more than the max allowed addresses per message returns
|
|
||||||
// error.
|
|
||||||
for i := 0; i < MaxAddressesPerMsg+1; i++ {
|
|
||||||
err = msg.AddAddress(na)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("AddAddress: expected error on too many addresses " +
|
|
||||||
"not received")
|
|
||||||
}
|
|
||||||
err = msg.AddAddresses(na)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("AddAddresses: expected error on too many addresses " +
|
|
||||||
"not received")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed
|
|
||||||
// per message.
|
|
||||||
const MaxBlockLocatorsPerMsg = 500
|
|
||||||
|
|
||||||
// MsgBlockLocator implements the Message interface and represents a kaspa
|
|
||||||
// locator message. It is used to find the blockLocator of a peer that is
|
|
||||||
// syncing with you.
|
|
||||||
type MsgBlockLocator struct {
|
|
||||||
baseMessage
|
|
||||||
BlockLocatorHashes []*daghash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgBlockLocator) Command() MessageCommand {
|
|
||||||
return CmdBlockLocator
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgBlockLocator returns a new kaspa locator message that conforms to
|
|
||||||
// the Message interface. See MsgBlockLocator for details.
|
|
||||||
func NewMsgBlockLocator(locatorHashes []*daghash.Hash) *MsgBlockLocator {
|
|
||||||
return &MsgBlockLocator{
|
|
||||||
BlockLocatorHashes: locatorHashes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestBlockLocator tests the MsgBlockLocator API.
|
|
||||||
func TestBlockLocator(t *testing.T) {
|
|
||||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
|
||||||
locatorHash, err := daghash.NewHashFromStr(hashStr)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewHashFromStr: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := NewMsgBlockLocator([]*daghash.Hash{locatorHash})
|
|
||||||
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(10)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgBlockLocator: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure block locator hashes are added properly.
|
|
||||||
if msg.BlockLocatorHashes[0] != locatorHash {
|
|
||||||
t.Errorf("AddBlockLocatorHash: wrong block locator added - "+
|
|
||||||
"got %v, want %v",
|
|
||||||
spew.Sprint(msg.BlockLocatorHashes[0]),
|
|
||||||
spew.Sprint(locatorHash))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
// MsgDoneIBDBlocks implements the Message interface and represents a kaspa
|
|
||||||
// DoneIBDBlocks message. It is used to notify the IBD syncing peer that the
|
|
||||||
// syncer sent all the requested blocks.
|
|
||||||
//
|
|
||||||
// This message has no payload.
|
|
||||||
type MsgDoneIBDBlocks struct {
|
|
||||||
baseMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgDoneIBDBlocks) Command() MessageCommand {
|
|
||||||
return CmdDoneIBDBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgDoneIBDBlocks returns a new kaspa DoneIBDBlocks message that conforms to the
|
|
||||||
// Message interface.
|
|
||||||
func NewMsgDoneIBDBlocks() *MsgDoneIBDBlocks {
|
|
||||||
return &MsgDoneIBDBlocks{}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
// MsgIBDBlock implements the Message interface and represents a kaspa
|
|
||||||
// ibdblock message. It is used to deliver block and transaction information in
|
|
||||||
// response to a RequestIBDBlocks message (MsgRequestIBDBlocks).
|
|
||||||
type MsgIBDBlock struct {
|
|
||||||
baseMessage
|
|
||||||
*MsgBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgIBDBlock) Command() MessageCommand {
|
|
||||||
return CmdIBDBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxPayloadLength returns the maximum length the payload can be for the
|
|
||||||
// receiver. This is part of the Message interface implementation.
|
|
||||||
func (msg *MsgIBDBlock) MaxPayloadLength(pver uint32) uint32 {
|
|
||||||
return MaxMessagePayload
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgIBDBlock returns a new kaspa ibdblock message that conforms to the
|
|
||||||
// Message interface. See MsgIBDBlock for details.
|
|
||||||
func NewMsgIBDBlock(msgBlock *MsgBlock) *MsgIBDBlock {
|
|
||||||
return &MsgIBDBlock{MsgBlock: msgBlock}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestIBDBlock tests the MsgIBDBlock API.
|
|
||||||
func TestIBDBlock(t *testing.T) {
|
|
||||||
pver := ProtocolVersion
|
|
||||||
|
|
||||||
// Block 1 header.
|
|
||||||
parentHashes := blockOne.Header.ParentHashes
|
|
||||||
hashMerkleRoot := blockOne.Header.HashMerkleRoot
|
|
||||||
acceptedIDMerkleRoot := blockOne.Header.AcceptedIDMerkleRoot
|
|
||||||
utxoCommitment := blockOne.Header.UTXOCommitment
|
|
||||||
bits := blockOne.Header.Bits
|
|
||||||
nonce := blockOne.Header.Nonce
|
|
||||||
bh := NewBlockHeader(1, parentHashes, hashMerkleRoot, acceptedIDMerkleRoot, utxoCommitment, bits, nonce)
|
|
||||||
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(17)
|
|
||||||
msg := NewMsgIBDBlock(NewMsgBlock(bh))
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgIBDBlock: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure max payload is expected value for latest protocol version.
|
|
||||||
wantPayload := uint32(1024 * 1024 * 32)
|
|
||||||
maxPayload := msg.MaxPayloadLength(pver)
|
|
||||||
if maxPayload != wantPayload {
|
|
||||||
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
|
||||||
"protocol version %d - got %v, want %v", pver,
|
|
||||||
maxPayload, wantPayload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we get the same block header data back out.
|
|
||||||
if !reflect.DeepEqual(&msg.Header, bh) {
|
|
||||||
t.Errorf("NewMsgIBDBlock: wrong block header - got %v, want %v",
|
|
||||||
spew.Sdump(&msg.Header), spew.Sdump(bh))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure transactions are added properly.
|
|
||||||
tx := blockOne.Transactions[0].Copy()
|
|
||||||
msg.AddTransaction(tx)
|
|
||||||
if !reflect.DeepEqual(msg.Transactions, blockOne.Transactions) {
|
|
||||||
t.Errorf("AddTransaction: wrong transactions - got %v, want %v",
|
|
||||||
spew.Sdump(msg.Transactions),
|
|
||||||
spew.Sdump(blockOne.Transactions))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure transactions are properly cleared.
|
|
||||||
msg.ClearTransactions()
|
|
||||||
if len(msg.Transactions) != 0 {
|
|
||||||
t.Errorf("ClearTransactions: wrong transactions - got %v, want %v",
|
|
||||||
len(msg.Transactions), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestIBDBlockEncoding tests the MsgIBDBlock appmessage encode and decode for various numbers
|
|
||||||
// of transaction inputs and outputs and protocol versions.
|
|
||||||
func TestIBDBlockEncoding(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
in *MsgIBDBlock // Message to encode
|
|
||||||
out *MsgIBDBlock // Expected decoded message
|
|
||||||
buf []byte // Encoded value
|
|
||||||
txLocs []TxLoc // Expected transaction locations
|
|
||||||
pver uint32 // Protocol version for appmessage encoding
|
|
||||||
}{
|
|
||||||
// Latest protocol version.
|
|
||||||
{
|
|
||||||
&MsgIBDBlock{MsgBlock: &blockOne},
|
|
||||||
&MsgIBDBlock{MsgBlock: &blockOne},
|
|
||||||
blockOneBytes,
|
|
||||||
blockOneTxLocs,
|
|
||||||
ProtocolVersion,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("Running %d tests", len(tests))
|
|
||||||
for i, test := range tests {
|
|
||||||
// Encode the message to appmessage format.
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err := test.in.KaspaEncode(&buf, test.pver)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
|
||||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
|
||||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the message from appmessage format.
|
|
||||||
var msg MsgIBDBlock
|
|
||||||
msg.MsgBlock = new(MsgBlock)
|
|
||||||
rbuf := bytes.NewReader(test.buf)
|
|
||||||
err = msg.KaspaDecode(rbuf, test.pver)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(&msg, test.out) {
|
|
||||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
|
||||||
spew.Sdump(&msg), spew.Sdump(test.out))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MsgInvRelayBlock implements the Message interface and represents a kaspa
|
|
||||||
// block inventory message. It is used to notify the network about new block
|
|
||||||
// by sending their hash, and let the receiving node decide if it needs it.
|
|
||||||
type MsgInvRelayBlock struct {
|
|
||||||
baseMessage
|
|
||||||
Hash *daghash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgInvRelayBlock) Command() MessageCommand {
|
|
||||||
return CmdInvRelayBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgInvBlock returns a new kaspa invrelblk message that conforms to
|
|
||||||
// the Message interface. See MsgInvRelayBlock for details.
|
|
||||||
func NewMsgInvBlock(hash *daghash.Hash) *MsgInvRelayBlock {
|
|
||||||
return &MsgInvRelayBlock{
|
|
||||||
Hash: hash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MaxInvPerTxInvMsg is the maximum number of hashes that can
|
|
||||||
// be in a single CmdInvTransaction message.
|
|
||||||
const MaxInvPerTxInvMsg = MaxInvPerMsg
|
|
||||||
|
|
||||||
// MsgInvTransaction implements the Message interface and represents a kaspa
|
|
||||||
// TxInv message. It is used to notify the network about new transactions
|
|
||||||
// by sending their ID, and let the receiving node decide if it needs it.
|
|
||||||
type MsgInvTransaction struct {
|
|
||||||
baseMessage
|
|
||||||
TxIDs []*daghash.TxID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgInvTransaction) Command() MessageCommand {
|
|
||||||
return CmdInvTransaction
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgInvTransaction returns a new kaspa TxInv message that conforms to
|
|
||||||
// the Message interface. See MsgInvTransaction for details.
|
|
||||||
func NewMsgInvTransaction(ids []*daghash.TxID) *MsgInvTransaction {
|
|
||||||
return &MsgInvTransaction{
|
|
||||||
TxIDs: ids,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/random"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestPing tests the MsgPing API against the latest protocol version.
|
|
||||||
func TestPing(t *testing.T) {
|
|
||||||
// Ensure we get the same nonce back out.
|
|
||||||
nonce, err := random.Uint64()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("random.Uint64: Error generating nonce: %v", err)
|
|
||||||
}
|
|
||||||
msg := NewMsgPing(nonce)
|
|
||||||
if msg.Nonce != nonce {
|
|
||||||
t.Errorf("NewMsgPing: wrong nonce - got %v, want %v",
|
|
||||||
msg.Nonce, nonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(7)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgPing: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/random"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestPongLatest tests the MsgPong API against the latest protocol version.
|
|
||||||
func TestPongLatest(t *testing.T) {
|
|
||||||
nonce, err := random.Uint64()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("random.Uint64: error generating nonce: %v", err)
|
|
||||||
}
|
|
||||||
msg := NewMsgPong(nonce)
|
|
||||||
if msg.Nonce != nonce {
|
|
||||||
t.Errorf("NewMsgPong: wrong nonce - got %v, want %v",
|
|
||||||
msg.Nonce, nonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(8)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgPong: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
// MsgReject implements the Message interface and represents a kaspa
|
|
||||||
// Reject message. It is used to notify peers why they are banned.
|
|
||||||
type MsgReject struct {
|
|
||||||
baseMessage
|
|
||||||
Reason string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgReject) Command() MessageCommand {
|
|
||||||
return CmdReject
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgReject returns a new kaspa Reject message that conforms to the
|
|
||||||
// Message interface.
|
|
||||||
func NewMsgReject(reason string) *MsgReject {
|
|
||||||
return &MsgReject{
|
|
||||||
Reason: reason,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// 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 appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MsgRequestAddresses implements the Message interface and represents a kaspa
|
|
||||||
// RequestAddresses message. It is used to request a list of known active peers on the
|
|
||||||
// network from a peer to help identify potential nodes. The list is returned
|
|
||||||
// via one or more addr messages (MsgAddresses).
|
|
||||||
//
|
|
||||||
// This message has no payload.
|
|
||||||
type MsgRequestAddresses struct {
|
|
||||||
baseMessage
|
|
||||||
IncludeAllSubnetworks bool
|
|
||||||
SubnetworkID *subnetworkid.SubnetworkID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgRequestAddresses) Command() MessageCommand {
|
|
||||||
return CmdRequestAddresses
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgRequestAddresses returns a new kaspa RequestAddresses message that conforms to the
|
|
||||||
// Message interface. See MsgRequestAddresses for details.
|
|
||||||
func NewMsgRequestAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgRequestAddresses {
|
|
||||||
return &MsgRequestAddresses{
|
|
||||||
IncludeAllSubnetworks: includeAllSubnetworks,
|
|
||||||
SubnetworkID: subnetworkID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRequestAddresses tests the MsgRequestAddresses API.
|
|
||||||
func TestRequestAddresses(t *testing.T) {
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(2)
|
|
||||||
msg := NewMsgRequestAddresses(false, nil)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgRequestAddresses: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MsgRequestBlockLocator implements the Message interface and represents a kaspa
|
|
||||||
// RequestBlockLocator message. It is used to request a block locator between high
|
|
||||||
// and low hash.
|
|
||||||
// The locator is returned via a locator message (MsgBlockLocator).
|
|
||||||
type MsgRequestBlockLocator struct {
|
|
||||||
baseMessage
|
|
||||||
HighHash *daghash.Hash
|
|
||||||
LowHash *daghash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgRequestBlockLocator) Command() MessageCommand {
|
|
||||||
return CmdRequestBlockLocator
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgRequestBlockLocator returns a new RequestBlockLocator message that conforms to the
|
|
||||||
// Message interface using the passed parameters and defaults for the remaining
|
|
||||||
// fields.
|
|
||||||
func NewMsgRequestBlockLocator(highHash, lowHash *daghash.Hash) *MsgRequestBlockLocator {
|
|
||||||
return &MsgRequestBlockLocator{
|
|
||||||
HighHash: highHash,
|
|
||||||
LowHash: lowHash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRequestBlockLocator tests the MsgRequestBlockLocator API.
|
|
||||||
func TestRequestBlockLocator(t *testing.T) {
|
|
||||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
|
||||||
highHash, err := daghash.NewHashFromStr(hashStr)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewHashFromStr: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(9)
|
|
||||||
msg := NewMsgRequestBlockLocator(highHash, &daghash.ZeroHash)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgRequestBlockLocator: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MsgRequestIBDBlocks implements the Message interface and represents a kaspa
|
|
||||||
// RequestIBDBlocks message. It is used to request a list of blocks starting after the
|
|
||||||
// low hash and until the high hash.
|
|
||||||
type MsgRequestIBDBlocks struct {
|
|
||||||
baseMessage
|
|
||||||
LowHash *daghash.Hash
|
|
||||||
HighHash *daghash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgRequestIBDBlocks) Command() MessageCommand {
|
|
||||||
return CmdRequestIBDBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgRequstIBDBlocks returns a new kaspa RequestIBDBlocks message that conforms to the
|
|
||||||
// Message interface using the passed parameters and defaults for the remaining
|
|
||||||
// fields.
|
|
||||||
func NewMsgRequstIBDBlocks(lowHash, highHash *daghash.Hash) *MsgRequestIBDBlocks {
|
|
||||||
return &MsgRequestIBDBlocks{
|
|
||||||
LowHash: lowHash,
|
|
||||||
HighHash: highHash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRequstIBDBlocks tests the MsgRequestIBDBlocks API.
|
|
||||||
func TestRequstIBDBlocks(t *testing.T) {
|
|
||||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
|
||||||
lowHash, err := daghash.NewHashFromStr(hashStr)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewHashFromStr: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
|
||||||
highHash, err := daghash.NewHashFromStr(hashStr)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewHashFromStr: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we get the same data back out.
|
|
||||||
msg := NewMsgRequstIBDBlocks(lowHash, highHash)
|
|
||||||
if !msg.HighHash.IsEqual(highHash) {
|
|
||||||
t.Errorf("NewMsgRequstIBDBlocks: wrong high hash - got %v, want %v",
|
|
||||||
msg.HighHash, highHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(4)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgRequstIBDBlocks: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
// MsgRequestNextIBDBlocks implements the Message interface and represents a kaspa
|
|
||||||
// RequestNextIBDBlocks message. It is used to notify the IBD syncer peer to send
|
|
||||||
// more blocks.
|
|
||||||
//
|
|
||||||
// This message has no payload.
|
|
||||||
type MsgRequestNextIBDBlocks struct {
|
|
||||||
baseMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgRequestNextIBDBlocks) Command() MessageCommand {
|
|
||||||
return CmdRequestNextIBDBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgRequestNextIBDBlocks returns a new kaspa RequestNextIBDBlocks message that conforms to the
|
|
||||||
// Message interface.
|
|
||||||
func NewMsgRequestNextIBDBlocks() *MsgRequestNextIBDBlocks {
|
|
||||||
return &MsgRequestNextIBDBlocks{}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MsgRequestRelayBlocksHashes is the maximum number of hashes that can
|
|
||||||
// be in a single RequestRelayBlocks message.
|
|
||||||
const MsgRequestRelayBlocksHashes = MaxInvPerMsg
|
|
||||||
|
|
||||||
// MsgRequestRelayBlocks implements the Message interface and represents a kaspa
|
|
||||||
// RequestRelayBlocks message. It is used to request blocks as part of the block
|
|
||||||
// relay protocol.
|
|
||||||
type MsgRequestRelayBlocks struct {
|
|
||||||
baseMessage
|
|
||||||
Hashes []*daghash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgRequestRelayBlocks) Command() MessageCommand {
|
|
||||||
return CmdRequestRelayBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgRequestRelayBlocks returns a new kaspa RequestRelayBlocks message that conforms to
|
|
||||||
// the Message interface. See MsgRequestRelayBlocks for details.
|
|
||||||
func NewMsgRequestRelayBlocks(hashes []*daghash.Hash) *MsgRequestRelayBlocks {
|
|
||||||
return &MsgRequestRelayBlocks{
|
|
||||||
Hashes: hashes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
// MsgRequestSelectedTip implements the Message interface and represents a kaspa
|
|
||||||
// RequestSelectedTip message. It is used to request the selected tip of another peer.
|
|
||||||
//
|
|
||||||
// This message has no payload.
|
|
||||||
type MsgRequestSelectedTip struct {
|
|
||||||
baseMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgRequestSelectedTip) Command() MessageCommand {
|
|
||||||
return CmdRequestSelectedTip
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgRequestSelectedTip returns a new kaspa RequestSelectedTip message that conforms to the
|
|
||||||
// Message interface.
|
|
||||||
func NewMsgRequestSelectedTip() *MsgRequestSelectedTip {
|
|
||||||
return &MsgRequestSelectedTip{}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRequestSelectedTip tests the MsgRequestSelectedTip API.
|
|
||||||
func TestRequestSelectedTip(t *testing.T) {
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(12)
|
|
||||||
msg := NewMsgRequestSelectedTip()
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgRequestSelectedTip: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MaxInvPerRequestTransactionsMsg is the maximum number of hashes that can
|
|
||||||
// be in a single CmdInvTransaction message.
|
|
||||||
const MaxInvPerRequestTransactionsMsg = MaxInvPerMsg
|
|
||||||
|
|
||||||
// MsgRequestTransactions implements the Message interface and represents a kaspa
|
|
||||||
// RequestTransactions message. It is used to request transactions as part of the
|
|
||||||
// transactions relay protocol.
|
|
||||||
type MsgRequestTransactions struct {
|
|
||||||
baseMessage
|
|
||||||
IDs []*daghash.TxID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgRequestTransactions) Command() MessageCommand {
|
|
||||||
return CmdRequestTransactions
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgRequestTransactions returns a new kaspa RequestTransactions message that conforms to
|
|
||||||
// the Message interface. See MsgRequestTransactions for details.
|
|
||||||
func NewMsgRequestTransactions(ids []*daghash.TxID) *MsgRequestTransactions {
|
|
||||||
return &MsgRequestTransactions{
|
|
||||||
IDs: ids,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MsgSelectedTip implements the Message interface and represents a kaspa
|
|
||||||
// selectedtip message. It is used to answer getseltip messages and tell
|
|
||||||
// the asking peer what is the selected tip of this peer.
|
|
||||||
type MsgSelectedTip struct {
|
|
||||||
baseMessage
|
|
||||||
// The selected tip hash of the generator of the message.
|
|
||||||
SelectedTipHash *daghash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgSelectedTip) Command() MessageCommand {
|
|
||||||
return CmdSelectedTip
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgSelectedTip returns a new kaspa selectedtip message that conforms to the
|
|
||||||
// Message interface.
|
|
||||||
func NewMsgSelectedTip(selectedTipHash *daghash.Hash) *MsgSelectedTip {
|
|
||||||
return &MsgSelectedTip{
|
|
||||||
SelectedTipHash: selectedTipHash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestSelectedTip tests the MsgSelectedTip API.
|
|
||||||
func TestSelectedTip(t *testing.T) {
|
|
||||||
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(11)
|
|
||||||
msg := NewMsgSelectedTip(&daghash.ZeroHash)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgSelectedTip: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// 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 appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MsgTransactionNotFound defines a kaspa TransactionNotFound message which is sent in response to
|
|
||||||
// a RequestTransactions message if any of the requested data in not available on the peer.
|
|
||||||
type MsgTransactionNotFound struct {
|
|
||||||
baseMessage
|
|
||||||
ID *daghash.TxID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgTransactionNotFound) Command() MessageCommand {
|
|
||||||
return CmdTransactionNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgTransactionNotFound returns a new kaspa transactionsnotfound message that conforms to the
|
|
||||||
// Message interface. See MsgTransactionNotFound for details.
|
|
||||||
func NewMsgTransactionNotFound(id *daghash.TxID) *MsgTransactionNotFound {
|
|
||||||
return &MsgTransactionNotFound{
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// 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 appmessage
|
|
||||||
|
|
||||||
// MsgVerAck defines a kaspa verack message which is used for a peer to
|
|
||||||
// acknowledge a version message (MsgVersion) after it has used the information
|
|
||||||
// to negotiate parameters. It implements the Message interface.
|
|
||||||
//
|
|
||||||
// This message has no payload.
|
|
||||||
type MsgVerAck struct {
|
|
||||||
baseMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgVerAck) Command() MessageCommand {
|
|
||||||
return CmdVerAck
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgVerAck returns a new kaspa verack message that conforms to the
|
|
||||||
// Message interface.
|
|
||||||
func NewMsgVerAck() *MsgVerAck {
|
|
||||||
return &MsgVerAck{}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestVerAck tests the MsgVerAck API.
|
|
||||||
func TestVerAck(t *testing.T) {
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(1)
|
|
||||||
msg := NewMsgVerAck()
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgVerAck: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/kaspanet/kaspad/version"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
|
||||||
"github.com/kaspanet/kaspad/util/mstime"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MaxUserAgentLen is the maximum allowed length for the user agent field in a
|
|
||||||
// version message (MsgVersion).
|
|
||||||
const MaxUserAgentLen = 256
|
|
||||||
|
|
||||||
// DefaultUserAgent for appmessage in the stack
|
|
||||||
var DefaultUserAgent = fmt.Sprintf("/kaspad:%s/", version.Version())
|
|
||||||
|
|
||||||
// MsgVersion implements the Message interface and represents a kaspa version
|
|
||||||
// message. It is used for a peer to advertise itself as soon as an outbound
|
|
||||||
// connection is made. The remote peer then uses this information along with
|
|
||||||
// its own to negotiate. The remote peer must then respond with a version
|
|
||||||
// message of its own containing the negotiated values followed by a verack
|
|
||||||
// message (MsgVerAck). This exchange must take place before any further
|
|
||||||
// communication is allowed to proceed.
|
|
||||||
type MsgVersion struct {
|
|
||||||
baseMessage
|
|
||||||
// Version of the protocol the node is using.
|
|
||||||
ProtocolVersion uint32
|
|
||||||
|
|
||||||
// The peer's network (mainnet, testnet, etc.)
|
|
||||||
Network string
|
|
||||||
|
|
||||||
// Bitfield which identifies the enabled services.
|
|
||||||
Services ServiceFlag
|
|
||||||
|
|
||||||
// Time the message was generated. This is encoded as an int64 on the appmessage.
|
|
||||||
Timestamp mstime.Time
|
|
||||||
|
|
||||||
// Address of the local peer.
|
|
||||||
Address *NetAddress
|
|
||||||
|
|
||||||
// The peer unique ID
|
|
||||||
ID *id.ID
|
|
||||||
|
|
||||||
// The user agent that generated messsage. This is a encoded as a varString
|
|
||||||
// on the appmessage. This has a max length of MaxUserAgentLen.
|
|
||||||
UserAgent string
|
|
||||||
|
|
||||||
// The selected tip hash of the generator of the version message.
|
|
||||||
SelectedTipHash *daghash.Hash
|
|
||||||
|
|
||||||
// Don't announce transactions to peer.
|
|
||||||
DisableRelayTx bool
|
|
||||||
|
|
||||||
// The subnetwork of the generator of the version message. Should be nil in full nodes
|
|
||||||
SubnetworkID *subnetworkid.SubnetworkID
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasService returns whether the specified service is supported by the peer
|
|
||||||
// that generated the message.
|
|
||||||
func (msg *MsgVersion) HasService(service ServiceFlag) bool {
|
|
||||||
return msg.Services&service == service
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddService adds service as a supported service by the peer generating the
|
|
||||||
// message.
|
|
||||||
func (msg *MsgVersion) AddService(service ServiceFlag) {
|
|
||||||
msg.Services |= service
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the protocol command string for the message. This is part
|
|
||||||
// of the Message interface implementation.
|
|
||||||
func (msg *MsgVersion) Command() MessageCommand {
|
|
||||||
return CmdVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMsgVersion returns a new kaspa version message that conforms to the
|
|
||||||
// Message interface using the passed parameters and defaults for the remaining
|
|
||||||
// fields.
|
|
||||||
func NewMsgVersion(addr *NetAddress, id *id.ID, network string,
|
|
||||||
selectedTipHash *daghash.Hash, subnetworkID *subnetworkid.SubnetworkID) *MsgVersion {
|
|
||||||
|
|
||||||
// Limit the timestamp to one millisecond precision since the protocol
|
|
||||||
// doesn't support better.
|
|
||||||
return &MsgVersion{
|
|
||||||
ProtocolVersion: ProtocolVersion,
|
|
||||||
Network: network,
|
|
||||||
Services: 0,
|
|
||||||
Timestamp: mstime.Now(),
|
|
||||||
Address: addr,
|
|
||||||
ID: id,
|
|
||||||
UserAgent: DefaultUserAgent,
|
|
||||||
SelectedTipHash: selectedTipHash,
|
|
||||||
DisableRelayTx: false,
|
|
||||||
SubnetworkID: subnetworkID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateUserAgent checks userAgent length against MaxUserAgentLen
|
|
||||||
func ValidateUserAgent(userAgent string) error {
|
|
||||||
if len(userAgent) > MaxUserAgentLen {
|
|
||||||
str := fmt.Sprintf("user agent too long [len %d, max %d]",
|
|
||||||
len(userAgent), MaxUserAgentLen)
|
|
||||||
return messageError("MsgVersion", str)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddUserAgent adds a user agent to the user agent string for the version
|
|
||||||
// message. The version string is not defined to any strict format, although
|
|
||||||
// it is recommended to use the form "major.minor.revision" e.g. "2.6.41".
|
|
||||||
func (msg *MsgVersion) AddUserAgent(name string, version string,
|
|
||||||
comments ...string) {
|
|
||||||
|
|
||||||
newUserAgent := fmt.Sprintf("%s:%s", name, version)
|
|
||||||
if len(comments) != 0 {
|
|
||||||
newUserAgent = fmt.Sprintf("%s(%s)", newUserAgent,
|
|
||||||
strings.Join(comments, "; "))
|
|
||||||
}
|
|
||||||
newUserAgent = fmt.Sprintf("%s%s/", msg.UserAgent, newUserAgent)
|
|
||||||
msg.UserAgent = newUserAgent
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestVersion tests the MsgVersion API.
|
|
||||||
func TestVersion(t *testing.T) {
|
|
||||||
pver := ProtocolVersion
|
|
||||||
|
|
||||||
// Create version message data.
|
|
||||||
selectedTipHash := &daghash.Hash{12, 34}
|
|
||||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
|
||||||
me := NewNetAddress(tcpAddrMe, SFNodeNetwork)
|
|
||||||
generatedID, err := id.GenerateID()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("id.GenerateID: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we get the correct data back out.
|
|
||||||
msg := NewMsgVersion(me, generatedID, "mainnet", selectedTipHash, nil)
|
|
||||||
if msg.ProtocolVersion != pver {
|
|
||||||
t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v",
|
|
||||||
msg.ProtocolVersion, pver)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(msg.Address, me) {
|
|
||||||
t.Errorf("NewMsgVersion: wrong me address - got %v, want %v",
|
|
||||||
spew.Sdump(&msg.Address), spew.Sdump(me))
|
|
||||||
}
|
|
||||||
if msg.ID.String() != generatedID.String() {
|
|
||||||
t.Errorf("NewMsgVersion: wrong nonce - got %s, want %s",
|
|
||||||
msg.ID, generatedID)
|
|
||||||
}
|
|
||||||
if msg.UserAgent != DefaultUserAgent {
|
|
||||||
t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v",
|
|
||||||
msg.UserAgent, DefaultUserAgent)
|
|
||||||
}
|
|
||||||
if !msg.SelectedTipHash.IsEqual(selectedTipHash) {
|
|
||||||
t.Errorf("NewMsgVersion: wrong selected tip hash - got %s, want %s",
|
|
||||||
msg.SelectedTipHash, selectedTipHash)
|
|
||||||
}
|
|
||||||
if msg.DisableRelayTx {
|
|
||||||
t.Errorf("NewMsgVersion: disable relay tx is not false by "+
|
|
||||||
"default - got %v, want %v", msg.DisableRelayTx, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.AddUserAgent("myclient", "1.2.3", "optional", "comments")
|
|
||||||
customUserAgent := DefaultUserAgent + "myclient:1.2.3(optional; comments)/"
|
|
||||||
if msg.UserAgent != customUserAgent {
|
|
||||||
t.Errorf("AddUserAgent: wrong user agent - got %s, want %s",
|
|
||||||
msg.UserAgent, customUserAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.AddUserAgent("mygui", "3.4.5")
|
|
||||||
customUserAgent += "mygui:3.4.5/"
|
|
||||||
if msg.UserAgent != customUserAgent {
|
|
||||||
t.Errorf("AddUserAgent: wrong user agent - got %s, want %s",
|
|
||||||
msg.UserAgent, customUserAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version message should not have any services set by default.
|
|
||||||
if msg.Services != 0 {
|
|
||||||
t.Errorf("NewMsgVersion: wrong default services - got %v, want %v",
|
|
||||||
msg.Services, 0)
|
|
||||||
|
|
||||||
}
|
|
||||||
if msg.HasService(SFNodeNetwork) {
|
|
||||||
t.Errorf("HasService: SFNodeNetwork service is set")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the command is expected value.
|
|
||||||
wantCmd := MessageCommand(0)
|
|
||||||
if cmd := msg.Command(); cmd != wantCmd {
|
|
||||||
t.Errorf("NewMsgVersion: wrong command - got %v want %v",
|
|
||||||
cmd, wantCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure adding the full service node flag works.
|
|
||||||
msg.AddService(SFNodeNetwork)
|
|
||||||
if msg.Services != SFNodeNetwork {
|
|
||||||
t.Errorf("AddService: wrong services - got %v, want %v",
|
|
||||||
msg.Services, SFNodeNetwork)
|
|
||||||
}
|
|
||||||
if !msg.HasService(SFNodeNetwork) {
|
|
||||||
t.Errorf("HasService: SFNodeNetwork service not set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// 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 appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/mstime"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NetAddress defines information about a peer on the network including the time
|
|
||||||
// it was last seen, the services it supports, its IP address, and port.
|
|
||||||
type NetAddress struct {
|
|
||||||
// Last time the address was seen.
|
|
||||||
Timestamp mstime.Time
|
|
||||||
|
|
||||||
// Bitfield which identifies the services supported by the address.
|
|
||||||
Services ServiceFlag
|
|
||||||
|
|
||||||
// IP address of the peer.
|
|
||||||
IP net.IP
|
|
||||||
|
|
||||||
// Port the peer is using. This is encoded in big endian on the appmessage
|
|
||||||
// which differs from most everything else.
|
|
||||||
Port uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasService returns whether the specified service is supported by the address.
|
|
||||||
func (na *NetAddress) HasService(service ServiceFlag) bool {
|
|
||||||
return na.Services&service == service
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddService adds service as a supported service by the peer generating the
|
|
||||||
// message.
|
|
||||||
func (na *NetAddress) AddService(service ServiceFlag) {
|
|
||||||
na.Services |= service
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPAddress converts the NetAddress to *net.TCPAddr
|
|
||||||
func (na *NetAddress) TCPAddress() *net.TCPAddr {
|
|
||||||
return &net.TCPAddr{
|
|
||||||
IP: na.IP,
|
|
||||||
Port: int(na.Port),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNetAddressIPPort returns a new NetAddress using the provided IP, port, and
|
|
||||||
// supported services with defaults for the remaining fields.
|
|
||||||
func NewNetAddressIPPort(ip net.IP, port uint16, services ServiceFlag) *NetAddress {
|
|
||||||
return NewNetAddressTimestamp(mstime.Now(), services, ip, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNetAddressTimestamp returns a new NetAddress using the provided
|
|
||||||
// timestamp, IP, port, and supported services. The timestamp is rounded to
|
|
||||||
// single millisecond precision.
|
|
||||||
func NewNetAddressTimestamp(
|
|
||||||
timestamp mstime.Time, services ServiceFlag, ip net.IP, port uint16) *NetAddress {
|
|
||||||
// Limit the timestamp to one millisecond precision since the protocol
|
|
||||||
// doesn't support better.
|
|
||||||
na := NetAddress{
|
|
||||||
Timestamp: timestamp,
|
|
||||||
Services: services,
|
|
||||||
IP: ip,
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
return &na
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNetAddress returns a new NetAddress using the provided TCP address and
|
|
||||||
// supported services with defaults for the remaining fields.
|
|
||||||
func NewNetAddress(addr *net.TCPAddr, services ServiceFlag) *NetAddress {
|
|
||||||
return NewNetAddressIPPort(addr.IP, uint16(addr.Port), services)
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package appmessage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestNetAddress tests the NetAddress API.
|
|
||||||
func TestNetAddress(t *testing.T) {
|
|
||||||
ip := net.ParseIP("127.0.0.1")
|
|
||||||
port := 16111
|
|
||||||
|
|
||||||
// Test NewNetAddress.
|
|
||||||
na := NewNetAddress(&net.TCPAddr{IP: ip, Port: port}, 0)
|
|
||||||
|
|
||||||
// Ensure we get the same ip, port, and services back out.
|
|
||||||
if !na.IP.Equal(ip) {
|
|
||||||
t.Errorf("NetNetAddress: wrong ip - got %v, want %v", na.IP, ip)
|
|
||||||
}
|
|
||||||
if na.Port != uint16(port) {
|
|
||||||
t.Errorf("NetNetAddress: wrong port - got %v, want %v", na.Port,
|
|
||||||
port)
|
|
||||||
}
|
|
||||||
if na.Services != 0 {
|
|
||||||
t.Errorf("NetNetAddress: wrong services - got %v, want %v",
|
|
||||||
na.Services, 0)
|
|
||||||
}
|
|
||||||
if na.HasService(SFNodeNetwork) {
|
|
||||||
t.Errorf("HasService: SFNodeNetwork service is set")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure adding the full service node flag works.
|
|
||||||
na.AddService(SFNodeNetwork)
|
|
||||||
if na.Services != SFNodeNetwork {
|
|
||||||
t.Errorf("AddService: wrong services - got %v, want %v",
|
|
||||||
na.Services, SFNodeNetwork)
|
|
||||||
}
|
|
||||||
if !na.HasService(SFNodeNetwork) {
|
|
||||||
t.Errorf("HasService: SFNodeNetwork service not set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
app/log.go
14
app/log.go
@@ -1,14 +0,0 @@
|
|||||||
// Copyright (c) 2013-2017 The btcsuite developers
|
|
||||||
// Copyright (c) 2017 The Decred developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.KASD)
|
|
||||||
var spawn = panics.GoroutineWrapperFunc(log)
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// Copyright (c) 2015-2017 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package blocklogger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/mstime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
receivedLogBlocks int64
|
|
||||||
receivedLogTx int64
|
|
||||||
lastBlockLogTime = mstime.Now()
|
|
||||||
mtx sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogBlock logs a new block blue score as an information message
|
|
||||||
// to show progress to the user. In order to prevent spam, it limits logging to
|
|
||||||
// one message every 10 seconds with duration and totals included.
|
|
||||||
func LogBlock(block *util.Block) error {
|
|
||||||
mtx.Lock()
|
|
||||||
defer mtx.Unlock()
|
|
||||||
|
|
||||||
receivedLogBlocks++
|
|
||||||
receivedLogTx += int64(len(block.MsgBlock().Transactions))
|
|
||||||
|
|
||||||
now := mstime.Now()
|
|
||||||
duration := now.Sub(lastBlockLogTime)
|
|
||||||
if duration < time.Second*10 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Truncate the duration to 10s of milliseconds.
|
|
||||||
tDuration := duration.Round(10 * time.Millisecond)
|
|
||||||
|
|
||||||
// Log information about new block blue score.
|
|
||||||
blockStr := "blocks"
|
|
||||||
if receivedLogBlocks == 1 {
|
|
||||||
blockStr = "block"
|
|
||||||
}
|
|
||||||
txStr := "transactions"
|
|
||||||
if receivedLogTx == 1 {
|
|
||||||
txStr = "transaction"
|
|
||||||
}
|
|
||||||
|
|
||||||
blueScore, err := block.BlueScore()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Processed %d %s in the last %s (%d %s, blue score %d, %s)",
|
|
||||||
receivedLogBlocks, blockStr, tDuration, receivedLogTx,
|
|
||||||
txStr, blueScore, block.MsgBlock().Header.Timestamp)
|
|
||||||
|
|
||||||
receivedLogBlocks = 0
|
|
||||||
receivedLogTx = 0
|
|
||||||
lastBlockLogTime = now
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// Copyright (c) 2017 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package blocklogger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.PROT)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultTimeout is the default duration to wait for enqueuing/dequeuing
|
|
||||||
// to/from routes.
|
|
||||||
const DefaultTimeout = 30 * time.Second
|
|
||||||
|
|
||||||
// ErrPeerWithSameIDExists signifies that a peer with the same ID already exist.
|
|
||||||
var ErrPeerWithSameIDExists = errors.New("ready peer with the same ID already exists")
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddressManager returns the address manager associated to the flow context.
|
|
||||||
func (f *FlowContext) AddressManager() *addressmanager.AddressManager {
|
|
||||||
return f.addressManager
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OnNewBlock updates the mempool after a new block arrival, and
|
|
||||||
// relays newly unorphaned transactions and possibly rebroadcast
|
|
||||||
// manually added transactions when not in IBD.
|
|
||||||
func (f *FlowContext) OnNewBlock(block *util.Block) error {
|
|
||||||
transactionsAcceptedToMempool, err := f.txPool.HandleNewBlock(block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.broadcastTransactionsAfterBlockAdded(block, transactionsAcceptedToMempool)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(block *util.Block, transactionsAcceptedToMempool []*util.Tx) error {
|
|
||||||
f.updateTransactionsToRebroadcast(block)
|
|
||||||
|
|
||||||
// Don't relay transactions when in IBD.
|
|
||||||
if atomic.LoadUint32(&f.isInIBD) != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var txIDsToRebroadcast []*daghash.TxID
|
|
||||||
if f.shouldRebroadcastTransactions() {
|
|
||||||
txIDsToRebroadcast = f.txIDsToRebroadcast()
|
|
||||||
}
|
|
||||||
|
|
||||||
txIDsToBroadcast := make([]*daghash.TxID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast))
|
|
||||||
for i, tx := range transactionsAcceptedToMempool {
|
|
||||||
txIDsToBroadcast[i] = tx.ID()
|
|
||||||
}
|
|
||||||
offset := len(transactionsAcceptedToMempool)
|
|
||||||
for i, txID := range txIDsToRebroadcast {
|
|
||||||
txIDsToBroadcast[offset+i] = txID
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(txIDsToBroadcast) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(txIDsToBroadcast) > appmessage.MaxInvPerTxInvMsg {
|
|
||||||
txIDsToBroadcast = txIDsToBroadcast[:appmessage.MaxInvPerTxInvMsg]
|
|
||||||
}
|
|
||||||
inv := appmessage.NewMsgInvTransaction(txIDsToBroadcast)
|
|
||||||
return f.Broadcast(inv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SharedRequestedBlocks returns a *blockrelay.SharedRequestedBlocks for sharing
|
|
||||||
// data about requested blocks between different peers.
|
|
||||||
func (f *FlowContext) SharedRequestedBlocks() *blockrelay.SharedRequestedBlocks {
|
|
||||||
return f.sharedRequestedBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBlock adds the given block to the DAG and propagates it.
|
|
||||||
func (f *FlowContext) AddBlock(block *util.Block, flags blockdag.BehaviorFlags) error {
|
|
||||||
_, _, err := f.DAG().ProcessBlock(block, flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = f.OnNewBlock(block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return f.Broadcast(appmessage.NewMsgInvBlock(block.Hash()))
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import "github.com/kaspanet/kaspad/infrastructure/config"
|
|
||||||
|
|
||||||
// Config returns an instance of *config.Config associated to the flow context.
|
|
||||||
func (f *FlowContext) Config() *config.Config {
|
|
||||||
return f.cfg
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import "github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
|
|
||||||
// DAG returns the DAG associated to the flow context.
|
|
||||||
func (f *FlowContext) DAG() *blockdag.BlockDAG {
|
|
||||||
return f.dag
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleError handles an error from a flow,
|
|
||||||
// It sends the error to errChan if isStopping == 0 and increments isStopping
|
|
||||||
//
|
|
||||||
// If this is ErrRouteClosed - forward it to errChan
|
|
||||||
// If this is ProtocolError - logs the error, and forward it to errChan
|
|
||||||
// Otherwise - panics
|
|
||||||
func (*FlowContext) HandleError(err error, flowName string, isStopping *uint32, errChan chan<- error) {
|
|
||||||
isErrRouteClosed := errors.Is(err, router.ErrRouteClosed)
|
|
||||||
if !isErrRouteClosed {
|
|
||||||
if protocolErr := &(protocolerrors.ProtocolError{}); !errors.As(err, &protocolErr) {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Errorf("error from %s: %s", flowName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if atomic.AddUint32(isStopping, 1) == 1 {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/domain/mempool"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlowContext holds state that is relevant to more than one flow or one peer, and allows communication between
|
|
||||||
// different flows that can be associated to different peers.
|
|
||||||
type FlowContext struct {
|
|
||||||
cfg *config.Config
|
|
||||||
netAdapter *netadapter.NetAdapter
|
|
||||||
txPool *mempool.TxPool
|
|
||||||
dag *blockdag.BlockDAG
|
|
||||||
addressManager *addressmanager.AddressManager
|
|
||||||
connectionManager *connmanager.ConnectionManager
|
|
||||||
|
|
||||||
transactionsToRebroadcastLock sync.Mutex
|
|
||||||
transactionsToRebroadcast map[daghash.TxID]*util.Tx
|
|
||||||
lastRebroadcastTime time.Time
|
|
||||||
sharedRequestedTransactions *relaytransactions.SharedRequestedTransactions
|
|
||||||
|
|
||||||
sharedRequestedBlocks *blockrelay.SharedRequestedBlocks
|
|
||||||
|
|
||||||
isInIBD uint32
|
|
||||||
startIBDMutex sync.Mutex
|
|
||||||
ibdPeer *peerpkg.Peer
|
|
||||||
|
|
||||||
peers map[id.ID]*peerpkg.Peer
|
|
||||||
peersMutex sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new instance of FlowContext.
|
|
||||||
func New(cfg *config.Config, dag *blockdag.BlockDAG, addressManager *addressmanager.AddressManager,
|
|
||||||
txPool *mempool.TxPool, netAdapter *netadapter.NetAdapter,
|
|
||||||
connectionManager *connmanager.ConnectionManager) *FlowContext {
|
|
||||||
|
|
||||||
return &FlowContext{
|
|
||||||
cfg: cfg,
|
|
||||||
netAdapter: netAdapter,
|
|
||||||
dag: dag,
|
|
||||||
addressManager: addressManager,
|
|
||||||
connectionManager: connectionManager,
|
|
||||||
txPool: txPool,
|
|
||||||
sharedRequestedTransactions: relaytransactions.NewSharedRequestedTransactions(),
|
|
||||||
sharedRequestedBlocks: blockrelay.NewSharedRequestedBlocks(),
|
|
||||||
peers: make(map[id.ID]*peerpkg.Peer),
|
|
||||||
transactionsToRebroadcast: make(map[daghash.TxID]*util.Tx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StartIBDIfRequired selects a peer and starts IBD against it
|
|
||||||
// if required
|
|
||||||
func (f *FlowContext) StartIBDIfRequired() {
|
|
||||||
f.startIBDMutex.Lock()
|
|
||||||
defer f.startIBDMutex.Unlock()
|
|
||||||
|
|
||||||
if f.IsInIBD() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
peer := f.selectPeerForIBD(f.dag)
|
|
||||||
if peer == nil {
|
|
||||||
spawn("StartIBDIfRequired-requestSelectedTipsIfRequired", f.requestSelectedTipsIfRequired)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.StoreUint32(&f.isInIBD, 1)
|
|
||||||
f.ibdPeer = peer
|
|
||||||
spawn("StartIBDIfRequired-peer.StartIBD", peer.StartIBD)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInIBD is true if IBD is currently running
|
|
||||||
func (f *FlowContext) IsInIBD() bool {
|
|
||||||
return atomic.LoadUint32(&f.isInIBD) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectPeerForIBD returns the first peer whose selected tip
|
|
||||||
// hash is not in our DAG
|
|
||||||
func (f *FlowContext) selectPeerForIBD(dag *blockdag.BlockDAG) *peerpkg.Peer {
|
|
||||||
f.peersMutex.RLock()
|
|
||||||
defer f.peersMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, peer := range f.peers {
|
|
||||||
peerSelectedTipHash := peer.SelectedTipHash()
|
|
||||||
if !dag.IsInDAG(peerSelectedTipHash) {
|
|
||||||
return peer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FlowContext) requestSelectedTipsIfRequired() {
|
|
||||||
if f.isDAGTimeCurrent() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.requestSelectedTips()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FlowContext) isDAGTimeCurrent() bool {
|
|
||||||
const minDurationToRequestSelectedTips = time.Minute
|
|
||||||
return f.dag.Now().Sub(f.dag.SelectedTipHeader().Timestamp) > minDurationToRequestSelectedTips
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FlowContext) requestSelectedTips() {
|
|
||||||
f.peersMutex.RLock()
|
|
||||||
defer f.peersMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, peer := range f.peers {
|
|
||||||
peer.RequestSelectedTipIfRequired()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FinishIBD finishes the current IBD flow and starts a new one if required.
|
|
||||||
func (f *FlowContext) FinishIBD() {
|
|
||||||
f.ibdPeer = nil
|
|
||||||
|
|
||||||
atomic.StoreUint32(&f.isInIBD, 0)
|
|
||||||
|
|
||||||
f.StartIBDIfRequired()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IBDPeer returns the currently active IBD peer.
|
|
||||||
// Returns nil if we aren't currently in IBD
|
|
||||||
func (f *FlowContext) IBDPeer() *peerpkg.Peer {
|
|
||||||
if !f.IsInIBD() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return f.ibdPeer
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.PROT)
|
|
||||||
var spawn = panics.GoroutineWrapperFunc(log)
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NetAdapter returns the net adapter that is associated to the flow context.
|
|
||||||
func (f *FlowContext) NetAdapter() *netadapter.NetAdapter {
|
|
||||||
return f.netAdapter
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectionManager returns the connection manager that is associated to the flow context.
|
|
||||||
func (f *FlowContext) ConnectionManager() *connmanager.ConnectionManager {
|
|
||||||
return f.connectionManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddToPeers marks this peer as ready and adds it to the ready peers list.
|
|
||||||
func (f *FlowContext) AddToPeers(peer *peerpkg.Peer) error {
|
|
||||||
f.peersMutex.Lock()
|
|
||||||
defer f.peersMutex.Unlock()
|
|
||||||
|
|
||||||
if _, ok := f.peers[*peer.ID()]; ok {
|
|
||||||
return errors.Wrapf(common.ErrPeerWithSameIDExists, "peer with ID %s already exists", peer.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
f.peers[*peer.ID()] = peer
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveFromPeers remove this peer from the peers list.
|
|
||||||
func (f *FlowContext) RemoveFromPeers(peer *peerpkg.Peer) {
|
|
||||||
f.peersMutex.Lock()
|
|
||||||
defer f.peersMutex.Unlock()
|
|
||||||
|
|
||||||
delete(f.peers, *peer.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// readyPeerConnections returns the NetConnections of all the ready peers.
|
|
||||||
func (f *FlowContext) readyPeerConnections() []*netadapter.NetConnection {
|
|
||||||
f.peersMutex.RLock()
|
|
||||||
defer f.peersMutex.RUnlock()
|
|
||||||
peerConnections := make([]*netadapter.NetConnection, len(f.peers))
|
|
||||||
i := 0
|
|
||||||
for _, peer := range f.peers {
|
|
||||||
peerConnections[i] = peer.Connection()
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return peerConnections
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast broadcast the given message to all the ready peers.
|
|
||||||
func (f *FlowContext) Broadcast(message appmessage.Message) error {
|
|
||||||
return f.netAdapter.Broadcast(f.readyPeerConnections(), message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peers returns the currently active peers
|
|
||||||
func (f *FlowContext) Peers() []*peerpkg.Peer {
|
|
||||||
f.peersMutex.RLock()
|
|
||||||
defer f.peersMutex.RUnlock()
|
|
||||||
|
|
||||||
peers := make([]*peerpkg.Peer, len(f.peers))
|
|
||||||
i := 0
|
|
||||||
for _, peer := range f.peers {
|
|
||||||
peers[i] = peer
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return peers
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package flowcontext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
|
|
||||||
"github.com/kaspanet/kaspad/domain/mempool"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddTransaction adds transaction to the mempool and propagates it.
|
|
||||||
func (f *FlowContext) AddTransaction(tx *util.Tx) error {
|
|
||||||
f.transactionsToRebroadcastLock.Lock()
|
|
||||||
defer f.transactionsToRebroadcastLock.Unlock()
|
|
||||||
|
|
||||||
transactionsAcceptedToMempool, err := f.txPool.ProcessTransaction(tx, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(transactionsAcceptedToMempool) > 1 {
|
|
||||||
return errors.New("got more than one accepted transactions when no orphans were allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
f.transactionsToRebroadcast[*tx.ID()] = tx
|
|
||||||
inv := appmessage.NewMsgInvTransaction([]*daghash.TxID{tx.ID()})
|
|
||||||
log.Criticalf("~~~~~ FlowContext.AddTransaction() broadcasting %s", tx.ID())
|
|
||||||
return f.Broadcast(inv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FlowContext) updateTransactionsToRebroadcast(block *util.Block) {
|
|
||||||
f.transactionsToRebroadcastLock.Lock()
|
|
||||||
defer f.transactionsToRebroadcastLock.Unlock()
|
|
||||||
// Note: if the block is red, its transactions won't be rebroadcasted
|
|
||||||
// anymore, although they are not included in the UTXO set.
|
|
||||||
// This is probably ok, since red blocks are quite rare.
|
|
||||||
for _, tx := range block.Transactions() {
|
|
||||||
delete(f.transactionsToRebroadcast, *tx.ID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FlowContext) shouldRebroadcastTransactions() bool {
|
|
||||||
const rebroadcastInterval = 30 * time.Second
|
|
||||||
return time.Since(f.lastRebroadcastTime) > rebroadcastInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FlowContext) txIDsToRebroadcast() []*daghash.TxID {
|
|
||||||
f.transactionsToRebroadcastLock.Lock()
|
|
||||||
defer f.transactionsToRebroadcastLock.Unlock()
|
|
||||||
|
|
||||||
txIDs := make([]*daghash.TxID, len(f.transactionsToRebroadcast))
|
|
||||||
i := 0
|
|
||||||
for _, tx := range f.transactionsToRebroadcast {
|
|
||||||
txIDs[i] = tx.ID()
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return txIDs
|
|
||||||
}
|
|
||||||
|
|
||||||
// SharedRequestedTransactions returns a *relaytransactions.SharedRequestedTransactions for sharing
|
|
||||||
// data about requested transactions between different peers.
|
|
||||||
func (f *FlowContext) SharedRequestedTransactions() *relaytransactions.SharedRequestedTransactions {
|
|
||||||
return f.sharedRequestedTransactions
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxPool returns the transaction pool associated to the manager.
|
|
||||||
func (f *FlowContext) TxPool() *mempool.TxPool {
|
|
||||||
return f.txPool
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package addressexchange
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReceiveAddressesContext is the interface for the context needed for the ReceiveAddresses flow.
|
|
||||||
type ReceiveAddressesContext interface {
|
|
||||||
Config() *config.Config
|
|
||||||
AddressManager() *addressmanager.AddressManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiveAddresses asks a peer for more addresses if needed.
|
|
||||||
func ReceiveAddresses(context ReceiveAddressesContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
|
||||||
peer *peerpkg.Peer) error {
|
|
||||||
|
|
||||||
if !context.AddressManager().NeedMoreAddresses() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
subnetworkID := peer.SubnetworkID()
|
|
||||||
msgGetAddresses := appmessage.NewMsgRequestAddresses(false, subnetworkID)
|
|
||||||
err := outgoingRoute.Enqueue(msgGetAddresses)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
message, err := incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgAddresses := message.(*appmessage.MsgAddresses)
|
|
||||||
if len(msgAddresses.AddrList) > addressmanager.GetAddressesMax {
|
|
||||||
return protocolerrors.Errorf(true, "address count exceeded %d", addressmanager.GetAddressesMax)
|
|
||||||
}
|
|
||||||
|
|
||||||
if msgAddresses.IncludeAllSubnetworks {
|
|
||||||
return protocolerrors.Errorf(true, "got unexpected "+
|
|
||||||
"IncludeAllSubnetworks=true in [%s] command", msgAddresses.Command())
|
|
||||||
}
|
|
||||||
if !msgAddresses.SubnetworkID.IsEqual(context.Config().SubnetworkID) && msgAddresses.SubnetworkID != nil {
|
|
||||||
return protocolerrors.Errorf(false, "only full nodes and %s subnetwork IDs "+
|
|
||||||
"are allowed in [%s] command, but got subnetwork ID %s",
|
|
||||||
context.Config().SubnetworkID, msgAddresses.Command(), msgAddresses.SubnetworkID)
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceAddress := peer.Connection().NetAddress()
|
|
||||||
context.AddressManager().AddAddresses(msgAddresses.AddrList, sourceAddress, msgAddresses.SubnetworkID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package addressexchange
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendAddressesContext is the interface for the context needed for the SendAddresses flow.
|
|
||||||
type SendAddressesContext interface {
|
|
||||||
AddressManager() *addressmanager.AddressManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendAddresses sends addresses to a peer that requests it.
|
|
||||||
func SendAddresses(context SendAddressesContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
|
||||||
message, err := incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgGetAddresses := message.(*appmessage.MsgRequestAddresses)
|
|
||||||
addresses := context.AddressManager().AddressCache(msgGetAddresses.IncludeAllSubnetworks,
|
|
||||||
msgGetAddresses.SubnetworkID)
|
|
||||||
msgAddresses := appmessage.NewMsgAddresses(msgGetAddresses.IncludeAllSubnetworks, msgGetAddresses.SubnetworkID)
|
|
||||||
err = msgAddresses.AddAddresses(shuffleAddresses(addresses)...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return outgoingRoute.Enqueue(msgAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
// shuffleAddresses randomizes the given addresses sent if there are more than the maximum allowed in one message.
|
|
||||||
func shuffleAddresses(addresses []*appmessage.NetAddress) []*appmessage.NetAddress {
|
|
||||||
addressCount := len(addresses)
|
|
||||||
|
|
||||||
if addressCount < appmessage.MaxAddressesPerMsg {
|
|
||||||
return addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
shuffleAddresses := make([]*appmessage.NetAddress, addressCount)
|
|
||||||
copy(shuffleAddresses, addresses)
|
|
||||||
|
|
||||||
rand.Shuffle(addressCount, func(i, j int) {
|
|
||||||
shuffleAddresses[i], shuffleAddresses[j] = shuffleAddresses[j], shuffleAddresses[i]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Truncate it to the maximum size.
|
|
||||||
shuffleAddresses = shuffleAddresses[:appmessage.MaxAddressesPerMsg]
|
|
||||||
return shuffleAddresses
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package blockrelay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RelayBlockRequestsContext is the interface for the context needed for the HandleRelayBlockRequests flow.
|
|
||||||
type RelayBlockRequestsContext interface {
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRelayBlockRequests listens to appmessage.MsgRequestRelayBlocks messages and sends
|
|
||||||
// their corresponding blocks to the requesting peer.
|
|
||||||
func HandleRelayBlockRequests(context RelayBlockRequestsContext, incomingRoute *router.Route,
|
|
||||||
outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
|
||||||
|
|
||||||
for {
|
|
||||||
message, err := incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
getRelayBlocksMessage := message.(*appmessage.MsgRequestRelayBlocks)
|
|
||||||
for _, hash := range getRelayBlocksMessage.Hashes {
|
|
||||||
// Fetch the block from the database.
|
|
||||||
block, err := context.DAG().BlockByHash(hash)
|
|
||||||
if blockdag.IsNotInDAGErr(err) {
|
|
||||||
return protocolerrors.Errorf(true, "block %s not found", hash)
|
|
||||||
} else if err != nil {
|
|
||||||
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
|
|
||||||
}
|
|
||||||
msgBlock := block.MsgBlock()
|
|
||||||
|
|
||||||
// If we are a full node and the peer is a partial node, we must convert
|
|
||||||
// the block to a partial block.
|
|
||||||
nodeSubnetworkID := context.DAG().SubnetworkID()
|
|
||||||
peerSubnetworkID := peer.SubnetworkID()
|
|
||||||
|
|
||||||
isNodeFull := nodeSubnetworkID == nil
|
|
||||||
isPeerFull := peerSubnetworkID == nil
|
|
||||||
if isNodeFull && !isPeerFull {
|
|
||||||
msgBlock.ConvertToPartial(peerSubnetworkID)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = outgoingRoute.Enqueue(msgBlock)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
package blockrelay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/blocklogger"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
mathUtil "github.com/kaspanet/kaspad/util/math"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RelayInvsContext is the interface for the context needed for the HandleRelayInvs flow.
|
|
||||||
type RelayInvsContext interface {
|
|
||||||
NetAdapter() *netadapter.NetAdapter
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
OnNewBlock(block *util.Block) error
|
|
||||||
SharedRequestedBlocks() *SharedRequestedBlocks
|
|
||||||
StartIBDIfRequired()
|
|
||||||
IsInIBD() bool
|
|
||||||
Broadcast(message appmessage.Message) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type handleRelayInvsFlow struct {
|
|
||||||
RelayInvsContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
peer *peerpkg.Peer
|
|
||||||
invsQueue []*appmessage.MsgInvRelayBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRelayInvs listens to appmessage.MsgInvRelayBlock messages, requests their corresponding blocks if they
|
|
||||||
// are missing, adds them to the DAG and propagates them to the rest of the network.
|
|
||||||
func HandleRelayInvs(context RelayInvsContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
|
||||||
peer *peerpkg.Peer) error {
|
|
||||||
|
|
||||||
flow := &handleRelayInvsFlow{
|
|
||||||
RelayInvsContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
peer: peer,
|
|
||||||
invsQueue: make([]*appmessage.MsgInvRelayBlock, 0),
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayInvsFlow) start() error {
|
|
||||||
for {
|
|
||||||
inv, err := flow.readInv()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Got relay inv for block %s", inv.Hash)
|
|
||||||
|
|
||||||
if flow.DAG().IsKnownBlock(inv.Hash) {
|
|
||||||
if flow.DAG().IsKnownInvalid(inv.Hash) {
|
|
||||||
return protocolerrors.Errorf(true, "sent inv of an invalid block %s",
|
|
||||||
inv.Hash)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
flow.StartIBDIfRequired()
|
|
||||||
if flow.IsInIBD() {
|
|
||||||
// Block relay is disabled during IBD
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
requestQueue := newHashesQueueSet()
|
|
||||||
requestQueue.enqueueIfNotExists(inv.Hash)
|
|
||||||
|
|
||||||
for requestQueue.len() > 0 {
|
|
||||||
err := flow.requestBlocks(requestQueue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayInvsFlow) readInv() (*appmessage.MsgInvRelayBlock, error) {
|
|
||||||
|
|
||||||
if len(flow.invsQueue) > 0 {
|
|
||||||
var inv *appmessage.MsgInvRelayBlock
|
|
||||||
inv, flow.invsQueue = flow.invsQueue[0], flow.invsQueue[1:]
|
|
||||||
return inv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := flow.incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
inv, ok := msg.(*appmessage.MsgInvRelayBlock)
|
|
||||||
if !ok {
|
|
||||||
return nil, protocolerrors.Errorf(true, "unexpected %s message in the block relay handleRelayInvsFlow while "+
|
|
||||||
"expecting an inv message", msg.Command())
|
|
||||||
}
|
|
||||||
return inv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) error {
|
|
||||||
numHashesToRequest := mathUtil.MinInt(appmessage.MsgRequestRelayBlocksHashes, requestQueue.len())
|
|
||||||
hashesToRequest := requestQueue.dequeue(numHashesToRequest)
|
|
||||||
|
|
||||||
pendingBlocks := map[daghash.Hash]struct{}{}
|
|
||||||
var filteredHashesToRequest []*daghash.Hash
|
|
||||||
for _, hash := range hashesToRequest {
|
|
||||||
exists := flow.SharedRequestedBlocks().addIfNotExists(hash)
|
|
||||||
if exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// The block can become known from another peer in the process of orphan resolution
|
|
||||||
if flow.DAG().IsKnownBlock(hash) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingBlocks[*hash] = struct{}{}
|
|
||||||
filteredHashesToRequest = append(filteredHashesToRequest, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit early if we've filtered out all the hashes
|
|
||||||
if len(filteredHashesToRequest) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case the function returns earlier than expected, we want to make sure requestedBlocks is
|
|
||||||
// clean from any pending blocks.
|
|
||||||
defer flow.SharedRequestedBlocks().removeSet(pendingBlocks)
|
|
||||||
|
|
||||||
getRelayBlocksMsg := appmessage.NewMsgRequestRelayBlocks(filteredHashesToRequest)
|
|
||||||
err := flow.outgoingRoute.Enqueue(getRelayBlocksMsg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(pendingBlocks) > 0 {
|
|
||||||
msgBlock, err := flow.readMsgBlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
block := util.NewBlock(msgBlock)
|
|
||||||
blockHash := block.Hash()
|
|
||||||
|
|
||||||
if _, ok := pendingBlocks[*blockHash]; !ok {
|
|
||||||
return protocolerrors.Errorf(true, "got unrequested block %s", block.Hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = flow.processAndRelayBlock(requestQueue, block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(pendingBlocks, *blockHash)
|
|
||||||
flow.SharedRequestedBlocks().remove(blockHash)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readMsgBlock returns the next msgBlock in msgChan, and populates invsQueue with any inv messages that meanwhile arrive.
|
|
||||||
//
|
|
||||||
// Note: this function assumes msgChan can contain only appmessage.MsgInvRelayBlock and appmessage.MsgBlock messages.
|
|
||||||
func (flow *handleRelayInvsFlow) readMsgBlock() (
|
|
||||||
msgBlock *appmessage.MsgBlock, err error) {
|
|
||||||
|
|
||||||
for {
|
|
||||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch message := message.(type) {
|
|
||||||
case *appmessage.MsgInvRelayBlock:
|
|
||||||
flow.invsQueue = append(flow.invsQueue, message)
|
|
||||||
case *appmessage.MsgBlock:
|
|
||||||
return message, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.Errorf("unexpected message %s", message.Command())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayInvsFlow) processAndRelayBlock(requestQueue *hashesQueueSet, block *util.Block) error {
|
|
||||||
blockHash := block.Hash()
|
|
||||||
isOrphan, isDelayed, err := flow.DAG().ProcessBlock(block, blockdag.BFNone)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.As(err, &blockdag.RuleError{}) {
|
|
||||||
return errors.Wrapf(err, "failed to process block %s", blockHash)
|
|
||||||
}
|
|
||||||
log.Infof("Rejected block %s from %s: %s", blockHash, flow.peer, err)
|
|
||||||
|
|
||||||
return protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDelayed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if isOrphan {
|
|
||||||
blueScore, err := block.BlueScore()
|
|
||||||
if err != nil {
|
|
||||||
return protocolerrors.Errorf(true, "received an orphan "+
|
|
||||||
"block %s with malformed blue score", blockHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxOrphanBlueScoreDiff = 10000
|
|
||||||
selectedTipBlueScore := flow.DAG().SelectedTipBlueScore()
|
|
||||||
if blueScore > selectedTipBlueScore+maxOrphanBlueScoreDiff {
|
|
||||||
log.Infof("Orphan block %s has blue score %d and the selected tip blue score is "+
|
|
||||||
"%d. Ignoring orphans with a blue score difference from the selected tip greater than %d",
|
|
||||||
blockHash, blueScore, selectedTipBlueScore, maxOrphanBlueScoreDiff)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request the parents for the orphan block from the peer that sent it.
|
|
||||||
missingAncestors := flow.DAG().GetOrphanMissingAncestorHashes(blockHash)
|
|
||||||
for _, missingAncestor := range missingAncestors {
|
|
||||||
requestQueue.enqueueIfNotExists(missingAncestor)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = blocklogger.LogBlock(block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = flow.Broadcast(appmessage.NewMsgInvBlock(blockHash))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
flow.StartIBDIfRequired()
|
|
||||||
err = flow.OnNewBlock(block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package blockrelay
|
|
||||||
|
|
||||||
import "github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
|
|
||||||
type hashesQueueSet struct {
|
|
||||||
queue []*daghash.Hash
|
|
||||||
set map[daghash.Hash]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *hashesQueueSet) enqueueIfNotExists(hash *daghash.Hash) {
|
|
||||||
if _, ok := r.set[*hash]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.queue = append(r.queue, hash)
|
|
||||||
r.set[*hash] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *hashesQueueSet) dequeue(numItems int) []*daghash.Hash {
|
|
||||||
var hashes []*daghash.Hash
|
|
||||||
hashes, r.queue = r.queue[:numItems], r.queue[numItems:]
|
|
||||||
for _, hash := range hashes {
|
|
||||||
delete(r.set, *hash)
|
|
||||||
}
|
|
||||||
return hashes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *hashesQueueSet) len() int {
|
|
||||||
return len(r.queue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHashesQueueSet() *hashesQueueSet {
|
|
||||||
return &hashesQueueSet{
|
|
||||||
set: make(map[daghash.Hash]struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package blockrelay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.PROT)
|
|
||||||
var spawn = panics.GoroutineWrapperFunc(log)
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package blockrelay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SharedRequestedBlocks is a data structure that is shared between peers that
|
|
||||||
// holds the hashes of all the requested blocks to prevent redundant requests.
|
|
||||||
type SharedRequestedBlocks struct {
|
|
||||||
blocks map[daghash.Hash]struct{}
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SharedRequestedBlocks) remove(hash *daghash.Hash) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
delete(s.blocks, *hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SharedRequestedBlocks) removeSet(blockHashes map[daghash.Hash]struct{}) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
for hash := range blockHashes {
|
|
||||||
delete(s.blocks, hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SharedRequestedBlocks) addIfNotExists(hash *daghash.Hash) (exists bool) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
_, ok := s.blocks[*hash]
|
|
||||||
if ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
s.blocks[*hash] = struct{}{}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSharedRequestedBlocks returns a new instance of SharedRequestedBlocks.
|
|
||||||
func NewSharedRequestedBlocks() *SharedRequestedBlocks {
|
|
||||||
return &SharedRequestedBlocks{
|
|
||||||
blocks: make(map[daghash.Hash]struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package handshake
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/util/locks"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleHandshakeContext is the interface for the context needed for the HandleHandshake flow.
|
|
||||||
type HandleHandshakeContext interface {
|
|
||||||
Config() *config.Config
|
|
||||||
NetAdapter() *netadapter.NetAdapter
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
AddressManager() *addressmanager.AddressManager
|
|
||||||
StartIBDIfRequired()
|
|
||||||
AddToPeers(peer *peerpkg.Peer) error
|
|
||||||
HandleError(err error, flowName string, isStopping *uint32, errChan chan<- error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleHandshake sets up the handshake protocol - It sends a version message and waits for an incoming
|
|
||||||
// version message, as well as a verack for the sent version
|
|
||||||
func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.NetConnection,
|
|
||||||
receiveVersionRoute *routerpkg.Route, sendVersionRoute *routerpkg.Route, outgoingRoute *routerpkg.Route,
|
|
||||||
) (*peerpkg.Peer, error) {
|
|
||||||
|
|
||||||
// For HandleHandshake to finish, we need to get from the other node
|
|
||||||
// a version and verack messages, so we increase the wait group by 2
|
|
||||||
// and block HandleHandshake with wg.Wait().
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(2)
|
|
||||||
|
|
||||||
isStopping := uint32(0)
|
|
||||||
errChan := make(chan error)
|
|
||||||
|
|
||||||
peer := peerpkg.New(netConnection)
|
|
||||||
|
|
||||||
var peerAddress *appmessage.NetAddress
|
|
||||||
spawn("HandleHandshake-ReceiveVersion", func() {
|
|
||||||
address, err := ReceiveVersion(context, receiveVersionRoute, outgoingRoute, peer)
|
|
||||||
if err != nil {
|
|
||||||
handleError(err, "ReceiveVersion", &isStopping, errChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
peerAddress = address
|
|
||||||
wg.Done()
|
|
||||||
})
|
|
||||||
|
|
||||||
spawn("HandleHandshake-SendVersion", func() {
|
|
||||||
err := SendVersion(context, sendVersionRoute, outgoingRoute, peer)
|
|
||||||
if err != nil {
|
|
||||||
handleError(err, "SendVersion", &isStopping, errChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
})
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errChan:
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
case <-locks.ReceiveFromChanWhenDone(func() { wg.Wait() }):
|
|
||||||
}
|
|
||||||
|
|
||||||
err := context.AddToPeers(peer)
|
|
||||||
if err != nil {
|
|
||||||
if errors.As(err, &common.ErrPeerWithSameIDExists) {
|
|
||||||
return nil, protocolerrors.Wrap(false, err, "peer already exists")
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if peerAddress != nil {
|
|
||||||
subnetworkID := peer.SubnetworkID()
|
|
||||||
context.AddressManager().AddAddress(peerAddress, peerAddress, subnetworkID)
|
|
||||||
context.AddressManager().Good(peerAddress, subnetworkID)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.StartIBDIfRequired()
|
|
||||||
|
|
||||||
return peer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handshake is different from other flows, since in it should forward router.ErrRouteClosed to errChan
|
|
||||||
// Therefore we implement a separate handleError for handshake
|
|
||||||
func handleError(err error, flowName string, isStopping *uint32, errChan chan error) {
|
|
||||||
if errors.Is(err, routerpkg.ErrRouteClosed) {
|
|
||||||
if atomic.AddUint32(isStopping, 1) == 1 {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if protocolErr := &(protocolerrors.ProtocolError{}); errors.As(err, &protocolErr) {
|
|
||||||
log.Errorf("Handshake protocol error from %s: %s", flowName, err)
|
|
||||||
if atomic.AddUint32(isStopping, 1) == 1 {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package handshake
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.PROT)
|
|
||||||
var spawn = panics.GoroutineWrapperFunc(log)
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package handshake
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// allowSelfConnections is only used to allow the tests to bypass the self
|
|
||||||
// connection detecting and disconnect logic since they intentionally
|
|
||||||
// do so for testing purposes.
|
|
||||||
allowSelfConnections bool
|
|
||||||
|
|
||||||
// minAcceptableProtocolVersion is the lowest protocol version that a
|
|
||||||
// connected peer may support.
|
|
||||||
minAcceptableProtocolVersion = appmessage.ProtocolVersion
|
|
||||||
)
|
|
||||||
|
|
||||||
type receiveVersionFlow struct {
|
|
||||||
HandleHandshakeContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
peer *peerpkg.Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiveVersion waits for the peer to send a version message, sends a
|
|
||||||
// verack in response, and updates its info accordingly.
|
|
||||||
func ReceiveVersion(context HandleHandshakeContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
|
||||||
peer *peerpkg.Peer) (*appmessage.NetAddress, error) {
|
|
||||||
|
|
||||||
flow := &receiveVersionFlow{
|
|
||||||
HandleHandshakeContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
peer: peer,
|
|
||||||
}
|
|
||||||
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *receiveVersionFlow) start() (*appmessage.NetAddress, error) {
|
|
||||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgVersion, ok := message.(*appmessage.MsgVersion)
|
|
||||||
if !ok {
|
|
||||||
return nil, protocolerrors.New(true, "a version message must precede all others")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowSelfConnections && flow.NetAdapter().ID().IsEqual(msgVersion.ID) {
|
|
||||||
return nil, protocolerrors.New(true, "connected to self")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect and ban peers from a different network
|
|
||||||
if msgVersion.Network != flow.Config().ActiveNetParams.Name {
|
|
||||||
return nil, protocolerrors.Errorf(true, "wrong network")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify and disconnect clients that have a protocol version that is
|
|
||||||
// too old.
|
|
||||||
//
|
|
||||||
// NOTE: If minAcceptableProtocolVersion is raised to be higher than
|
|
||||||
// appmessage.RejectVersion, this should send a reject packet before
|
|
||||||
// disconnecting.
|
|
||||||
if msgVersion.ProtocolVersion < minAcceptableProtocolVersion {
|
|
||||||
return nil, protocolerrors.Errorf(false, "protocol version must be %d or greater",
|
|
||||||
minAcceptableProtocolVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect from partial nodes in networks that don't allow them
|
|
||||||
if !flow.DAG().Params.EnableNonNativeSubnetworks && msgVersion.SubnetworkID != nil {
|
|
||||||
return nil, protocolerrors.New(true, "partial nodes are not allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect if:
|
|
||||||
// - we are a full node and the outbound connection we've initiated is a partial node
|
|
||||||
// - the remote node is partial and our subnetwork doesn't match their subnetwork
|
|
||||||
localSubnetworkID := flow.Config().SubnetworkID
|
|
||||||
isLocalNodeFull := localSubnetworkID == nil
|
|
||||||
isRemoteNodeFull := msgVersion.SubnetworkID == nil
|
|
||||||
isOutbound := flow.peer.Connection().IsOutbound()
|
|
||||||
if (isLocalNodeFull && !isRemoteNodeFull && isOutbound) ||
|
|
||||||
(!isLocalNodeFull && !isRemoteNodeFull && !msgVersion.SubnetworkID.IsEqual(localSubnetworkID)) {
|
|
||||||
|
|
||||||
return nil, protocolerrors.New(false, "incompatible subnetworks")
|
|
||||||
}
|
|
||||||
|
|
||||||
flow.peer.UpdateFieldsFromMsgVersion(msgVersion)
|
|
||||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgVerAck())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
flow.peer.Connection().SetID(msgVersion.ID)
|
|
||||||
|
|
||||||
return msgVersion.Address, nil
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package handshake
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// userAgentName is the user agent name and is used to help identify
|
|
||||||
// ourselves to other kaspa peers.
|
|
||||||
userAgentName = "kaspad"
|
|
||||||
|
|
||||||
// userAgentVersion is the user agent version and is used to help
|
|
||||||
// identify ourselves to other kaspa peers.
|
|
||||||
userAgentVersion = version.Version()
|
|
||||||
|
|
||||||
// defaultServices describes the default services that are supported by
|
|
||||||
// the server.
|
|
||||||
defaultServices = appmessage.DefaultServices
|
|
||||||
|
|
||||||
// defaultRequiredServices describes the default services that are
|
|
||||||
// required to be supported by outbound peers.
|
|
||||||
defaultRequiredServices = appmessage.SFNodeNetwork
|
|
||||||
)
|
|
||||||
|
|
||||||
type sendVersionFlow struct {
|
|
||||||
HandleHandshakeContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
peer *peerpkg.Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendVersion sends a version to a peer and waits for verack.
|
|
||||||
func SendVersion(context HandleHandshakeContext, incomingRoute *router.Route,
|
|
||||||
outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
|
||||||
|
|
||||||
flow := &sendVersionFlow{
|
|
||||||
HandleHandshakeContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
peer: peer,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *sendVersionFlow) start() error {
|
|
||||||
selectedTipHash := flow.DAG().SelectedTipHash()
|
|
||||||
subnetworkID := flow.Config().SubnetworkID
|
|
||||||
|
|
||||||
// Version message.
|
|
||||||
localAddress := flow.AddressManager().GetBestLocalAddress(flow.peer.Connection().NetAddress())
|
|
||||||
msg := appmessage.NewMsgVersion(localAddress, flow.NetAdapter().ID(),
|
|
||||||
flow.Config().ActiveNetParams.Name, selectedTipHash, subnetworkID)
|
|
||||||
msg.AddUserAgent(userAgentName, userAgentVersion, flow.Config().UserAgentComments...)
|
|
||||||
|
|
||||||
// Advertise the services flag
|
|
||||||
msg.Services = defaultServices
|
|
||||||
|
|
||||||
// Advertise our max supported protocol version.
|
|
||||||
msg.ProtocolVersion = appmessage.ProtocolVersion
|
|
||||||
|
|
||||||
// Advertise if inv messages for transactions are desired.
|
|
||||||
msg.DisableRelayTx = flow.Config().BlocksOnly
|
|
||||||
|
|
||||||
err := flow.outgoingRoute.Enqueue(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for verack
|
|
||||||
_, err = flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package ibd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RequestBlockLocatorContext is the interface for the context needed for the HandleRequestBlockLocator flow.
|
|
||||||
type RequestBlockLocatorContext interface {
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
}
|
|
||||||
|
|
||||||
type handleRequestBlockLocatorFlow struct {
|
|
||||||
RequestBlockLocatorContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequestBlockLocator handles getBlockLocator messages
|
|
||||||
func HandleRequestBlockLocator(context RequestBlockLocatorContext, incomingRoute *router.Route,
|
|
||||||
outgoingRoute *router.Route) error {
|
|
||||||
|
|
||||||
flow := &handleRequestBlockLocatorFlow{
|
|
||||||
RequestBlockLocatorContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestBlockLocatorFlow) start() error {
|
|
||||||
for {
|
|
||||||
lowHash, highHash, err := flow.receiveGetBlockLocator()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
locator, err := flow.DAG().BlockLocatorFromHashes(highHash, lowHash)
|
|
||||||
if err != nil || len(locator) == 0 {
|
|
||||||
return protocolerrors.Errorf(true, "couldn't build a block "+
|
|
||||||
"locator between blocks %s and %s", lowHash, highHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = flow.sendBlockLocator(locator)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *daghash.Hash,
|
|
||||||
highHash *daghash.Hash, err error) {
|
|
||||||
|
|
||||||
message, err := flow.incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
msgGetBlockLocator := message.(*appmessage.MsgRequestBlockLocator)
|
|
||||||
|
|
||||||
return msgGetBlockLocator.LowHash, msgGetBlockLocator.HighHash, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestBlockLocatorFlow) sendBlockLocator(locator blockdag.BlockLocator) error {
|
|
||||||
msgBlockLocator := appmessage.NewMsgBlockLocator(locator)
|
|
||||||
err := flow.outgoingRoute.Enqueue(msgBlockLocator)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package ibd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ibdBatchSize = router.DefaultMaxMessages
|
|
||||||
|
|
||||||
// RequestIBDBlocksContext is the interface for the context needed for the HandleRequestIBDBlocks flow.
|
|
||||||
type RequestIBDBlocksContext interface {
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
}
|
|
||||||
|
|
||||||
type handleRequestBlocksFlow struct {
|
|
||||||
RequestIBDBlocksContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequestIBDBlocks handles getBlocks messages
|
|
||||||
func HandleRequestIBDBlocks(context RequestIBDBlocksContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
|
||||||
flow := &handleRequestBlocksFlow{
|
|
||||||
RequestIBDBlocksContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestBlocksFlow) start() error {
|
|
||||||
for {
|
|
||||||
lowHash, highHash, err := receiveRequestIBDBlocks(flow.incomingRoute)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgIBDBlocks, err := flow.buildMsgIBDBlocks(lowHash, highHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for offset := 0; offset < len(msgIBDBlocks); offset += ibdBatchSize {
|
|
||||||
end := offset + ibdBatchSize
|
|
||||||
if end > len(msgIBDBlocks) {
|
|
||||||
end = len(msgIBDBlocks)
|
|
||||||
}
|
|
||||||
|
|
||||||
blocksToSend := msgIBDBlocks[offset:end]
|
|
||||||
err = flow.sendMsgIBDBlocks(blocksToSend)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit the loop and don't wait for the GetNextIBDBlocks message if the last batch was
|
|
||||||
// less than ibdBatchSize.
|
|
||||||
if len(blocksToSend) < ibdBatchSize {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
message, err := flow.incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := message.(*appmessage.MsgRequestNextIBDBlocks); !ok {
|
|
||||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
|
||||||
"expected: %s, got: %s", appmessage.CmdRequestNextIBDBlocks, message.Command())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgDoneIBDBlocks())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func receiveRequestIBDBlocks(incomingRoute *router.Route) (lowHash *daghash.Hash,
|
|
||||||
highHash *daghash.Hash, err error) {
|
|
||||||
|
|
||||||
message, err := incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestIBDBlocks)
|
|
||||||
|
|
||||||
return msgRequestIBDBlocks.LowHash, msgRequestIBDBlocks.HighHash, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestBlocksFlow) buildMsgIBDBlocks(lowHash *daghash.Hash,
|
|
||||||
highHash *daghash.Hash) ([]*appmessage.MsgIBDBlock, error) {
|
|
||||||
|
|
||||||
const maxHashesInMsgIBDBlocks = appmessage.MaxInvPerMsg
|
|
||||||
blockHashes, err := flow.DAG().AntiPastHashesBetween(lowHash, highHash, maxHashesInMsgIBDBlocks)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, blockdag.ErrInvalidParameter) {
|
|
||||||
return nil, protocolerrors.Wrapf(true, err, "could not get antiPast between "+
|
|
||||||
"%s and %s", lowHash, highHash)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgIBDBlocks := make([]*appmessage.MsgIBDBlock, len(blockHashes))
|
|
||||||
for i, blockHash := range blockHashes {
|
|
||||||
block, err := flow.DAG().BlockByHash(blockHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
msgIBDBlocks[i] = appmessage.NewMsgIBDBlock(block.MsgBlock())
|
|
||||||
}
|
|
||||||
|
|
||||||
return msgIBDBlocks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestBlocksFlow) sendMsgIBDBlocks(msgIBDBlocks []*appmessage.MsgIBDBlock) error {
|
|
||||||
for _, msgIBDBlock := range msgIBDBlocks {
|
|
||||||
err := flow.outgoingRoute.Enqueue(msgIBDBlock)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
package ibd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/blocklogger"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleIBDContext is the interface for the context needed for the HandleIBD flow.
|
|
||||||
type HandleIBDContext interface {
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
OnNewBlock(block *util.Block) error
|
|
||||||
StartIBDIfRequired()
|
|
||||||
FinishIBD()
|
|
||||||
}
|
|
||||||
|
|
||||||
type handleIBDFlow struct {
|
|
||||||
HandleIBDContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
peer *peerpkg.Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleIBD waits for IBD start and handles it when IBD is triggered for this peer
|
|
||||||
func HandleIBD(context HandleIBDContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
|
||||||
peer *peerpkg.Peer) error {
|
|
||||||
|
|
||||||
flow := &handleIBDFlow{
|
|
||||||
HandleIBDContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
peer: peer,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) start() error {
|
|
||||||
for {
|
|
||||||
err := flow.runIBD()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) runIBD() error {
|
|
||||||
flow.peer.WaitForIBDStart()
|
|
||||||
defer flow.FinishIBD()
|
|
||||||
|
|
||||||
peerSelectedTipHash := flow.peer.SelectedTipHash()
|
|
||||||
log.Debugf("Trying to find highest shared chain block with peer %s with selected tip %s", flow.peer, peerSelectedTipHash)
|
|
||||||
highestSharedBlockHash, err := flow.findHighestSharedBlockHash(peerSelectedTipHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Found highest shared chain block %s with peer %s", highestSharedBlockHash, flow.peer)
|
|
||||||
if flow.DAG().IsKnownFinalizedBlock(highestSharedBlockHash) {
|
|
||||||
return protocolerrors.Errorf(false, "cannot initiate "+
|
|
||||||
"IBD with peer %s because the highest shared chain block (%s) is "+
|
|
||||||
"below the finality point", flow.peer, highestSharedBlockHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
return flow.downloadBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) findHighestSharedBlockHash(peerSelectedTipHash *daghash.Hash) (lowHash *daghash.Hash,
|
|
||||||
err error) {
|
|
||||||
|
|
||||||
lowHash = flow.DAG().Params.GenesisHash
|
|
||||||
highHash := peerSelectedTipHash
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := flow.sendGetBlockLocator(lowHash, highHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
blockLocatorHashes, err := flow.receiveBlockLocator()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We check whether the locator's highest hash is in the local DAG.
|
|
||||||
// If it is, return it. If it isn't, we need to narrow our
|
|
||||||
// getBlockLocator request and try again.
|
|
||||||
locatorHighHash := blockLocatorHashes[0]
|
|
||||||
if flow.DAG().IsInDAG(locatorHighHash) {
|
|
||||||
return locatorHighHash, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
highHash, lowHash = flow.DAG().FindNextLocatorBoundaries(blockLocatorHashes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) sendGetBlockLocator(lowHash *daghash.Hash, highHash *daghash.Hash) error {
|
|
||||||
|
|
||||||
msgGetBlockLocator := appmessage.NewMsgRequestBlockLocator(highHash, lowHash)
|
|
||||||
return flow.outgoingRoute.Enqueue(msgGetBlockLocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) receiveBlockLocator() (blockLocatorHashes []*daghash.Hash, err error) {
|
|
||||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
msgBlockLocator, ok := message.(*appmessage.MsgBlockLocator)
|
|
||||||
if !ok {
|
|
||||||
return nil,
|
|
||||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
|
||||||
"expected: %s, got: %s", appmessage.CmdBlockLocator, message.Command())
|
|
||||||
}
|
|
||||||
return msgBlockLocator.BlockLocatorHashes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) downloadBlocks(highestSharedBlockHash *daghash.Hash,
|
|
||||||
peerSelectedTipHash *daghash.Hash) error {
|
|
||||||
|
|
||||||
err := flow.sendGetBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
blocksReceived := 0
|
|
||||||
for {
|
|
||||||
msgIBDBlock, doneIBD, err := flow.receiveIBDBlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if doneIBD {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = flow.processIBDBlock(msgIBDBlock)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
blocksReceived++
|
|
||||||
if blocksReceived%ibdBatchSize == 0 {
|
|
||||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestNextIBDBlocks())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) sendGetBlocks(highestSharedBlockHash *daghash.Hash,
|
|
||||||
peerSelectedTipHash *daghash.Hash) error {
|
|
||||||
|
|
||||||
msgGetBlockInvs := appmessage.NewMsgRequstIBDBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
|
||||||
return flow.outgoingRoute.Enqueue(msgGetBlockInvs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) receiveIBDBlock() (msgIBDBlock *appmessage.MsgIBDBlock, doneIBD bool, err error) {
|
|
||||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
switch message := message.(type) {
|
|
||||||
case *appmessage.MsgIBDBlock:
|
|
||||||
return message, false, nil
|
|
||||||
case *appmessage.MsgDoneIBDBlocks:
|
|
||||||
return nil, true, nil
|
|
||||||
default:
|
|
||||||
return nil, false,
|
|
||||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
|
||||||
"expected: %s, got: %s", appmessage.CmdIBDBlock, message.Command())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleIBDFlow) processIBDBlock(msgIBDBlock *appmessage.MsgIBDBlock) error {
|
|
||||||
block := util.NewBlock(msgIBDBlock.MsgBlock)
|
|
||||||
if flow.DAG().IsInDAG(block.Hash()) {
|
|
||||||
log.Debugf("IBD block %s is already in the DAG. Skipping...", block.Hash())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
isOrphan, isDelayed, err := flow.DAG().ProcessBlock(block, blockdag.BFNone)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.As(err, &blockdag.RuleError{}) {
|
|
||||||
return errors.Wrapf(err, "failed to process block %s during IBD", block.Hash())
|
|
||||||
}
|
|
||||||
log.Infof("Rejected block %s from %s during IBD: %s", block.Hash(), flow.peer, err)
|
|
||||||
|
|
||||||
return protocolerrors.Wrapf(true, err, "got invalid block %s during IBD", block.Hash())
|
|
||||||
}
|
|
||||||
if isOrphan {
|
|
||||||
return protocolerrors.Errorf(true, "received orphan block %s "+
|
|
||||||
"during IBD", block.Hash())
|
|
||||||
}
|
|
||||||
if isDelayed {
|
|
||||||
return protocolerrors.Errorf(false, "received delayed block %s "+
|
|
||||||
"during IBD", block.Hash())
|
|
||||||
}
|
|
||||||
err = flow.OnNewBlock(block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = blocklogger.LogBlock(block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package ibd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.IBDS)
|
|
||||||
var spawn = panics.GoroutineWrapperFunc(log)
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package selectedtip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleRequestSelectedTipContext is the interface for the context needed for the HandleRequestSelectedTip flow.
|
|
||||||
type HandleRequestSelectedTipContext interface {
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
}
|
|
||||||
|
|
||||||
type handleRequestSelectedTipFlow struct {
|
|
||||||
HandleRequestSelectedTipContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequestSelectedTip handles getSelectedTip messages
|
|
||||||
func HandleRequestSelectedTip(context HandleRequestSelectedTipContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
|
||||||
flow := &handleRequestSelectedTipFlow{
|
|
||||||
HandleRequestSelectedTipContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestSelectedTipFlow) start() error {
|
|
||||||
for {
|
|
||||||
err := flow.receiveGetSelectedTip()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = flow.sendSelectedTipHash()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestSelectedTipFlow) receiveGetSelectedTip() error {
|
|
||||||
message, err := flow.incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, ok := message.(*appmessage.MsgRequestSelectedTip)
|
|
||||||
if !ok {
|
|
||||||
return errors.Errorf("received unexpected message type. "+
|
|
||||||
"expected: %s, got: %s", appmessage.CmdRequestSelectedTip, message.Command())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestSelectedTipFlow) sendSelectedTipHash() error {
|
|
||||||
msgSelectedTip := appmessage.NewMsgSelectedTip(flow.DAG().SelectedTipHash())
|
|
||||||
return flow.outgoingRoute.Enqueue(msgSelectedTip)
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package selectedtip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RequestSelectedTipContext is the interface for the context needed for the RequestSelectedTip flow.
|
|
||||||
type RequestSelectedTipContext interface {
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
StartIBDIfRequired()
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestSelectedTipFlow struct {
|
|
||||||
RequestSelectedTipContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
peer *peerpkg.Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestSelectedTip waits for selected tip requests and handles them
|
|
||||||
func RequestSelectedTip(context RequestSelectedTipContext, incomingRoute *router.Route,
|
|
||||||
outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
|
||||||
|
|
||||||
flow := &requestSelectedTipFlow{
|
|
||||||
RequestSelectedTipContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
peer: peer,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *requestSelectedTipFlow) start() error {
|
|
||||||
for {
|
|
||||||
err := flow.runSelectedTipRequest()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *requestSelectedTipFlow) runSelectedTipRequest() error {
|
|
||||||
|
|
||||||
flow.peer.WaitForSelectedTipRequests()
|
|
||||||
defer flow.peer.FinishRequestingSelectedTip()
|
|
||||||
|
|
||||||
err := flow.requestSelectedTip()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
peerSelectedTipHash, err := flow.receiveSelectedTip()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
flow.peer.SetSelectedTipHash(peerSelectedTipHash)
|
|
||||||
flow.StartIBDIfRequired()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *requestSelectedTipFlow) requestSelectedTip() error {
|
|
||||||
msgGetSelectedTip := appmessage.NewMsgRequestSelectedTip()
|
|
||||||
return flow.outgoingRoute.Enqueue(msgGetSelectedTip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *requestSelectedTipFlow) receiveSelectedTip() (selectedTipHash *daghash.Hash, err error) {
|
|
||||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
msgSelectedTip := message.(*appmessage.MsgSelectedTip)
|
|
||||||
|
|
||||||
return msgSelectedTip.SelectedTipHash, nil
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package ping
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReceivePingsContext is the interface for the context needed for the ReceivePings flow.
|
|
||||||
type ReceivePingsContext interface {
|
|
||||||
}
|
|
||||||
|
|
||||||
type receivePingsFlow struct {
|
|
||||||
ReceivePingsContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceivePings handles all ping messages coming through incomingRoute.
|
|
||||||
// This function assumes that incomingRoute will only return MsgPing.
|
|
||||||
func ReceivePings(context ReceivePingsContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
|
||||||
flow := &receivePingsFlow{
|
|
||||||
ReceivePingsContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *receivePingsFlow) start() error {
|
|
||||||
for {
|
|
||||||
message, err := flow.incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pingMessage := message.(*appmessage.MsgPing)
|
|
||||||
|
|
||||||
pongMessage := appmessage.NewMsgPong(pingMessage.Nonce)
|
|
||||||
err = flow.outgoingRoute.Enqueue(pongMessage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package ping
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/util/random"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendPingsContext is the interface for the context needed for the SendPings flow.
|
|
||||||
type SendPingsContext interface {
|
|
||||||
}
|
|
||||||
|
|
||||||
type sendPingsFlow struct {
|
|
||||||
SendPingsContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
peer *peerpkg.Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendPings starts sending MsgPings every pingInterval seconds to the
|
|
||||||
// given peer.
|
|
||||||
// This function assumes that incomingRoute will only return MsgPong.
|
|
||||||
func SendPings(context SendPingsContext, incomingRoute *router.Route, outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
|
||||||
flow := &sendPingsFlow{
|
|
||||||
SendPingsContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
peer: peer,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *sendPingsFlow) start() error {
|
|
||||||
const pingInterval = 2 * time.Minute
|
|
||||||
ticker := time.NewTicker(pingInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for range ticker.C {
|
|
||||||
nonce, err := random.Uint64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
flow.peer.SetPingPending(nonce)
|
|
||||||
|
|
||||||
pingMessage := appmessage.NewMsgPing(nonce)
|
|
||||||
err = flow.outgoingRoute.Enqueue(pingMessage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pongMessage := message.(*appmessage.MsgPong)
|
|
||||||
if pongMessage.Nonce != pingMessage.Nonce {
|
|
||||||
return protocolerrors.New(true, "nonce mismatch between ping and pong")
|
|
||||||
}
|
|
||||||
flow.peer.SetPingIdle()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package rejects
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleRejectsContext is the interface for the context needed for the HandleRejects flow.
|
|
||||||
type HandleRejectsContext interface {
|
|
||||||
}
|
|
||||||
|
|
||||||
type handleRejectsFlow struct {
|
|
||||||
HandleRejectsContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRejects handles all reject messages coming through incomingRoute.
|
|
||||||
// This function assumes that incomingRoute will only return MsgReject.
|
|
||||||
func HandleRejects(context HandleRejectsContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
|
||||||
flow := &handleRejectsFlow{
|
|
||||||
HandleRejectsContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRejectsFlow) start() error {
|
|
||||||
message, err := flow.incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rejectMessage := message.(*appmessage.MsgReject)
|
|
||||||
|
|
||||||
const maxReasonLength = 255
|
|
||||||
if len(rejectMessage.Reason) > maxReasonLength {
|
|
||||||
return protocolerrors.Errorf(false, "got reject message longer than %d", maxReasonLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
return protocolerrors.Errorf(false, "got reject message: `%s`", rejectMessage.Reason)
|
|
||||||
}
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
package relaytransactions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/domain/mempool"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TransactionsRelayContext is the interface for the context needed for the
|
|
||||||
// HandleRelayedTransactions and HandleRequestedTransactions flows.
|
|
||||||
type TransactionsRelayContext interface {
|
|
||||||
NetAdapter() *netadapter.NetAdapter
|
|
||||||
DAG() *blockdag.BlockDAG
|
|
||||||
SharedRequestedTransactions() *SharedRequestedTransactions
|
|
||||||
TxPool() *mempool.TxPool
|
|
||||||
Broadcast(message appmessage.Message) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type handleRelayedTransactionsFlow struct {
|
|
||||||
TransactionsRelayContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
invsQueue []*appmessage.MsgInvTransaction
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRelayedTransactions listens to appmessage.MsgInvTransaction messages, requests their corresponding transactions if they
|
|
||||||
// are missing, adds them to the mempool and propagates them to the rest of the network.
|
|
||||||
func HandleRelayedTransactions(context TransactionsRelayContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
|
||||||
flow := &handleRelayedTransactionsFlow{
|
|
||||||
TransactionsRelayContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
invsQueue: make([]*appmessage.MsgInvTransaction, 0),
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayedTransactionsFlow) start() error {
|
|
||||||
for {
|
|
||||||
inv, err := flow.readInv()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, txID := range inv.TxIDs {
|
|
||||||
log.Criticalf("~~~~~ handleRelayedTransactionsFlow.start() got %s", txID)
|
|
||||||
}
|
|
||||||
|
|
||||||
requestedIDs, err := flow.requestInvTransactions(inv)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = flow.receiveTransactions(requestedIDs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
|
|
||||||
inv *appmessage.MsgInvTransaction) (requestedIDs []*daghash.TxID, err error) {
|
|
||||||
|
|
||||||
idsToRequest := make([]*daghash.TxID, 0, len(inv.TxIDs))
|
|
||||||
for _, txID := range inv.TxIDs {
|
|
||||||
if flow.isKnownTransaction(txID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
exists := flow.SharedRequestedTransactions().addIfNotExists(txID)
|
|
||||||
if exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
idsToRequest = append(idsToRequest, txID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(idsToRequest) == 0 {
|
|
||||||
return idsToRequest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msgGetTransactions := appmessage.NewMsgRequestTransactions(idsToRequest)
|
|
||||||
err = flow.outgoingRoute.Enqueue(msgGetTransactions)
|
|
||||||
if err != nil {
|
|
||||||
flow.SharedRequestedTransactions().removeMany(idsToRequest)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return idsToRequest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *daghash.TxID) bool {
|
|
||||||
// Ask the transaction memory pool if the transaction is known
|
|
||||||
// to it in any form (main pool or orphan).
|
|
||||||
if flow.TxPool().HaveTransaction(txID) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the transaction exists from the point of view of the
|
|
||||||
// DAG's virtual block. Note that this is only a best effort
|
|
||||||
// since it is expensive to check existence of every output and
|
|
||||||
// the only purpose of this check is to avoid downloading
|
|
||||||
// already known transactions. Only the first two outputs are
|
|
||||||
// checked because the vast majority of transactions consist of
|
|
||||||
// two outputs where one is some form of "pay-to-somebody-else"
|
|
||||||
// and the other is a change output.
|
|
||||||
prevOut := appmessage.Outpoint{TxID: *txID}
|
|
||||||
for i := uint32(0); i < 2; i++ {
|
|
||||||
prevOut.Index = i
|
|
||||||
_, ok := flow.DAG().GetUTXOEntry(prevOut)
|
|
||||||
if ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayedTransactionsFlow) readInv() (*appmessage.MsgInvTransaction, error) {
|
|
||||||
if len(flow.invsQueue) > 0 {
|
|
||||||
var inv *appmessage.MsgInvTransaction
|
|
||||||
inv, flow.invsQueue = flow.invsQueue[0], flow.invsQueue[1:]
|
|
||||||
return inv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := flow.incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
inv, ok := msg.(*appmessage.MsgInvTransaction)
|
|
||||||
if !ok {
|
|
||||||
return nil, protocolerrors.Errorf(true, "unexpected %s message in the block relay flow while "+
|
|
||||||
"expecting an inv message", msg.Command())
|
|
||||||
}
|
|
||||||
return inv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayedTransactionsFlow) broadcastAcceptedTransactions(acceptedTxs []*mempool.TxDesc) error {
|
|
||||||
idsToBroadcast := make([]*daghash.TxID, len(acceptedTxs))
|
|
||||||
for i, tx := range acceptedTxs {
|
|
||||||
log.Criticalf("~~~~~ broadcastAcceptedTransactions() broadcasting %s", tx.Tx.ID())
|
|
||||||
idsToBroadcast[i] = tx.Tx.ID()
|
|
||||||
}
|
|
||||||
inv := appmessage.NewMsgInvTransaction(idsToBroadcast)
|
|
||||||
return flow.Broadcast(inv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readMsgTxOrNotFound returns the next msgTx or msgTransactionNotFound in incomingRoute,
|
|
||||||
// returning only one of the message types at a time.
|
|
||||||
//
|
|
||||||
// and populates invsQueue with any inv messages that meanwhile arrive.
|
|
||||||
func (flow *handleRelayedTransactionsFlow) readMsgTxOrNotFound() (
|
|
||||||
msgTx *appmessage.MsgTx, msgNotFound *appmessage.MsgTransactionNotFound, err error) {
|
|
||||||
|
|
||||||
for {
|
|
||||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch message := message.(type) {
|
|
||||||
case *appmessage.MsgInvTransaction:
|
|
||||||
flow.invsQueue = append(flow.invsQueue, message)
|
|
||||||
case *appmessage.MsgTx:
|
|
||||||
return message, nil, nil
|
|
||||||
case *appmessage.MsgTransactionNotFound:
|
|
||||||
return nil, message, nil
|
|
||||||
default:
|
|
||||||
return nil, nil, errors.Errorf("unexpected message %s", message.Command())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransactions []*daghash.TxID) error {
|
|
||||||
// In case the function returns earlier than expected, we want to make sure sharedRequestedTransactions is
|
|
||||||
// clean from any pending transactions.
|
|
||||||
defer flow.SharedRequestedTransactions().removeMany(requestedTransactions)
|
|
||||||
for _, expectedID := range requestedTransactions {
|
|
||||||
msgTx, msgTxNotFound, err := flow.readMsgTxOrNotFound()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if msgTxNotFound != nil {
|
|
||||||
if !msgTxNotFound.ID.IsEqual(expectedID) {
|
|
||||||
return protocolerrors.Errorf(true, "expected transaction %s, but got %s",
|
|
||||||
expectedID, msgTxNotFound.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tx := util.NewTx(msgTx)
|
|
||||||
log.Criticalf("~~~~~ receiveTransactions() got %s", tx.ID())
|
|
||||||
if !tx.ID().IsEqual(expectedID) {
|
|
||||||
return protocolerrors.Errorf(true, "expected transaction %s, but got %s",
|
|
||||||
expectedID, tx.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptedTxs, err := flow.TxPool().ProcessTransaction(tx, true)
|
|
||||||
if err != nil {
|
|
||||||
ruleErr := &mempool.RuleError{}
|
|
||||||
if !errors.As(err, ruleErr) {
|
|
||||||
return errors.Wrapf(err, "failed to process transaction %s", tx.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldBan := false
|
|
||||||
if txRuleErr := (&mempool.TxRuleError{}); errors.As(ruleErr.Err, txRuleErr) {
|
|
||||||
if txRuleErr.RejectCode == mempool.RejectInvalid {
|
|
||||||
shouldBan = true
|
|
||||||
}
|
|
||||||
} else if dagRuleErr := (&blockdag.RuleError{}); errors.As(ruleErr.Err, dagRuleErr) {
|
|
||||||
shouldBan = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !shouldBan {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return protocolerrors.Errorf(true, "rejected transaction %s", tx.ID())
|
|
||||||
}
|
|
||||||
err = flow.broadcastAcceptedTransactions(acceptedTxs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package relaytransactions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
type handleRequestedTransactionsFlow struct {
|
|
||||||
TransactionsRelayContext
|
|
||||||
incomingRoute, outgoingRoute *router.Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequestedTransactions listens to appmessage.MsgRequestTransactions messages, responding with the requested
|
|
||||||
// transactions if those are in the mempool.
|
|
||||||
// Missing transactions would be ignored
|
|
||||||
func HandleRequestedTransactions(context TransactionsRelayContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
|
||||||
flow := &handleRequestedTransactionsFlow{
|
|
||||||
TransactionsRelayContext: context,
|
|
||||||
incomingRoute: incomingRoute,
|
|
||||||
outgoingRoute: outgoingRoute,
|
|
||||||
}
|
|
||||||
return flow.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestedTransactionsFlow) start() error {
|
|
||||||
for {
|
|
||||||
msgRequestTransactions, err := flow.readRequestTransactions()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, transactionID := range msgRequestTransactions.IDs {
|
|
||||||
log.Criticalf("~~~~~ handleRequestedTransactionsFlow.start() tx %s was requested", transactionID)
|
|
||||||
tx, ok := flow.TxPool().FetchTransaction(transactionID)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
msgTransactionNotFound := appmessage.NewMsgTransactionNotFound(transactionID)
|
|
||||||
err := flow.outgoingRoute.Enqueue(msgTransactionNotFound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := flow.outgoingRoute.Enqueue(tx.MsgTx())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (flow *handleRequestedTransactionsFlow) readRequestTransactions() (*appmessage.MsgRequestTransactions, error) {
|
|
||||||
msg, err := flow.incomingRoute.Dequeue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg.(*appmessage.MsgRequestTransactions), nil
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package relaytransactions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.PROT)
|
|
||||||
var spawn = panics.GoroutineWrapperFunc(log)
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package relaytransactions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SharedRequestedTransactions is a data structure that is shared between peers that
|
|
||||||
// holds the IDs of all the requested transactions to prevent redundant requests.
|
|
||||||
type SharedRequestedTransactions struct {
|
|
||||||
transactions map[daghash.TxID]struct{}
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SharedRequestedTransactions) remove(txID *daghash.TxID) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
delete(s.transactions, *txID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SharedRequestedTransactions) removeMany(txIDs []*daghash.TxID) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
for _, txID := range txIDs {
|
|
||||||
delete(s.transactions, *txID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SharedRequestedTransactions) addIfNotExists(txID *daghash.TxID) (exists bool) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
_, ok := s.transactions[*txID]
|
|
||||||
if ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
s.transactions[*txID] = struct{}{}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSharedRequestedTransactions returns a new instance of SharedRequestedTransactions.
|
|
||||||
func NewSharedRequestedTransactions() *SharedRequestedTransactions {
|
|
||||||
return &SharedRequestedTransactions{
|
|
||||||
transactions: make(map[daghash.TxID]struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.PROT)
|
|
||||||
var spawn = panics.GoroutineWrapperFunc(log)
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
|
||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
|
||||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
|
||||||
"github.com/kaspanet/kaspad/domain/mempool"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Manager manages the p2p protocol
|
|
||||||
type Manager struct {
|
|
||||||
context *flowcontext.FlowContext
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewManager creates a new instance of the p2p protocol manager
|
|
||||||
func NewManager(cfg *config.Config, dag *blockdag.BlockDAG, netAdapter *netadapter.NetAdapter,
|
|
||||||
addressManager *addressmanager.AddressManager, txPool *mempool.TxPool,
|
|
||||||
connectionManager *connmanager.ConnectionManager) (*Manager, error) {
|
|
||||||
|
|
||||||
manager := Manager{
|
|
||||||
context: flowcontext.New(cfg, dag, addressManager, txPool, netAdapter, connectionManager),
|
|
||||||
}
|
|
||||||
netAdapter.SetRouterInitializer(manager.routerInitializer)
|
|
||||||
return &manager, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the p2p protocol
|
|
||||||
func (m *Manager) Start() error {
|
|
||||||
return m.context.NetAdapter().Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the p2p protocol
|
|
||||||
func (m *Manager) Stop() error {
|
|
||||||
return m.context.NetAdapter().Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peers returns the currently active peers
|
|
||||||
func (m *Manager) Peers() []*peerpkg.Peer {
|
|
||||||
return m.context.Peers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IBDPeer returns the currently active IBD peer.
|
|
||||||
// Returns nil if we aren't currently in IBD
|
|
||||||
func (m *Manager) IBDPeer() *peerpkg.Peer {
|
|
||||||
return m.context.IBDPeer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTransaction adds transaction to the mempool and propagates it.
|
|
||||||
func (m *Manager) AddTransaction(tx *util.Tx) error {
|
|
||||||
return m.context.AddTransaction(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBlock adds the given block to the DAG and propagates it.
|
|
||||||
func (m *Manager) AddBlock(block *util.Block, flags blockdag.BehaviorFlags) error {
|
|
||||||
return m.context.AddBlock(block, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) runFlows(flows []*flow, peer *peerpkg.Peer, errChan <-chan error) error {
|
|
||||||
for _, flow := range flows {
|
|
||||||
executeFunc := flow.executeFunc // extract to new variable so that it's not overwritten
|
|
||||||
spawn(fmt.Sprintf("flow-%s", flow.name), func() {
|
|
||||||
executeFunc(peer)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return <-errChan
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package peer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/panics"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.PROT)
|
|
||||||
var spawn = panics.GoroutineWrapperFunc(log)
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
package peer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
|
||||||
mathUtil "github.com/kaspanet/kaspad/util/math"
|
|
||||||
"github.com/kaspanet/kaspad/util/mstime"
|
|
||||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Peer holds data about a peer.
|
|
||||||
type Peer struct {
|
|
||||||
connection *netadapter.NetConnection
|
|
||||||
|
|
||||||
selectedTipHashMtx sync.RWMutex
|
|
||||||
selectedTipHash *daghash.Hash
|
|
||||||
|
|
||||||
userAgent string
|
|
||||||
services appmessage.ServiceFlag
|
|
||||||
advertisedProtocolVerion uint32 // protocol version advertised by remote
|
|
||||||
protocolVersion uint32 // negotiated protocol version
|
|
||||||
disableRelayTx bool
|
|
||||||
subnetworkID *subnetworkid.SubnetworkID
|
|
||||||
|
|
||||||
timeOffset time.Duration
|
|
||||||
connectionStarted time.Time
|
|
||||||
|
|
||||||
pingLock sync.RWMutex
|
|
||||||
lastPingNonce uint64 // The nonce of the last ping we sent
|
|
||||||
lastPingTime time.Time // Time we sent last ping
|
|
||||||
lastPingDuration time.Duration // Time for last ping to return
|
|
||||||
|
|
||||||
isSelectedTipRequested uint32
|
|
||||||
selectedTipRequestChan chan struct{}
|
|
||||||
lastSelectedTipRequest mstime.Time
|
|
||||||
|
|
||||||
ibdStartChan chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new Peer
|
|
||||||
func New(connection *netadapter.NetConnection) *Peer {
|
|
||||||
return &Peer{
|
|
||||||
connection: connection,
|
|
||||||
selectedTipRequestChan: make(chan struct{}),
|
|
||||||
ibdStartChan: make(chan struct{}),
|
|
||||||
connectionStarted: time.Now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connection returns the NetConnection associated with this peer
|
|
||||||
func (p *Peer) Connection() *netadapter.NetConnection {
|
|
||||||
return p.connection
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectedTipHash returns the selected tip of the peer.
|
|
||||||
func (p *Peer) SelectedTipHash() *daghash.Hash {
|
|
||||||
p.selectedTipHashMtx.RLock()
|
|
||||||
defer p.selectedTipHashMtx.RUnlock()
|
|
||||||
return p.selectedTipHash
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSelectedTipHash sets the selected tip of the peer.
|
|
||||||
func (p *Peer) SetSelectedTipHash(hash *daghash.Hash) {
|
|
||||||
p.selectedTipHashMtx.Lock()
|
|
||||||
defer p.selectedTipHashMtx.Unlock()
|
|
||||||
p.selectedTipHash = hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubnetworkID returns the subnetwork the peer is associated with.
|
|
||||||
// It is nil in full nodes.
|
|
||||||
func (p *Peer) SubnetworkID() *subnetworkid.SubnetworkID {
|
|
||||||
return p.subnetworkID
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the peer ID.
|
|
||||||
func (p *Peer) ID() *id.ID {
|
|
||||||
return p.connection.ID()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeOffset returns the peer's time offset.
|
|
||||||
func (p *Peer) TimeOffset() time.Duration {
|
|
||||||
return p.timeOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserAgent returns the peer's user agent.
|
|
||||||
func (p *Peer) UserAgent() string {
|
|
||||||
return p.userAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdvertisedProtocolVersion returns the peer's advertised protocol version.
|
|
||||||
func (p *Peer) AdvertisedProtocolVersion() uint32 {
|
|
||||||
return p.advertisedProtocolVerion
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeConnected returns the time since the connection to this been has been started.
|
|
||||||
func (p *Peer) TimeConnected() time.Duration {
|
|
||||||
return time.Since(p.connectionStarted)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsOutbound returns whether the peer is an outbound connection.
|
|
||||||
func (p *Peer) IsOutbound() bool {
|
|
||||||
return p.connection.IsOutbound()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFieldsFromMsgVersion updates the peer with the data from the version message.
|
|
||||||
func (p *Peer) UpdateFieldsFromMsgVersion(msg *appmessage.MsgVersion) {
|
|
||||||
// Negotiate the protocol version.
|
|
||||||
p.advertisedProtocolVerion = msg.ProtocolVersion
|
|
||||||
p.protocolVersion = mathUtil.MinUint32(p.protocolVersion, p.advertisedProtocolVerion)
|
|
||||||
log.Debugf("Negotiated protocol version %d for peer %s",
|
|
||||||
p.protocolVersion, p.ID())
|
|
||||||
|
|
||||||
// Set the supported services for the peer to what the remote peer
|
|
||||||
// advertised.
|
|
||||||
p.services = msg.Services
|
|
||||||
|
|
||||||
// Set the remote peer's user agent.
|
|
||||||
p.userAgent = msg.UserAgent
|
|
||||||
|
|
||||||
p.disableRelayTx = msg.DisableRelayTx
|
|
||||||
p.selectedTipHash = msg.SelectedTipHash
|
|
||||||
p.subnetworkID = msg.SubnetworkID
|
|
||||||
|
|
||||||
p.timeOffset = mstime.Since(msg.Timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPingPending sets the ping state of the peer to 'pending'
|
|
||||||
func (p *Peer) SetPingPending(nonce uint64) {
|
|
||||||
p.pingLock.Lock()
|
|
||||||
defer p.pingLock.Unlock()
|
|
||||||
|
|
||||||
p.lastPingNonce = nonce
|
|
||||||
p.lastPingTime = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPingIdle sets the ping state of the peer to 'idle'
|
|
||||||
func (p *Peer) SetPingIdle() {
|
|
||||||
p.pingLock.Lock()
|
|
||||||
defer p.pingLock.Unlock()
|
|
||||||
|
|
||||||
p.lastPingNonce = 0
|
|
||||||
p.lastPingDuration = time.Since(p.lastPingTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Peer) String() string {
|
|
||||||
return p.connection.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestSelectedTipIfRequired notifies the peer that requesting
|
|
||||||
// a selected tip is required. This triggers the selected tip
|
|
||||||
// request flow.
|
|
||||||
func (p *Peer) RequestSelectedTipIfRequired() {
|
|
||||||
if atomic.SwapUint32(&p.isSelectedTipRequested, 1) != 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const minGetSelectedTipInterval = time.Minute
|
|
||||||
if mstime.Since(p.lastSelectedTipRequest) < minGetSelectedTipInterval {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.lastSelectedTipRequest = mstime.Now()
|
|
||||||
p.selectedTipRequestChan <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForSelectedTipRequests blocks the current thread until
|
|
||||||
// a selected tip is requested from this peer
|
|
||||||
func (p *Peer) WaitForSelectedTipRequests() {
|
|
||||||
<-p.selectedTipRequestChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// FinishRequestingSelectedTip finishes requesting the selected
|
|
||||||
// tip from this peer
|
|
||||||
func (p *Peer) FinishRequestingSelectedTip() {
|
|
||||||
atomic.StoreUint32(&p.isSelectedTipRequested, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartIBD starts the IBD process for this peer
|
|
||||||
func (p *Peer) StartIBD() {
|
|
||||||
p.ibdStartChan <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForIBDStart blocks the current thread until
|
|
||||||
// IBD start is requested from this peer
|
|
||||||
func (p *Peer) WaitForIBDStart() {
|
|
||||||
<-p.ibdStartChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address returns the address associated with this connection
|
|
||||||
func (p *Peer) Address() string {
|
|
||||||
return p.connection.Address()
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastPingDuration returns the duration of the last ping to
|
|
||||||
// this peer
|
|
||||||
func (p *Peer) LastPingDuration() time.Duration {
|
|
||||||
p.pingLock.Lock()
|
|
||||||
defer p.pingLock.Unlock()
|
|
||||||
|
|
||||||
return p.lastPingDuration
|
|
||||||
}
|
|
||||||
@@ -1,314 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/rejects"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/addressexchange"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/handshake"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/ibd"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/ibd/selectedtip"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/ping"
|
|
||||||
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
|
|
||||||
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/netadapter"
|
|
||||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type flowInitializeFunc func(route *routerpkg.Route, peer *peerpkg.Peer) error
|
|
||||||
type flowExecuteFunc func(peer *peerpkg.Peer)
|
|
||||||
|
|
||||||
type flow struct {
|
|
||||||
name string
|
|
||||||
executeFunc flowExecuteFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) routerInitializer(router *routerpkg.Router, netConnection *netadapter.NetConnection) {
|
|
||||||
// isStopping flag is raised the moment that the connection associated with this router is disconnected
|
|
||||||
// errChan is used by the flow goroutines to return to runFlows when an error occurs.
|
|
||||||
// They are both initialized here and passed to register flows.
|
|
||||||
isStopping := uint32(0)
|
|
||||||
errChan := make(chan error)
|
|
||||||
|
|
||||||
flows := m.registerFlows(router, errChan, &isStopping)
|
|
||||||
receiveVersionRoute, sendVersionRoute := registerHandshakeRoutes(router)
|
|
||||||
|
|
||||||
// After flows were registered - spawn a new thread that will wait for connection to finish initializing
|
|
||||||
// and start receiving messages
|
|
||||||
spawn("routerInitializer-runFlows", func() {
|
|
||||||
isBanned, err := m.context.ConnectionManager().IsBanned(netConnection)
|
|
||||||
if err != nil && !errors.Is(err, addressmanager.ErrAddressNotFound) {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if isBanned {
|
|
||||||
netConnection.Disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn("Manager.routerInitializer-netConnection.DequeueInvalidMessage", func() {
|
|
||||||
for {
|
|
||||||
isOpen, err := netConnection.DequeueInvalidMessage()
|
|
||||||
if !isOpen {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if atomic.AddUint32(&isStopping, 1) == 1 {
|
|
||||||
errChan <- protocolerrors.Wrap(true, err, "received bad message")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
peer, err := handshake.HandleHandshake(m.context, netConnection, receiveVersionRoute,
|
|
||||||
sendVersionRoute, router.OutgoingRoute())
|
|
||||||
if err != nil {
|
|
||||||
m.handleError(err, netConnection, router.OutgoingRoute())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer m.context.RemoveFromPeers(peer)
|
|
||||||
|
|
||||||
removeHandshakeRoutes(router)
|
|
||||||
|
|
||||||
err = m.runFlows(flows, peer, errChan)
|
|
||||||
if err != nil {
|
|
||||||
m.handleError(err, netConnection, router.OutgoingRoute())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) handleError(err error, netConnection *netadapter.NetConnection, outgoingRoute *routerpkg.Route) {
|
|
||||||
if protocolErr := &(protocolerrors.ProtocolError{}); errors.As(err, &protocolErr) {
|
|
||||||
if !m.context.Config().DisableBanning && protocolErr.ShouldBan {
|
|
||||||
log.Warnf("Banning %s (reason: %s)", netConnection, protocolErr.Cause)
|
|
||||||
|
|
||||||
err := m.context.ConnectionManager().Ban(netConnection)
|
|
||||||
if err != nil && !errors.Is(err, addressmanager.ErrAddressNotFound) {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = outgoingRoute.Enqueue(appmessage.NewMsgReject(protocolErr.Error()))
|
|
||||||
if err != nil && !errors.Is(err, routerpkg.ErrRouteClosed) {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
netConnection.Disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errors.Is(err, routerpkg.ErrTimeout) {
|
|
||||||
log.Warnf("Got timeout from %s. Disconnecting...", netConnection)
|
|
||||||
netConnection.Disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errors.Is(err, routerpkg.ErrRouteClosed) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerFlows(router *routerpkg.Router, errChan chan error, isStopping *uint32) (flows []*flow) {
|
|
||||||
flows = m.registerAddressFlows(router, isStopping, errChan)
|
|
||||||
flows = append(flows, m.registerBlockRelayFlows(router, isStopping, errChan)...)
|
|
||||||
flows = append(flows, m.registerPingFlows(router, isStopping, errChan)...)
|
|
||||||
flows = append(flows, m.registerIBDFlows(router, isStopping, errChan)...)
|
|
||||||
flows = append(flows, m.registerTransactionRelayFlow(router, isStopping, errChan)...)
|
|
||||||
flows = append(flows, m.registerRejectsFlow(router, isStopping, errChan)...)
|
|
||||||
|
|
||||||
return flows
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerAddressFlows(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
|
||||||
outgoingRoute := router.OutgoingRoute()
|
|
||||||
|
|
||||||
return []*flow{
|
|
||||||
m.registerOneTimeFlow("SendAddresses", router, []appmessage.MessageCommand{appmessage.CmdRequestAddresses}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return addressexchange.SendAddresses(m.context, incomingRoute, outgoingRoute)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
m.registerOneTimeFlow("ReceiveAddresses", router, []appmessage.MessageCommand{appmessage.CmdAddresses}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return addressexchange.ReceiveAddresses(m.context, incomingRoute, outgoingRoute, peer)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerBlockRelayFlows(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
|
||||||
outgoingRoute := router.OutgoingRoute()
|
|
||||||
|
|
||||||
return []*flow{
|
|
||||||
m.registerFlow("HandleRelayInvs", router, []appmessage.MessageCommand{appmessage.CmdInvRelayBlock, appmessage.CmdBlock}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return blockrelay.HandleRelayInvs(m.context, incomingRoute,
|
|
||||||
outgoingRoute, peer)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
m.registerFlow("HandleRelayBlockRequests", router, []appmessage.MessageCommand{appmessage.CmdRequestRelayBlocks}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return blockrelay.HandleRelayBlockRequests(m.context, incomingRoute, outgoingRoute, peer)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerPingFlows(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
|
||||||
outgoingRoute := router.OutgoingRoute()
|
|
||||||
|
|
||||||
return []*flow{
|
|
||||||
m.registerFlow("ReceivePings", router, []appmessage.MessageCommand{appmessage.CmdPing}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return ping.ReceivePings(m.context, incomingRoute, outgoingRoute)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
m.registerFlow("SendPings", router, []appmessage.MessageCommand{appmessage.CmdPong}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return ping.SendPings(m.context, incomingRoute, outgoingRoute, peer)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerIBDFlows(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
|
||||||
outgoingRoute := router.OutgoingRoute()
|
|
||||||
|
|
||||||
return []*flow{
|
|
||||||
m.registerFlow("HandleIBD", router, []appmessage.MessageCommand{appmessage.CmdBlockLocator, appmessage.CmdIBDBlock,
|
|
||||||
appmessage.CmdDoneIBDBlocks}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return ibd.HandleIBD(m.context, incomingRoute, outgoingRoute, peer)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
m.registerFlow("RequestSelectedTip", router, []appmessage.MessageCommand{appmessage.CmdSelectedTip}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return selectedtip.RequestSelectedTip(m.context, incomingRoute, outgoingRoute, peer)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
m.registerFlow("HandleRequestSelectedTip", router, []appmessage.MessageCommand{appmessage.CmdRequestSelectedTip}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return selectedtip.HandleRequestSelectedTip(m.context, incomingRoute, outgoingRoute)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
m.registerFlow("HandleRequestBlockLocator", router, []appmessage.MessageCommand{appmessage.CmdRequestBlockLocator}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return ibd.HandleRequestBlockLocator(m.context, incomingRoute, outgoingRoute)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
m.registerFlow("HandleRequestIBDBlocks", router, []appmessage.MessageCommand{appmessage.CmdRequestIBDBlocks, appmessage.CmdRequestNextIBDBlocks}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return ibd.HandleRequestIBDBlocks(m.context, incomingRoute, outgoingRoute)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerTransactionRelayFlow(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
|
||||||
outgoingRoute := router.OutgoingRoute()
|
|
||||||
|
|
||||||
return []*flow{
|
|
||||||
m.registerFlow("HandleRelayedTransactions", router,
|
|
||||||
[]appmessage.MessageCommand{appmessage.CmdInvTransaction, appmessage.CmdTx, appmessage.CmdTransactionNotFound}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return relaytransactions.HandleRelayedTransactions(m.context, incomingRoute, outgoingRoute)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
m.registerFlow("HandleRequestTransactions", router,
|
|
||||||
[]appmessage.MessageCommand{appmessage.CmdRequestTransactions}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return relaytransactions.HandleRequestedTransactions(m.context, incomingRoute, outgoingRoute)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerRejectsFlow(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
|
||||||
outgoingRoute := router.OutgoingRoute()
|
|
||||||
|
|
||||||
return []*flow{
|
|
||||||
m.registerFlow("HandleRejects", router,
|
|
||||||
[]appmessage.MessageCommand{appmessage.CmdReject}, isStopping, errChan,
|
|
||||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
|
||||||
return rejects.HandleRejects(m.context, incomingRoute, outgoingRoute)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand, isStopping *uint32,
|
|
||||||
errChan chan error, initializeFunc flowInitializeFunc) *flow {
|
|
||||||
|
|
||||||
route, err := router.AddIncomingRoute(messageTypes)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &flow{
|
|
||||||
name: name,
|
|
||||||
executeFunc: func(peer *peerpkg.Peer) {
|
|
||||||
err := initializeFunc(route, peer)
|
|
||||||
if err != nil {
|
|
||||||
m.context.HandleError(err, name, isStopping, errChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) registerOneTimeFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand,
|
|
||||||
isStopping *uint32, stopChan chan error, initializeFunc flowInitializeFunc) *flow {
|
|
||||||
|
|
||||||
route, err := router.AddIncomingRoute(messageTypes)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &flow{
|
|
||||||
name: name,
|
|
||||||
executeFunc: func(peer *peerpkg.Peer) {
|
|
||||||
defer func() {
|
|
||||||
err := router.RemoveRoute(messageTypes)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := initializeFunc(route, peer)
|
|
||||||
if err != nil {
|
|
||||||
m.context.HandleError(err, name, isStopping, stopChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerHandshakeRoutes(router *routerpkg.Router) (
|
|
||||||
receiveVersionRoute *routerpkg.Route, sendVersionRoute *routerpkg.Route) {
|
|
||||||
receiveVersionRoute, err := router.AddIncomingRoute([]appmessage.MessageCommand{appmessage.CmdVersion})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendVersionRoute, err = router.AddIncomingRoute([]appmessage.MessageCommand{appmessage.CmdVerAck})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return receiveVersionRoute, sendVersionRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeHandshakeRoutes(router *routerpkg.Router) {
|
|
||||||
err := router.RemoveRoute([]appmessage.MessageCommand{appmessage.CmdVersion, appmessage.CmdVerAck})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package protocolerrors
|
|
||||||
|
|
||||||
import "github.com/pkg/errors"
|
|
||||||
|
|
||||||
// ProtocolError is an error that signifies a violation
|
|
||||||
// of the peer-to-peer protocol
|
|
||||||
type ProtocolError struct {
|
|
||||||
ShouldBan bool
|
|
||||||
Cause error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ProtocolError) Error() string {
|
|
||||||
return e.Cause.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ProtocolError) Unwrap() error {
|
|
||||||
return e.Cause
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf formats according to a format specifier and returns the string
|
|
||||||
// as a ProtocolError.
|
|
||||||
func Errorf(shouldBan bool, format string, args ...interface{}) error {
|
|
||||||
return &ProtocolError{
|
|
||||||
ShouldBan: shouldBan,
|
|
||||||
Cause: errors.Errorf(format, args...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a ProtocolError with the supplied message.
|
|
||||||
// New also records the stack trace at the point it was called.
|
|
||||||
func New(shouldBan bool, message string) error {
|
|
||||||
return &ProtocolError{
|
|
||||||
ShouldBan: shouldBan,
|
|
||||||
Cause: errors.New(message),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap wraps the given error and returns it as a ProtocolError.
|
|
||||||
func Wrap(shouldBan bool, err error, message string) error {
|
|
||||||
return &ProtocolError{
|
|
||||||
ShouldBan: shouldBan,
|
|
||||||
Cause: errors.Wrap(err, message),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapf wraps the given error with the given format and returns it as a ProtocolError.
|
|
||||||
func Wrapf(shouldBan bool, err error, format string, args ...interface{}) error {
|
|
||||||
return &ProtocolError{
|
|
||||||
ShouldBan: shouldBan,
|
|
||||||
Cause: errors.Wrapf(err, format, args...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
155
blockdag/accept.go
Normal file
155
blockdag/accept.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// 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 blockdag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/dbaccess"
|
||||||
|
"github.com/kaspanet/kaspad/util"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error {
|
||||||
|
blockHeader := &block.MsgBlock().Header
|
||||||
|
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
|
||||||
|
newNode.status = statusInvalidAncestor
|
||||||
|
dag.index.AddNode(newNode)
|
||||||
|
|
||||||
|
dbTx, err := dbaccess.NewTx()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dbTx.RollbackUnlessClosed()
|
||||||
|
err = dag.index.flushToDB(dbTx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return dbTx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybeAcceptBlock potentially accepts a block into the block DAG. It
|
||||||
|
// performs several validation checks which depend on its position within
|
||||||
|
// the block DAG before adding it. The block is expected to have already
|
||||||
|
// gone through ProcessBlock before calling this function with it.
|
||||||
|
//
|
||||||
|
// The flags are also passed to checkBlockContext and connectToDAG. See
|
||||||
|
// their documentation for how the flags modify their behavior.
|
||||||
|
//
|
||||||
|
// This function MUST be called with the dagLock held (for writes).
|
||||||
|
func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) error {
|
||||||
|
parents, err := lookupParentNodes(block, dag)
|
||||||
|
if err != nil {
|
||||||
|
var ruleErr RuleError
|
||||||
|
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
|
||||||
|
err := dag.addNodeToIndexWithInvalidAncestor(block)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The block must pass all of the validation rules which depend on the
|
||||||
|
// position of the block within the block DAG.
|
||||||
|
err = dag.checkBlockContext(block, parents, flags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new block node for the block and add it to the node index.
|
||||||
|
newNode, selectedParentAnticone := dag.newBlockNode(&block.MsgBlock().Header, parents)
|
||||||
|
newNode.status = statusDataStored
|
||||||
|
dag.index.AddNode(newNode)
|
||||||
|
|
||||||
|
// Insert the block into the database if it's not already there. Even
|
||||||
|
// though it is possible the block will ultimately fail to connect, it
|
||||||
|
// has already passed all proof-of-work and validity tests which means
|
||||||
|
// it would be prohibitively expensive for an attacker to fill up the
|
||||||
|
// disk with a bunch of blocks that fail to connect. This is necessary
|
||||||
|
// since it allows block download to be decoupled from the much more
|
||||||
|
// expensive connection logic. It also has some other nice properties
|
||||||
|
// such as making blocks that never become part of the DAG or
|
||||||
|
// blocks that fail to connect available for further analysis.
|
||||||
|
dbTx, err := dbaccess.NewTx()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dbTx.RollbackUnlessClosed()
|
||||||
|
blockExists, err := dbaccess.HasBlock(dbTx, block.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !blockExists {
|
||||||
|
err := storeBlock(dbTx, block)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = dag.index.flushToDB(dbTx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dbTx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that all the block's transactions are finalized
|
||||||
|
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||||
|
bluestParent := parents.bluest()
|
||||||
|
if !fastAdd {
|
||||||
|
if err := dag.validateAllTxsFinalized(block, newNode, bluestParent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block.SetBlueScore(newNode.blueScore)
|
||||||
|
|
||||||
|
// Connect the passed block to the DAG. This also handles validation of the
|
||||||
|
// transaction scripts.
|
||||||
|
chainUpdates, err := dag.addBlock(newNode, block, selectedParentAnticone, flags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the caller that the new block was accepted into the block
|
||||||
|
// DAG. The caller would typically want to react by relaying the
|
||||||
|
// inventory to other peers.
|
||||||
|
dag.dagLock.Unlock()
|
||||||
|
dag.sendNotification(NTBlockAdded, &BlockAddedNotificationData{
|
||||||
|
Block: block,
|
||||||
|
WasUnorphaned: flags&BFWasUnorphaned != 0,
|
||||||
|
})
|
||||||
|
if len(chainUpdates.addedChainBlockHashes) > 0 {
|
||||||
|
dag.sendNotification(NTChainChanged, &ChainChangedNotificationData{
|
||||||
|
RemovedChainBlockHashes: chainUpdates.removedChainBlockHashes,
|
||||||
|
AddedChainBlockHashes: chainUpdates.addedChainBlockHashes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
dag.dagLock.Lock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupParentNodes(block *util.Block, blockDAG *BlockDAG) (blockSet, error) {
|
||||||
|
header := block.MsgBlock().Header
|
||||||
|
parentHashes := header.ParentHashes
|
||||||
|
|
||||||
|
nodes := newBlockSet()
|
||||||
|
for _, parentHash := range parentHashes {
|
||||||
|
node := blockDAG.index.LookupNode(parentHash)
|
||||||
|
if node == nil {
|
||||||
|
str := fmt.Sprintf("parent block %s is unknown", parentHash)
|
||||||
|
return nil, ruleError(ErrParentBlockUnknown, str)
|
||||||
|
} else if blockDAG.index.NodeStatus(node).KnownInvalid() {
|
||||||
|
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
|
||||||
|
return nil, ruleError(ErrInvalidAncestorBlock, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.add(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
104
blockdag/accept_test.go
Normal file
104
blockdag/accept_test.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package blockdag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/dagconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMaybeAcceptBlockErrors(t *testing.T) {
|
||||||
|
// Create a new database and DAG instance to run tests against.
|
||||||
|
dag, teardownFunc, err := DAGSetup("TestMaybeAcceptBlockErrors", true, Config{
|
||||||
|
DAGParams: &dagconfig.SimnetParams,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestMaybeAcceptBlockErrors: Failed to setup DAG instance: %v", err)
|
||||||
|
}
|
||||||
|
defer teardownFunc()
|
||||||
|
|
||||||
|
dag.TestSetCoinbaseMaturity(0)
|
||||||
|
|
||||||
|
// Test rejecting the block if its parents are missing
|
||||||
|
orphanBlockFile := "blk_3B.dat"
|
||||||
|
loadedBlocks, err := LoadBlocks(filepath.Join("testdata/", orphanBlockFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestMaybeAcceptBlockErrors: "+
|
||||||
|
"Error loading file '%s': %s\n", orphanBlockFile, err)
|
||||||
|
}
|
||||||
|
block := loadedBlocks[0]
|
||||||
|
|
||||||
|
err = dag.maybeAcceptBlock(block, BFNone)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||||
|
"Expected: %s, got: <nil>", ErrParentBlockUnknown)
|
||||||
|
}
|
||||||
|
var ruleErr RuleError
|
||||||
|
if ok := errors.As(err, &ruleErr); !ok {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||||
|
"Expected RuleError but got %s", err)
|
||||||
|
} else if ruleErr.ErrorCode != ErrParentBlockUnknown {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||||
|
"Unexpected error code. Want: %s, got: %s", ErrParentBlockUnknown, ruleErr.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test rejecting the block if its parents are invalid
|
||||||
|
blocksFile := "blk_0_to_4.dat"
|
||||||
|
blocks, err := LoadBlocks(filepath.Join("testdata/", blocksFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestMaybeAcceptBlockErrors: "+
|
||||||
|
"Error loading file '%s': %s\n", blocksFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a valid block and mark it as invalid
|
||||||
|
block1 := blocks[1]
|
||||||
|
isOrphan, isDelayed, err := dag.ProcessBlock(block1, BFNone)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestMaybeAcceptBlockErrors: Valid block unexpectedly returned an error: %s", err)
|
||||||
|
}
|
||||||
|
if isDelayed {
|
||||||
|
t.Fatalf("TestMaybeAcceptBlockErrors: block 1 is too far in the future")
|
||||||
|
}
|
||||||
|
if isOrphan {
|
||||||
|
t.Fatalf("TestMaybeAcceptBlockErrors: incorrectly returned block 1 is an orphan")
|
||||||
|
}
|
||||||
|
blockNode1 := dag.index.LookupNode(block1.Hash())
|
||||||
|
dag.index.SetStatusFlags(blockNode1, statusValidateFailed)
|
||||||
|
|
||||||
|
block2 := blocks[2]
|
||||||
|
err = dag.maybeAcceptBlock(block2, BFNone)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||||
|
"Expected: %s, got: <nil>", ErrInvalidAncestorBlock)
|
||||||
|
}
|
||||||
|
if ok := errors.As(err, &ruleErr); !ok {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||||
|
"Expected RuleError but got %s", err)
|
||||||
|
} else if ruleErr.ErrorCode != ErrInvalidAncestorBlock {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||||
|
"Unexpected error. Want: %s, got: %s", ErrInvalidAncestorBlock, ruleErr.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set block1's status back to valid for next tests
|
||||||
|
dag.index.UnsetStatusFlags(blockNode1, statusValidateFailed)
|
||||||
|
|
||||||
|
// Test rejecting the block due to bad context
|
||||||
|
originalBits := block2.MsgBlock().Header.Bits
|
||||||
|
block2.MsgBlock().Header.Bits = 0
|
||||||
|
err = dag.maybeAcceptBlock(block2, BFNone)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||||
|
"Expected: %s, got: <nil>", ErrUnexpectedDifficulty)
|
||||||
|
}
|
||||||
|
if ok := errors.As(err, &ruleErr); !ok {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||||
|
"Expected RuleError but got %s", err)
|
||||||
|
} else if ruleErr.ErrorCode != ErrUnexpectedDifficulty {
|
||||||
|
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||||
|
"Unexpected error. Want: %s, got: %s", ErrUnexpectedDifficulty, ruleErr.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set block2's bits back to valid for next tests
|
||||||
|
block2.MsgBlock().Header.Bits = originalBits
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package blockdag
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
"github.com/kaspanet/kaspad/dagconfig"
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
"github.com/kaspanet/kaspad/util/daghash"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,14 +11,14 @@ import (
|
|||||||
func TestBlockHeap(t *testing.T) {
|
func TestBlockHeap(t *testing.T) {
|
||||||
// Create a new database and DAG instance to run tests against.
|
// Create a new database and DAG instance to run tests against.
|
||||||
dag, teardownFunc, err := DAGSetup("TestBlockHeap", true, Config{
|
dag, teardownFunc, err := DAGSetup("TestBlockHeap", true, Config{
|
||||||
DAGParams: &dagconfig.SimnetParams,
|
DAGParams: &dagconfig.MainnetParams,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestBlockHeap: Failed to setup DAG instance: %s", err)
|
t.Fatalf("TestBlockHeap: Failed to setup DAG instance: %s", err)
|
||||||
}
|
}
|
||||||
defer teardownFunc()
|
defer teardownFunc()
|
||||||
|
|
||||||
block0Header := dagconfig.SimnetParams.GenesisBlock.Header
|
block0Header := dagconfig.MainnetParams.GenesisBlock.Header
|
||||||
block0, _ := dag.newBlockNode(&block0Header, newBlockSet())
|
block0, _ := dag.newBlockNode(&block0Header, newBlockSet())
|
||||||
|
|
||||||
block100000Header := Block100000.Header
|
block100000Header := Block100000.Header
|
||||||
@@ -5,12 +5,10 @@
|
|||||||
package blockdag
|
package blockdag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/kaspanet/kaspad/dbaccess"
|
||||||
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
|
|
||||||
"github.com/kaspanet/kaspad/util"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
"github.com/kaspanet/kaspad/dagconfig"
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
"github.com/kaspanet/kaspad/util/daghash"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,11 +50,11 @@ func (bi *blockIndex) HaveBlock(hash *daghash.Hash) bool {
|
|||||||
// return nil if there is no entry for the hash.
|
// return nil if there is no entry for the hash.
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (bi *blockIndex) LookupNode(hash *daghash.Hash) (*blockNode, bool) {
|
func (bi *blockIndex) LookupNode(hash *daghash.Hash) *blockNode {
|
||||||
bi.RLock()
|
bi.RLock()
|
||||||
defer bi.RUnlock()
|
defer bi.RUnlock()
|
||||||
node, ok := bi.index[*hash]
|
node := bi.index[*hash]
|
||||||
return node, ok
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddNode adds the provided node to the block index and marks it as dirty.
|
// AddNode adds the provided node to the block index and marks it as dirty.
|
||||||
@@ -136,42 +134,3 @@ func (bi *blockIndex) flushToDB(dbContext *dbaccess.TxContext) error {
|
|||||||
func (bi *blockIndex) clearDirtyEntries() {
|
func (bi *blockIndex) clearDirtyEntries() {
|
||||||
bi.dirty = make(map[*blockNode]struct{})
|
bi.dirty = make(map[*blockNode]struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error {
|
|
||||||
blockHeader := &block.MsgBlock().Header
|
|
||||||
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
|
|
||||||
newNode.status = statusInvalidAncestor
|
|
||||||
dag.index.AddNode(newNode)
|
|
||||||
|
|
||||||
dbTx, err := dag.databaseContext.NewTx()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dbTx.RollbackUnlessClosed()
|
|
||||||
err = dag.index.flushToDB(dbTx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return dbTx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupParentNodes(block *util.Block, dag *BlockDAG) (blockSet, error) {
|
|
||||||
header := block.MsgBlock().Header
|
|
||||||
parentHashes := header.ParentHashes
|
|
||||||
|
|
||||||
nodes := newBlockSet()
|
|
||||||
for _, parentHash := range parentHashes {
|
|
||||||
node, ok := dag.index.LookupNode(parentHash)
|
|
||||||
if !ok {
|
|
||||||
str := fmt.Sprintf("parent block %s is unknown", parentHash)
|
|
||||||
return nil, ruleError(ErrParentBlockUnknown, str)
|
|
||||||
} else if dag.index.NodeStatus(node).KnownInvalid() {
|
|
||||||
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
|
|
||||||
return nil, ruleError(ErrInvalidAncestorBlock, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes.add(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes, nil
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package blockdag
|
package blockdag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
|
||||||
"github.com/kaspanet/kaspad/util/mstime"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/dagconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAncestorErrors(t *testing.T) {
|
func TestAncestorErrors(t *testing.T) {
|
||||||
@@ -17,7 +18,7 @@ func TestAncestorErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer teardownFunc()
|
defer teardownFunc()
|
||||||
|
|
||||||
node := newTestNode(dag, newBlockSet(), int32(0x10000000), 0, mstime.Now())
|
node := newTestNode(dag, newBlockSet(), int32(0x10000000), 0, time.Unix(0, 0))
|
||||||
node.blueScore = 2
|
node.blueScore = 2
|
||||||
ancestor := node.SelectedAncestor(3)
|
ancestor := node.SelectedAncestor(3)
|
||||||
if ancestor != nil {
|
if ancestor != nil {
|
||||||
102
blockdag/blocklocator.go
Normal file
102
blockdag/blocklocator.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package blockdag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/util/daghash"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockLocator is used to help locate a specific block. The algorithm for
|
||||||
|
// building the block locator is to add block hashes in reverse order on the
|
||||||
|
// block's selected parent chain until the desired stop block is reached.
|
||||||
|
// In order to keep the list of locator hashes to a reasonable number of entries,
|
||||||
|
// the step between each entry is doubled each loop iteration to exponentially
|
||||||
|
// decrease the number of hashes as a function of the distance from the block
|
||||||
|
// being located.
|
||||||
|
//
|
||||||
|
// For example, assume a selected parent chain with IDs as depicted below, and the
|
||||||
|
// stop block is genesis:
|
||||||
|
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||||
|
//
|
||||||
|
// The block locator for block 17 would be the hashes of blocks:
|
||||||
|
// [17 16 14 11 7 2 genesis]
|
||||||
|
type BlockLocator []*daghash.Hash
|
||||||
|
|
||||||
|
// BlockLocatorFromHashes returns a block locator from high and low hash.
|
||||||
|
// See BlockLocator for details on the algorithm used to create a block locator.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access.
|
||||||
|
func (dag *BlockDAG) BlockLocatorFromHashes(highHash, lowHash *daghash.Hash) (BlockLocator, error) {
|
||||||
|
dag.dagLock.RLock()
|
||||||
|
defer dag.dagLock.RUnlock()
|
||||||
|
|
||||||
|
highNode := dag.index.LookupNode(highHash)
|
||||||
|
lowNode := dag.index.LookupNode(lowHash)
|
||||||
|
|
||||||
|
return dag.blockLocator(highNode, lowNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockLocator returns a block locator for the passed high and low nodes.
|
||||||
|
// See the BlockLocator type comments for more details.
|
||||||
|
//
|
||||||
|
// This function MUST be called with the DAG state lock held (for reads).
|
||||||
|
func (dag *BlockDAG) blockLocator(highNode, lowNode *blockNode) (BlockLocator, error) {
|
||||||
|
// We use the selected parent of the high node, so the
|
||||||
|
// block locator won't contain the high node.
|
||||||
|
highNode = highNode.selectedParent
|
||||||
|
|
||||||
|
node := highNode
|
||||||
|
step := uint64(1)
|
||||||
|
locator := make(BlockLocator, 0)
|
||||||
|
for node != nil {
|
||||||
|
locator = append(locator, node.hash)
|
||||||
|
|
||||||
|
// Nothing more to add once the low node has been added.
|
||||||
|
if node.blueScore <= lowNode.blueScore {
|
||||||
|
if node != lowNode {
|
||||||
|
return nil, errors.Errorf("highNode and lowNode are " +
|
||||||
|
"not in the same selected parent chain.")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate blueScore of previous node to include ensuring the
|
||||||
|
// final node is lowNode.
|
||||||
|
nextBlueScore := node.blueScore - step
|
||||||
|
if nextBlueScore < lowNode.blueScore {
|
||||||
|
nextBlueScore = lowNode.blueScore
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk backwards through the nodes to the correct ancestor.
|
||||||
|
node = node.SelectedAncestor(nextBlueScore)
|
||||||
|
|
||||||
|
// Double the distance between included hashes.
|
||||||
|
step *= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return locator, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindNextLocatorBoundaries returns the lowest unknown block locator, hash
|
||||||
|
// and the highest known block locator hash. This is used to create the
|
||||||
|
// next block locator to find the highest shared known chain block with the
|
||||||
|
// sync peer.
|
||||||
|
//
|
||||||
|
// This function MUST be called with the DAG state lock held (for reads).
|
||||||
|
func (dag *BlockDAG) FindNextLocatorBoundaries(locator BlockLocator) (highHash, lowHash *daghash.Hash) {
|
||||||
|
// Find the most recent locator block hash in the DAG. In the case none of
|
||||||
|
// the hashes in the locator are in the DAG, fall back to the genesis block.
|
||||||
|
lowNode := dag.genesis
|
||||||
|
nextBlockLocatorIndex := int64(len(locator) - 1)
|
||||||
|
for i, hash := range locator {
|
||||||
|
node := dag.index.LookupNode(hash)
|
||||||
|
if node != nil {
|
||||||
|
lowNode = node
|
||||||
|
nextBlockLocatorIndex = int64(i) - 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nextBlockLocatorIndex < 0 {
|
||||||
|
return nil, lowNode.hash
|
||||||
|
}
|
||||||
|
return locator[nextBlockLocatorIndex], lowNode.hash
|
||||||
|
}
|
||||||
@@ -6,14 +6,13 @@ package blockdag
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"github.com/kaspanet/kaspad/dagconfig"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
|
||||||
"github.com/kaspanet/kaspad/util/mstime"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
"github.com/kaspanet/kaspad/util/daghash"
|
||||||
|
"github.com/kaspanet/kaspad/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// blockStatus is a bit field representing the validation state of the block.
|
// blockStatus is a bit field representing the validation state of the block.
|
||||||
@@ -106,12 +105,12 @@ type blockNode struct {
|
|||||||
// anticone of its selected parent (parent with highest blue score).
|
// anticone of its selected parent (parent with highest blue score).
|
||||||
// selectedParentAnticone is used to update reachability data we store for future reachability queries.
|
// selectedParentAnticone is used to update reachability data we store for future reachability queries.
|
||||||
// This function is NOT safe for concurrent access.
|
// This function is NOT safe for concurrent access.
|
||||||
func (dag *BlockDAG) newBlockNode(blockHeader *appmessage.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
|
func (dag *BlockDAG) newBlockNode(blockHeader *wire.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
|
||||||
node = &blockNode{
|
node = &blockNode{
|
||||||
parents: parents,
|
parents: parents,
|
||||||
children: make(blockSet),
|
children: make(blockSet),
|
||||||
blueScore: math.MaxUint64, // Initialized to the max value to avoid collisions with the genesis block
|
blueScore: math.MaxUint64, // Initialized to the max value to avoid collisions with the genesis block
|
||||||
timestamp: dag.Now().UnixMilliseconds(),
|
timestamp: dag.Now().Unix(),
|
||||||
bluesAnticoneSizes: make(map[*blockNode]dagconfig.KType),
|
bluesAnticoneSizes: make(map[*blockNode]dagconfig.KType),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +120,7 @@ func (dag *BlockDAG) newBlockNode(blockHeader *appmessage.BlockHeader, parents b
|
|||||||
node.version = blockHeader.Version
|
node.version = blockHeader.Version
|
||||||
node.bits = blockHeader.Bits
|
node.bits = blockHeader.Bits
|
||||||
node.nonce = blockHeader.Nonce
|
node.nonce = blockHeader.Nonce
|
||||||
node.timestamp = blockHeader.Timestamp.UnixMilliseconds()
|
node.timestamp = blockHeader.Timestamp.Unix()
|
||||||
node.hashMerkleRoot = blockHeader.HashMerkleRoot
|
node.hashMerkleRoot = blockHeader.HashMerkleRoot
|
||||||
node.acceptedIDMerkleRoot = blockHeader.AcceptedIDMerkleRoot
|
node.acceptedIDMerkleRoot = blockHeader.AcceptedIDMerkleRoot
|
||||||
node.utxoCommitment = blockHeader.UTXOCommitment
|
node.utxoCommitment = blockHeader.UTXOCommitment
|
||||||
@@ -160,15 +159,15 @@ func (node *blockNode) less(other *blockNode) bool {
|
|||||||
// Header constructs a block header from the node and returns it.
|
// Header constructs a block header from the node and returns it.
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (node *blockNode) Header() *appmessage.BlockHeader {
|
func (node *blockNode) Header() *wire.BlockHeader {
|
||||||
// No lock is needed because all accessed fields are immutable.
|
// No lock is needed because all accessed fields are immutable.
|
||||||
return &appmessage.BlockHeader{
|
return &wire.BlockHeader{
|
||||||
Version: node.version,
|
Version: node.version,
|
||||||
ParentHashes: node.ParentHashes(),
|
ParentHashes: node.ParentHashes(),
|
||||||
HashMerkleRoot: node.hashMerkleRoot,
|
HashMerkleRoot: node.hashMerkleRoot,
|
||||||
AcceptedIDMerkleRoot: node.acceptedIDMerkleRoot,
|
AcceptedIDMerkleRoot: node.acceptedIDMerkleRoot,
|
||||||
UTXOCommitment: node.utxoCommitment,
|
UTXOCommitment: node.utxoCommitment,
|
||||||
Timestamp: node.time(),
|
Timestamp: time.Unix(node.timestamp, 0),
|
||||||
Bits: node.bits,
|
Bits: node.bits,
|
||||||
Nonce: node.nonce,
|
Nonce: node.nonce,
|
||||||
}
|
}
|
||||||
@@ -205,13 +204,13 @@ func (node *blockNode) RelativeAncestor(distance uint64) *blockNode {
|
|||||||
// prior to, and including, the block node.
|
// prior to, and including, the block node.
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (node *blockNode) PastMedianTime(dag *BlockDAG) mstime.Time {
|
func (node *blockNode) PastMedianTime(dag *BlockDAG) time.Time {
|
||||||
window := blueBlockWindow(node, 2*dag.TimestampDeviationTolerance-1)
|
window := blueBlockWindow(node, 2*dag.TimestampDeviationTolerance-1)
|
||||||
medianTimestamp, err := window.medianTimestamp()
|
medianTimestamp, err := window.medianTimestamp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("blueBlockWindow: %s", err))
|
panic(fmt.Sprintf("blueBlockWindow: %s", err))
|
||||||
}
|
}
|
||||||
return mstime.UnixMilliseconds(medianTimestamp)
|
return time.Unix(medianTimestamp, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *blockNode) ParentHashes() []*daghash.Hash {
|
func (node *blockNode) ParentHashes() []*daghash.Hash {
|
||||||
@@ -224,14 +223,10 @@ func (node *blockNode) isGenesis() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (node *blockNode) finalityScore(dag *BlockDAG) uint64 {
|
func (node *blockNode) finalityScore(dag *BlockDAG) uint64 {
|
||||||
return node.blueScore / uint64(dag.FinalityInterval())
|
return node.blueScore / uint64(dag.dagParams.FinalityInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a string that contains the block hash.
|
// String returns a string that contains the block hash.
|
||||||
func (node blockNode) String() string {
|
func (node blockNode) String() string {
|
||||||
return node.hash.String()
|
return node.hash.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *blockNode) time() mstime.Time {
|
|
||||||
return mstime.UnixMilliseconds(node.timestamp)
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package blockdag
|
package blockdag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
"github.com/kaspanet/kaspad/dagconfig"
|
||||||
"github.com/kaspanet/kaspad/util/daghash"
|
"github.com/kaspanet/kaspad/util/daghash"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user